Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 10 additions & 3 deletions crates/openshell-bootstrap/src/pki.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,12 @@ pub struct PkiBundle {
pub jwt_key_id: String,
}

/// Default SANs always included on the server certificate. Covers the host
/// aliases used by every supported runtime: Kubernetes service DNS,
/// Default SANs always included on the server certificate.
///
/// Covers the host aliases used by every supported runtime: Kubernetes service DNS,
/// `host.docker.internal` for Docker Desktop and rootless Docker on Linux,
/// and `host.containers.internal` for Podman containers reaching their host.
const DEFAULT_SERVER_SANS: &[&str] = &[
pub const DEFAULT_SERVER_SANS: &[&str] = &[
"openshell",
"openshell.openshell.svc",
"openshell.openshell.svc.cluster.local",
Expand Down Expand Up @@ -184,4 +185,10 @@ mod tests {
// Should have all default SANs + 2 extras
assert_eq!(sans.len(), DEFAULT_SERVER_SANS.len() + 2);
}

#[test]
fn default_server_sans_include_local_container_hostnames() {
assert!(DEFAULT_SERVER_SANS.contains(&"host.docker.internal"));
assert!(DEFAULT_SERVER_SANS.contains(&"host.containers.internal"));
}
}
41 changes: 40 additions & 1 deletion crates/openshell-driver-docker/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1869,10 +1869,27 @@ fn linux_supervisor_candidates(daemon_arch: &str) -> Vec<PathBuf> {
/// inside the digest-keyed directory and renamed into place, so concurrent
/// gateway starts don't observe a partial file.
async fn extract_supervisor_bin_from_image(docker: &Docker, image: &str) -> CoreResult<PathBuf> {
let refresh_attempted = if supervisor_image_should_refresh(image) {
info!(image = image, "Refreshing mutable docker supervisor image");
match pull_supervisor_image(docker, image).await {
Ok(()) => true,
Err(err) => {
warn!(
image = image,
error = %err,
"failed to refresh mutable docker supervisor image; falling back to local image if present",
);
true
}
}
} else {
false
};

// Inspect first to see if the image is already present; only pull on miss.
let inspect = match docker.inspect_image(image).await {
Ok(inspect) => inspect,
Err(err) if is_not_found_error(&err) => {
Err(err) if is_not_found_error(&err) && !refresh_attempted => {
info!(image = image, "Pulling docker supervisor image");
pull_supervisor_image(docker, image).await?;
docker.inspect_image(image).await.map_err(|err| {
Expand All @@ -1881,6 +1898,11 @@ async fn extract_supervisor_bin_from_image(docker: &Docker, image: &str) -> Core
))
})?
}
Err(err) if is_not_found_error(&err) => {
return Err(Error::config(format!(
"docker supervisor image '{image}' is not present locally after refresh attempt",
)));
}
Err(err) => {
return Err(Error::config(format!(
"failed to inspect docker supervisor image '{image}': {err}",
Expand Down Expand Up @@ -1926,6 +1948,23 @@ async fn extract_supervisor_bin_from_image(docker: &Docker, image: &str) -> Core
Ok(cache_path)
}

fn supervisor_image_should_refresh(image: &str) -> bool {
matches!(supervisor_image_tag(image), Some("dev" | "latest"))
}

fn supervisor_image_tag(image: &str) -> Option<&str> {
if image.contains('@') {
return None;
}

let image_name = image.rsplit('/').next().unwrap_or(image);
image_name
.rsplit_once(':')
.map_or(Some("latest"), |(_, tag)| {
if tag.is_empty() { None } else { Some(tag) }
})
}

async fn pull_supervisor_image(docker: &Docker, image: &str) -> CoreResult<()> {
let mut stream = docker.create_image(
Some(CreateImageOptions {
Expand Down
19 changes: 19 additions & 0 deletions crates/openshell-driver-docker/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -903,6 +903,25 @@ fn docker_supervisor_image_tag_sanitizes_build_metadata_for_docker() {
);
}

#[test]
fn docker_supervisor_image_refreshes_mutable_tags_only() {
assert!(supervisor_image_should_refresh(
"ghcr.io/nvidia/openshell/supervisor:dev"
));
assert!(supervisor_image_should_refresh(
"ghcr.io/nvidia/openshell/supervisor:latest"
));
assert!(supervisor_image_should_refresh(
"ghcr.io/nvidia/openshell/supervisor"
));
assert!(!supervisor_image_should_refresh(
"ghcr.io/nvidia/openshell/supervisor:0.0.47-dev.13-g57b71c68f"
));
assert!(!supervisor_image_should_refresh(
"ghcr.io/nvidia/openshell/supervisor@sha256:abc123"
));
}

#[test]
fn supervisor_cache_path_namespaces_by_digest_under_openshell_data_dir() {
let base = PathBuf::from("/var/cache/share");
Expand Down
60 changes: 56 additions & 4 deletions crates/openshell-driver-podman/src/driver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -328,15 +328,16 @@ impl PodmanComputeDriver {

// 1a. Pull the supervisor image if needed. The supervisor binary
// is shipped in a standalone OCI image and mounted into sandbox
// containers via Podman's type=image mount. Using "missing"
// policy so the image is only pulled once and then cached.
// containers via Podman's type=image mount. Refresh mutable tags
// like latest/dev, but avoid registry checks for pinned images.
let supervisor_pull_policy = supervisor_image_pull_policy(&self.config.supervisor_image);
info!(
image = %self.config.supervisor_image,
policy = "missing",
policy = supervisor_pull_policy,
"Ensuring supervisor image"
);
self.client
.pull_image(&self.config.supervisor_image, "missing")
.pull_image(&self.config.supervisor_image, supervisor_pull_policy)
.await
.map_err(ComputeDriverError::from)?;

Expand Down Expand Up @@ -589,6 +590,31 @@ impl PodmanComputeDriver {
}
}

fn supervisor_image_pull_policy(image: &str) -> &'static str {
if supervisor_image_should_refresh(image) {
"newer"
} else {
"missing"
}
}

fn supervisor_image_should_refresh(image: &str) -> bool {
matches!(supervisor_image_tag(image), Some("dev" | "latest"))
}

fn supervisor_image_tag(image: &str) -> Option<&str> {
if image.contains('@') {
return None;
}

let image_name = image.rsplit('/').next().unwrap_or(image);
image_name
.rsplit_once(':')
.map_or(Some("latest"), |(_, tag)| {
if tag.is_empty() { None } else { Some(tag) }
})
}

/// Check whether the current user has subuid/subgid ranges configured.
///
/// Rootless Podman requires entries in `/etc/subuid` and `/etc/subgid` for
Expand Down Expand Up @@ -719,6 +745,32 @@ mod tests {
assert_eq!(cfg.grpc_endpoint, "https://gateway.internal:9000");
}

#[test]
fn supervisor_pull_policy_refreshes_mutable_tags_only() {
assert_eq!(
supervisor_image_pull_policy("ghcr.io/nvidia/openshell/supervisor:dev"),
"newer"
);
assert_eq!(
supervisor_image_pull_policy("ghcr.io/nvidia/openshell/supervisor:latest"),
"newer"
);
assert_eq!(
supervisor_image_pull_policy("ghcr.io/nvidia/openshell/supervisor"),
"newer"
);
assert_eq!(
supervisor_image_pull_policy(
"ghcr.io/nvidia/openshell/supervisor:0.0.47-dev.13-g57b71c68f"
),
"missing"
);
assert_eq!(
supervisor_image_pull_policy("ghcr.io/nvidia/openshell/supervisor@sha256:abc123"),
"missing"
);
}

fn test_driver(socket_path: PathBuf) -> PodmanComputeDriver {
let config = PodmanComputeConfig {
socket_path,
Expand Down
Loading
Loading