Skip to content

Commit

Permalink
cli: Add a new bootc image subcommand
Browse files Browse the repository at this point in the history
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 <[email protected]>
  • Loading branch information
cgwalters committed Jun 20, 2024
1 parent b10a1fe commit b6a015b
Show file tree
Hide file tree
Showing 5 changed files with 112 additions and 3 deletions.
3 changes: 1 addition & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion lib/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
Expand Down
43 changes: 43 additions & 0 deletions lib/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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<String>,

#[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<String>,
},
}

/// Hidden, internal only options
#[derive(Debug, clap::Subcommand, PartialEq, Eq)]
pub(crate) enum InternalsOpts {
Expand Down Expand Up @@ -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)]
Expand Down Expand Up @@ -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,
Expand Down
66 changes: 66 additions & 0 deletions lib/src/image.rs
Original file line number Diff line number Diff line change
@@ -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(())
}
1 change: 1 addition & 0 deletions lib/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down

0 comments on commit b6a015b

Please sign in to comment.