diff --git a/crates/lib/src/cli.rs b/crates/lib/src/cli.rs index 3412a6b03..d6486926d 100644 --- a/crates/lib/src/cli.rs +++ b/crates/lib/src/cli.rs @@ -421,6 +421,15 @@ pub(crate) enum ImageOpts { /// The image to pull image: String, }, + /// Copy a container image to the bootc-owned container storage. + /// + /// This copies a container image from various sources (registries, OCI archives, + /// containers-storage, etc.) to the bootc-owned storage, which is used for + /// Logically Bound Images and other bootc functionality. + CopyToBootcStorage { + /// The source image to copy (supports various transports like registry:, containers-storage:, oci-archive:, etc.) + source: String, + }, /// Wrapper for selected `podman image` subcommands in bootc storage. #[clap(subcommand)] Cmd(ImageCmdOpts), @@ -1351,6 +1360,9 @@ async fn run_from_opt(opt: Opt) -> Result<()> { .pull_from_host_storage(&image) .await } + ImageOpts::CopyToBootcStorage { source } => { + crate::image::copy_to_bootc_storage_entrypoint(&source).await + } ImageOpts::Cmd(opt) => { let storage = get_storage().await?; let imgstore = storage.get_ensure_imgstore()?; diff --git a/crates/lib/src/image.rs b/crates/lib/src/image.rs index ad984ed6f..95c661daf 100644 --- a/crates/lib/src/image.rs +++ b/crates/lib/src/image.rs @@ -169,6 +169,13 @@ pub(crate) async fn push_entrypoint(source: Option<&str>, target: Option<&str>) Ok(()) } +/// Implementation of `bootc image copy-to-bootc-storage`. +#[context("Copying image to bootc storage")] +pub(crate) async fn copy_to_bootc_storage_entrypoint(source: &str) -> Result<()> { + let sysroot = crate::cli::get_storage().await?; + sysroot.get_ensure_imgstore()?.copy_to_storage(source).await +} + /// Thin wrapper for invoking `podman image ` but set up for our internal /// image store (as distinct from /var/lib/containers default). pub(crate) async fn imgcmd_entrypoint( diff --git a/crates/lib/src/podstorage.rs b/crates/lib/src/podstorage.rs index 6ac8b0a5b..65ddc3a3d 100644 --- a/crates/lib/src/podstorage.rs +++ b/crates/lib/src/podstorage.rs @@ -376,6 +376,63 @@ impl CStorage { Ok(()) } + /// Copy an image from any source to this bootc-owned storage. + /// This is more generic than pull_from_host_storage and supports various transports. + #[context("Copying to bootc storage: {source}")] + pub(crate) async fn copy_to_storage(&self, source: &str) -> Result<()> { + let temp_runroot = TempDir::new(cap_std::ambient_authority())?; + let mut cmd = Command::new("skopeo"); + cmd.stdin(Stdio::null()); + + bind_storage_roots(&mut cmd, &self.storage_root, &temp_runroot)?; + + // Parse the source to determine the transport and image name + let source_ref = + if source.contains("://") || (source.contains(':') && !source.starts_with('/')) { + // Looks like it has a transport specified or is a registry reference + source.to_string() + } else { + // Assume containers-storage if no transport is specified + format!("containers-storage:{}", source) + }; + + // Extract the image name (without transport prefix) for the destination + let image_name = if let Some(pos) = source.rfind('/') { + &source[pos + 1..] + } else if let Some(pos) = source.find(':') { + // Handle cases like "containers-storage:imagename" or "registry:imagename" + let after_colon = &source[pos + 1..]; + if after_colon.contains('/') { + after_colon.split('/').last().unwrap_or(after_colon) + } else { + after_colon + } + } else { + source + }; + + // The destination in our bootc-owned storage + let storage_dest = format!( + "containers-storage:[overlay@{STORAGE_ALIAS_DIR}+/proc/self/fd/{STORAGE_RUN_FD}]{image_name}" + ); + + cmd.args(["copy", &source_ref, &storage_dest]); + + // Handle authentication if available + let authfile = ostree_ext::globals::get_global_authfile(&self.sysroot)? + .map(|(authfile, _fd)| authfile); + if let Some(authfile) = authfile { + cmd.args(["--authfile", authfile.as_str()]); + } + + println!("Copying {source_ref} to bootc storage..."); + let mut cmd = AsyncCommand::from(cmd); + cmd.run().await.context("Failed to copy image")?; + temp_runroot.close()?; + println!("Successfully copied {image_name} to bootc storage"); + Ok(()) + } + fn subpath() -> Utf8PathBuf { Utf8Path::new(crate::store::BOOTC_ROOT).join(SUBPATH) }