mirror of
https://github.com/NVIDIA/OpenShell.git
synced 2026-10-28 07:13:04 +08:00
fix(cluster): remove stale image on destroy and verify architecture after pull
Destroy now removes the cluster image so the next deploy always pulls the latest version from the registry. After pulling on a remote host the image architecture is verified against the host platform, surfacing a clear error instead of a cryptic 'exec format error' at container start.
This commit is contained in:
@@ -9,7 +9,7 @@ use bollard::models::{
|
||||
};
|
||||
use bollard::query_parameters::{
|
||||
CreateContainerOptions, CreateImageOptions, InspectContainerOptions, InspectNetworkOptions,
|
||||
RemoveContainerOptions, RemoveVolumeOptions, StartContainerOptions,
|
||||
RemoveContainerOptions, RemoveImageOptions, RemoveVolumeOptions, StartContainerOptions,
|
||||
};
|
||||
use futures::StreamExt;
|
||||
use miette::{IntoDiagnostic, Result, WrapErr};
|
||||
@@ -427,6 +427,15 @@ pub async fn destroy_cluster_resources(
|
||||
let container_name = container_name(name);
|
||||
let volume_name = volume_name(name);
|
||||
|
||||
// Capture the container's image reference before removing the container so
|
||||
// we can clean it up afterwards. This prevents stale images from being
|
||||
// re-used on subsequent deploys.
|
||||
let container_image = docker
|
||||
.inspect_container(&container_name, None::<InspectContainerOptions>)
|
||||
.await
|
||||
.ok()
|
||||
.and_then(|info| info.image);
|
||||
|
||||
let _ = stop_container(docker, &container_name).await;
|
||||
|
||||
let remove_container = docker
|
||||
@@ -444,6 +453,28 @@ pub async fn destroy_cluster_resources(
|
||||
return Err(err).into_diagnostic();
|
||||
}
|
||||
|
||||
// Remove the cluster image so the next deploy always pulls the latest
|
||||
// version from the registry instead of reusing a stale local copy.
|
||||
if let Some(ref image_id) = container_image {
|
||||
tracing::debug!("Removing cluster image: {}", image_id);
|
||||
let result = docker
|
||||
.remove_image(
|
||||
image_id,
|
||||
Some(RemoveImageOptions {
|
||||
force: true,
|
||||
noprune: true,
|
||||
..Default::default()
|
||||
}),
|
||||
None,
|
||||
)
|
||||
.await;
|
||||
if let Err(err) = result
|
||||
&& !is_not_found(&err)
|
||||
{
|
||||
tracing::warn!("Failed to remove cluster image {}: {}", image_id, err);
|
||||
}
|
||||
}
|
||||
|
||||
let remove_volume = docker
|
||||
.remove_volume(&volume_name, Some(RemoveVolumeOptions { force: true }))
|
||||
.await;
|
||||
|
||||
@@ -252,6 +252,29 @@ pub async fn pull_remote_image(
|
||||
format!("failed to tag {registry_image} as {target_repo}:{target_tag} on remote")
|
||||
})?;
|
||||
|
||||
// Verify that the pulled image matches the expected architecture.
|
||||
// This catches cases where the registry returned the wrong platform
|
||||
// variant (e.g., amd64 on an arm64 host) which would cause an
|
||||
// "exec format error" at container start time.
|
||||
let inspect = remote
|
||||
.inspect_image(image_ref)
|
||||
.await
|
||||
.into_diagnostic()
|
||||
.wrap_err_with(|| format!("failed to inspect pulled image {image_ref} on remote"))?;
|
||||
|
||||
let actual_arch = inspect.architecture.as_deref().unwrap_or("unknown");
|
||||
if actual_arch != remote_platform.arch {
|
||||
return Err(miette::miette!(
|
||||
"architecture mismatch: pulled image {image_ref} is {actual_arch} but remote host is {expected}; \
|
||||
try removing stale images on the remote host and re-deploying",
|
||||
expected = remote_platform.arch,
|
||||
));
|
||||
}
|
||||
info!(
|
||||
"Verified image architecture: {} matches remote host",
|
||||
actual_arch
|
||||
);
|
||||
|
||||
on_progress(format!("[status] Image {image_ref} ready on remote host"));
|
||||
info!("Remote image pull and tag complete: {}", image_ref);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user