Skip to content
Closed
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
12 changes: 12 additions & 0 deletions crates/lib/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down Expand Up @@ -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()?;
Expand Down
7 changes: 7 additions & 0 deletions crates/lib/src/image.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 <X>` but set up for our internal
/// image store (as distinct from /var/lib/containers default).
pub(crate) async fn imgcmd_entrypoint(
Expand Down
57 changes: 57 additions & 0 deletions crates/lib/src/podstorage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down