diff --git a/crates/openshell-driver-docker/src/lib.rs b/crates/openshell-driver-docker/src/lib.rs index 4c9d2ac2bc..308a37f266 100644 --- a/crates/openshell-driver-docker/src/lib.rs +++ b/crates/openshell-driver-docker/src/lib.rs @@ -2960,6 +2960,7 @@ impl DockerComputeDriver { } async fn pull_image(&self, sandbox_id: &str, image: &str) -> Result<(), Status> { + let image_ref = normalize_docker_image_reference(image); self.publish_docker_progress( sandbox_id, "Pulling", @@ -2968,7 +2969,7 @@ impl DockerComputeDriver { ); let mut stream = self.docker.create_image( Some(CreateImageOptions { - from_image: Some(image.to_string()), + from_image: Some(image_ref), ..Default::default() }), None, @@ -2999,6 +3000,19 @@ impl DockerComputeDriver { } } +fn normalize_docker_image_reference(image: &str) -> String { + if image.contains('@') { + return image.to_string(); + } + + let last_slash = image.rfind('/').map_or(0, |index| index + 1); + if image[last_slash..].contains(':') { + image.to_string() + } else { + format!("{image}:latest") + } +} + // Standalone and in-process servers both use this wrapper. Delegating to the // driver's canonical tonic implementation keeps request validation and Docker // operation spans identical across both deployment modes. diff --git a/crates/openshell-driver-docker/src/tests.rs b/crates/openshell-driver-docker/src/tests.rs index 8aa05302e0..74972f6842 100644 --- a/crates/openshell-driver-docker/src/tests.rs +++ b/crates/openshell-driver-docker/src/tests.rs @@ -24,6 +24,33 @@ use std::io::Read as _; use std::sync::Arc; use tempfile::TempDir; +#[test] +fn docker_image_reference_defaults_bare_images_to_latest() { + assert_eq!( + normalize_docker_image_reference("nicolaka/netshoot"), + "nicolaka/netshoot:latest" + ); + assert_eq!( + normalize_docker_image_reference("ubuntu"), + "ubuntu:latest" + ); +} + +#[test] +fn docker_image_reference_preserves_tags_digests_and_registry_ports() { + assert_eq!( + normalize_docker_image_reference("localhost:5000/nicolaka/netshoot"), + "localhost:5000/nicolaka/netshoot:latest" + ); + for image in [ + "nicolaka/netshoot:v0.16", + "nicolaka/netshoot@sha256:abc", + "localhost:5000/nicolaka/netshoot:v0.16", + ] { + assert_eq!(normalize_docker_image_reference(image), image); + } +} + fn test_launch_authentication() -> Vec { serde_json::to_vec(&SandboxLaunchAuthentication { supervisor: SupervisorAuthBundle {