From b6a015ba97d909de10310bd29d937e6b4a14665b Mon Sep 17 00:00:00 2001 From: Colin Walters Date: Thu, 20 Jun 2024 14:29:22 -0400 Subject: [PATCH] cli: Add a new `bootc image` subcommand We have a basic `bootc image list` but more interesting is `bootc image push` which defaults to copying the booted image into the container storage. Signed-off-by: Colin Walters --- Cargo.lock | 3 +-- lib/Cargo.toml | 2 +- lib/src/cli.rs | 43 +++++++++++++++++++++++++++++++ lib/src/image.rs | 66 ++++++++++++++++++++++++++++++++++++++++++++++++ lib/src/lib.rs | 1 + 5 files changed, 112 insertions(+), 3 deletions(-) create mode 100644 lib/src/image.rs diff --git a/Cargo.lock b/Cargo.lock index 5f63bf886..40ac8a3af 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1348,8 +1348,7 @@ dependencies = [ [[package]] name = "ostree-ext" version = "0.14.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "945ba054bf4c562f8720668f4963af30ead7621e1b72ffc03638a70d31917124" +source = "git+https://github.com/cgwalters/ostree-rs-ext?branch=container-export#4374c05b25f2b16a228bc120a97c452c52f1a090" dependencies = [ "anyhow", "camino", diff --git a/lib/Cargo.toml b/lib/Cargo.toml index 662656e1c..a368788e6 100644 --- a/lib/Cargo.toml +++ b/lib/Cargo.toml @@ -16,7 +16,7 @@ anstream = "0.6.13" anstyle = "1.0.6" anyhow = "1.0.82" camino = { version = "1.1.6", features = ["serde1"] } -ostree-ext = { version = "0.14.0" } +ostree-ext = { version = "0.14.0", git = "https://github.com/cgwalters/ostree-rs-ext", branch = "container-export"} chrono = { version = "0.4.38", features = ["serde"] } clap = { version= "4.5.4", features = ["derive","cargo"] } clap_mangen = { version = "0.2.20", optional = true } diff --git a/lib/src/cli.rs b/lib/src/cli.rs index 1678d9e52..c00f01815 100644 --- a/lib/src/cli.rs +++ b/lib/src/cli.rs @@ -11,6 +11,7 @@ use fn_error_context::context; use ostree::gio; use ostree_container::store::PrepareResult; use ostree_ext::container as ostree_container; +use ostree_ext::container::Transport; use ostree_ext::keyfileext::KeyFileExt; use ostree_ext::ostree; use std::ffi::OsString; @@ -174,6 +175,31 @@ pub(crate) enum ContainerOpts { Lint, } +/// Subcommands which operate on images. +#[derive(Debug, clap::Subcommand, PartialEq, Eq)] +pub(crate) enum ImageOpts { + List, + /// Perform relatively inexpensive static analysis checks as part of a container + /// build. + /// + /// This is intended to be invoked via e.g. `RUN bootc container lint` as part + /// of a build process; it will error if any problems are detected. + Push { + /// The transport; e.g. oci, oci-archive, containers-storage. Defaults to `registry`. + #[clap(long, default_value = "registry")] + transport: String, + + #[clap(long)] + /// The source image; if not specified, the booted image will be used + source: Option, + + #[clap(long)] + /// The destination; if not specified, then the default is to push to `containers-storage:localhost/bootc`; + /// this will make the image accessible via e.g. `podman run localhost/bootc` and for builds. + target: Option, + }, +} + /// Hidden, internal only options #[derive(Debug, clap::Subcommand, PartialEq, Eq)] pub(crate) enum InternalsOpts { @@ -304,6 +330,12 @@ pub(crate) enum Opt { /// Operations which can be executed as part of a container build. #[clap(subcommand)] Container(ContainerOpts), + /// Operations on container images + /// + /// Stability: This interface is not declared stable and may change or be removed + /// at any point in the future. + #[clap(subcommand, hide = true)] + Image(ImageOpts), /// Execute the given command in the host mount namespace #[cfg(feature = "install")] #[clap(hide = true)] @@ -716,6 +748,17 @@ async fn run_from_opt(opt: Opt) -> Result<()> { Ok(()) } }, + Opt::Image(opts) => match opts { + ImageOpts::List => crate::image::list_entrypoint().await, + ImageOpts::Push { + transport, + source, + target, + } => { + let transport = Transport::try_from(transport.as_str())?; + crate::image::push_entrypoint(transport, source.as_deref(), target.as_deref()).await + } + }, #[cfg(feature = "install")] Opt::Install(opts) => match opts { InstallOpts::ToDisk(opts) => crate::install::install_to_disk(opts).await, diff --git a/lib/src/image.rs b/lib/src/image.rs new file mode 100644 index 000000000..aae7330b3 --- /dev/null +++ b/lib/src/image.rs @@ -0,0 +1,66 @@ +//! # Controlling bootc-managed images +//! +//! APIs for operating on container images in the bootc storage. + +use anyhow::{Context, Result}; +use fn_error_context::context; +use ostree_ext::container::{ImageReference, Transport}; + +/// The name of the image we push to containers-storage if nothing is specified. +const IMAGE_DEFAULT: &str = "localhost/bootc"; + +#[context("Listing images")] +pub(crate) async fn list_entrypoint() -> Result<()> { + let sysroot = crate::cli::get_locked_sysroot().await?; + let repo = &sysroot.repo(); + + let images = ostree_ext::container::store::list_images(repo).context("Querying images")?; + + for image in images { + println!("{image}"); + } + Ok(()) +} + +#[context("Pushing image")] +pub(crate) async fn push_entrypoint( + transport: Transport, + source: Option<&str>, + target: Option<&str>, +) -> Result<()> { + let sysroot = crate::cli::get_locked_sysroot().await?; + + let repo = &sysroot.repo(); + + // If the target isn't specified, push to containers-storage + our default image + let target = if let Some(target) = target { + ImageReference { + transport, + name: target.to_owned(), + } + } else { + ImageReference { + transport: Transport::ContainerStorage, + name: IMAGE_DEFAULT.to_string(), + } + }; + + // If the source isn't specified, we use the booted image + let source = if let Some(source) = source { + ImageReference::try_from(source).context("Parsing source image")? + } else { + let status = crate::status::get_status_require_booted(&sysroot)?; + // SAFETY: We know it's booted + let booted = status.2.status.booted.unwrap(); + let booted_image = booted.image.unwrap().image; + ImageReference { + transport: Transport::try_from(booted_image.transport.as_str()).unwrap(), + name: booted_image.image, + } + }; + let r = + ostree_ext::container::store::export(repo, &source, &target, Default::default()).await?; + + println!("Pushed: {target} {r}"); + Ok(()) +} diff --git a/lib/src/lib.rs b/lib/src/lib.rs index f2f2c60d2..9f8d4ac51 100644 --- a/lib/src/lib.rs +++ b/lib/src/lib.rs @@ -20,6 +20,7 @@ pub mod cli; pub(crate) mod deploy; pub(crate) mod generator; +mod image; pub(crate) mod journal; pub(crate) mod kargs; mod lints;