diff --git a/lib/src/boundimage.rs b/lib/src/boundimage.rs index f5b552c3c..bc78d538a 100644 --- a/lib/src/boundimage.rs +++ b/lib/src/boundimage.rs @@ -108,6 +108,7 @@ pub(crate) fn query_bound_images(root: &Dir) -> Result> { #[cfg(feature = "install")] impl ResolvedBoundImage { + #[context("resolving bound image {}", src.image)] pub(crate) async fn from_image(src: &BoundImage) -> Result { let proxy = containers_image_proxy::ImageProxy::new().await?; let img = proxy @@ -148,7 +149,10 @@ fn parse_container_file(file_contents: &tini::Ini) -> Result { } #[context("Pulling bound images")] -pub(crate) async fn pull_images(sysroot: &Storage, bound_images: Vec) -> Result<()> { +pub(crate) async fn pull_images( + sysroot: &Storage, + bound_images: Vec, +) -> Result<()> { tracing::debug!("Pulling bound images: {}", bound_images.len()); // Yes, the usage of NonZeroUsize here is...maybe odd looking, but I find // it an elegant way to divide (empty vector, non empty vector) since diff --git a/lib/src/install.rs b/lib/src/install.rs index 29e747081..2f8a81297 100644 --- a/lib/src/install.rs +++ b/lib/src/install.rs @@ -44,6 +44,7 @@ use rustix::fs::{FileTypeExt, MetadataExt as _}; use serde::{Deserialize, Serialize}; use self::baseline::InstallBlockDeviceOpts; +use crate::boundimage::{BoundImage, ResolvedBoundImage}; use crate::containerenv::ContainerExecutionInfo; use crate::mount::Filesystem; use crate::spec::ImageReference; @@ -165,11 +166,16 @@ pub(crate) struct InstallConfigOpts { #[serde(default)] pub(crate) generic_image: bool, - /// Do not pull any "logically bound" images at install time. + /// Do not resolve any "logically bound" images at install time. #[clap(long, hide = true)] #[serde(default)] pub(crate) skip_bound_images: bool, + /// Pull "logically bound" images at install time. + #[clap(long)] + #[serde(default)] + pub(crate) pull: bool, + /// The stateroot name to use. Defaults to `default`. #[clap(long)] pub(crate) stateroot: Option, @@ -1271,7 +1277,7 @@ async fn install_with_sysroot( rootfs: &RootSetup, sysroot: &Storage, boot_uuid: &str, - bound_images: &[crate::boundimage::ResolvedBoundImage], + bound_images: BoundImages, ) -> Result<()> { // And actually set up the container in that root, returning a deployment and // the aleph state (see below). @@ -1298,18 +1304,64 @@ async fn install_with_sysroot( tracing::debug!("Installed bootloader"); tracing::debug!("Perfoming post-deployment operations"); - // Note that we *always* initialize this container storage, even - // if there are no bound images today. + + // Note that we *always* initialize this container storage, even if there are no bound images + // today. let imgstore = sysroot.get_ensure_imgstore()?; - // Now copy each bound image from the host's container storage into the target. - for image in bound_images { - let image = image.image.as_str(); - imgstore.pull_from_host_storage(image).await?; + + match bound_images { + BoundImages::Skip => {} + BoundImages::Resolved(resolved_bound_images) => { + // Now copy each bound image from the host's container storage into the target. + for image in resolved_bound_images { + let image = image.image.as_str(); + imgstore.pull_from_host_storage(image).await?; + } + } + BoundImages::Unresolved(bound_images) => { + crate::boundimage::pull_images(sysroot, bound_images) + .await + .context("pulling bound images")?; + } } Ok(()) } +enum BoundImages { + Skip, + Resolved(Vec), + Unresolved(Vec), +} + +impl BoundImages { + async fn from_state(state: &State) -> Result { + let bound_images = (!state.config_opts.skip_bound_images) + .then(|| crate::boundimage::query_bound_images(&state.container_root)) + .transpose()?; + + Ok(match bound_images { + Some(bound_images) => { + tracing::debug!("bound images={bound_images:?}"); + if state.config_opts.pull { + // No need to resolve the images, we will pull them into the target later + BoundImages::Unresolved(bound_images) + } else { + // Verify each bound image is present in the container storage + let mut r = Vec::with_capacity(bound_images.len()); + for image in bound_images { + let resolved = ResolvedBoundImage::from_image(&image).await?; + tracing::debug!("Resolved {}: {}", resolved.image, resolved.digest); + r.push(resolved) + } + BoundImages::Resolved(r) + } + } + None => BoundImages::Skip, + }) + } +} + async fn install_to_filesystem_impl(state: &State, rootfs: &mut RootSetup) -> Result<()> { if matches!(state.selinux_state, SELinuxFinalState::ForceTargetDisabled) { rootfs.kargs.push("selinux=0".to_string()); @@ -1336,28 +1388,12 @@ async fn install_to_filesystem_impl(state: &State, rootfs: &mut RootSetup) -> Re .ok_or_else(|| anyhow!("No uuid for boot/root"))?; tracing::debug!("boot uuid={boot_uuid}"); - let bound_images = if state.config_opts.skip_bound_images { - Vec::new() - } else { - crate::boundimage::query_bound_images(&state.container_root)? - }; - tracing::debug!("bound images={bound_images:?}"); - - // Verify each bound image is present in the container storage - let bound_images = { - let mut r = Vec::with_capacity(bound_images.len()); - for image in bound_images { - let resolved = crate::boundimage::ResolvedBoundImage::from_image(&image).await?; - tracing::debug!("Resolved {}: {}", resolved.image, resolved.digest); - r.push(resolved) - } - r - }; + let bound_images = BoundImages::from_state(state).await?; // Initialize the ostree sysroot (repo, stateroot, etc.) { let sysroot = initialize_ostree_root(state, rootfs).await?; - install_with_sysroot(state, rootfs, &sysroot, &boot_uuid, &bound_images).await?; + install_with_sysroot(state, rootfs, &sysroot, &boot_uuid, bound_images).await?; // We must drop the sysroot here in order to close any open file // descriptors. }