Skip to content

Commit

Permalink
Initialize a containers-storage: owned by bootc, use for bound images
Browse files Browse the repository at this point in the history
Initial work for: containers#721

- Initialize a containers-storage: instance at install time
  (that defaults to empty)
- "Open" it (but do nothing with it) as part of the core CLI
  operations

Further APIs and work will build on top of this.

Signed-off-by: Colin Walters <[email protected]>
  • Loading branch information
cgwalters committed Jul 29, 2024
1 parent 218ed0e commit e420274
Show file tree
Hide file tree
Showing 11 changed files with 389 additions and 67 deletions.
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ install:
install -D -m 0755 -t $(DESTDIR)$(prefix)/bin target/release/bootc
install -d -m 0755 $(DESTDIR)$(prefix)/lib/bootc/bound-images.d
install -d -m 0755 $(DESTDIR)$(prefix)/lib/bootc/kargs.d
ln -s /sysroot/ostree/bootc/storage $(DESTDIR)$(prefix)/lib/bootc/storage
install -d -m 0755 $(DESTDIR)$(prefix)/lib/systemd/system-generators/
ln -f $(DESTDIR)$(prefix)/bin/bootc $(DESTDIR)$(prefix)/lib/systemd/system-generators/bootc-systemd-generator
install -d $(DESTDIR)$(prefix)/lib/bootc/install
Expand Down
29 changes: 16 additions & 13 deletions lib/src/boundimage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,16 @@
//! pre-pulled (and in the future, pinned) before a new image root
//! is considered ready.
use crate::task::Task;
use anyhow::{Context, Result};
use camino::Utf8Path;
use cap_std_ext::cap_std::fs::Dir;
use cap_std_ext::dirext::CapStdExtDirExt;
use fn_error_context::context;
use ostree_ext::containers_image_proxy;
use ostree_ext::ostree::Deployment;
use ostree_ext::sysroot::SysrootLock;

use crate::imgstorage::PullMode;
use crate::store::Storage;

/// The path in a root for bound images; this directory should only contain
/// symbolic links to `.container` or `.image` files.
Expand All @@ -37,10 +38,10 @@ pub(crate) struct ResolvedBoundImage {
}

/// Given a deployment, pull all container images it references.
pub(crate) fn pull_bound_images(sysroot: &SysrootLock, deployment: &Deployment) -> Result<()> {
pub(crate) async fn pull_bound_images(sysroot: &Storage, deployment: &Deployment) -> Result<()> {
let deployment_root = &crate::utils::deployment_fd(sysroot, deployment)?;
let bound_images = query_bound_images(deployment_root)?;
pull_images(deployment_root, bound_images)
pull_images(sysroot, bound_images).await
}

#[context("Querying bound images")]
Expand Down Expand Up @@ -133,18 +134,20 @@ fn parse_container_file(file_contents: &tini::Ini) -> Result<BoundImage> {
Ok(bound_image)
}

#[context("pull bound images")]
pub(crate) fn pull_images(_deployment_root: &Dir, bound_images: Vec<BoundImage>) -> Result<()> {
#[context("Pulling bound images")]
pub(crate) async fn pull_images(sysroot: &Storage, bound_images: Vec<BoundImage>) -> Result<()> {
tracing::debug!("Pulling bound images: {}", bound_images.len());
//TODO: do this in parallel
for bound_image in bound_images {
let mut task = Task::new("Pulling bound image", "/usr/bin/podman")
.arg("pull")
.arg(&bound_image.image);
if let Some(auth_file) = &bound_image.auth_file {
task = task.arg("--authfile").arg(auth_file);
}
task.run()?;
let image = &bound_image.image;
let desc = format!("Updating bound image: {image}");
crate::utils::async_task_with_spinner(&desc, async move {
sysroot
.imgstore
.pull(&bound_image.image, PullMode::IfNotExists)
.await
})
.await?;
}

Ok(())
Expand Down
60 changes: 59 additions & 1 deletion lib/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,31 @@ pub(crate) enum ContainerOpts {
Lint,
}

/// Subcommands which operate on images.
#[derive(Debug, clap::Subcommand, PartialEq, Eq)]
pub(crate) enum ImageCmdOpts {
/// Wrapper for `podman image list` in bootc storage.
List {
#[clap(allow_hyphen_values = true)]
args: Vec<OsString>,
},
/// Wrapper for `podman image build` in bootc storage.
Build {
#[clap(allow_hyphen_values = true)]
args: Vec<OsString>,
},
/// Wrapper for `podman image pull` in bootc storage.
Pull {
#[clap(allow_hyphen_values = true)]
args: Vec<OsString>,
},
/// Wrapper for `podman image push` in bootc storage.
Push {
#[clap(allow_hyphen_values = true)]
args: Vec<OsString>,
},
}

/// Subcommands which operate on images.
#[derive(Debug, clap::Subcommand, PartialEq, Eq)]
pub(crate) enum ImageOpts {
Expand Down Expand Up @@ -232,6 +257,16 @@ pub(crate) enum ImageOpts {
/// this will make the image accessible via e.g. `podman run localhost/bootc` and for builds.
target: Option<String>,
},
/// Copy a container image from the default `containers-storage:` to the bootc-owned container storage.
PullFromDefaultStorage {
/// The image to pull
image: String,
},
/// List fetched images stored in the bootc storage.
///
/// Note that these are distinct from images stored via e.g. `podman`.
#[clap(subcommand)]
Cmd(ImageCmdOpts),
}

/// Hidden, internal only options
Expand Down Expand Up @@ -430,10 +465,12 @@ pub(crate) async fn get_locked_sysroot() -> Result<ostree_ext::sysroot::SysrootL
Ok(sysroot)
}

/// Load global storage state, expecting that we're booted into a bootc system.
#[context("Initializing storage")]
pub(crate) async fn get_storage() -> Result<crate::store::Storage> {
let global_run = Dir::open_ambient_dir("/run", cap_std::ambient_authority())?;
let sysroot = get_locked_sysroot().await?;
crate::store::Storage::new(sysroot)
crate::store::Storage::new(sysroot, &global_run)
}

#[context("Querying root privilege")]
Expand Down Expand Up @@ -798,6 +835,27 @@ async fn run_from_opt(opt: Opt) -> Result<()> {
ImageOpts::CopyToStorage { source, target } => {
crate::image::push_entrypoint(source.as_deref(), target.as_deref()).await
}
ImageOpts::PullFromDefaultStorage { image } => {
let sysroot = get_storage().await?;
sysroot.imgstore.pull_from_host_storage(&image).await
}
ImageOpts::Cmd(opt) => {
let sysroot = get_storage().await?;
match opt {
ImageCmdOpts::List { args } => {
crate::image::imgcmd_entrypoint(&sysroot.imgstore, "list", &args).await
}
ImageCmdOpts::Build { args } => {
crate::image::imgcmd_entrypoint(&sysroot.imgstore, "build", &args).await
}
ImageCmdOpts::Pull { args } => {
crate::image::imgcmd_entrypoint(&sysroot.imgstore, "pull", &args).await
}
ImageCmdOpts::Push { args } => {
crate::image::imgcmd_entrypoint(&sysroot.imgstore, "push", &args).await
}
}
}
},
#[cfg(feature = "install")]
Opt::Install(opts) => match opts {
Expand Down
2 changes: 1 addition & 1 deletion lib/src/deploy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -399,7 +399,7 @@ pub(crate) async fn stage(
)
.await?;

crate::boundimage::pull_bound_images(sysroot, &deployment)?;
crate::boundimage::pull_bound_images(sysroot, &deployment).await?;

crate::deploy::cleanup(sysroot).await?;
println!("Queued for next boot: {:#}", spec.image);
Expand Down
25 changes: 24 additions & 1 deletion lib/src/image.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,29 @@ use anyhow::{Context, Result};
use fn_error_context::context;
use ostree_ext::container::{ImageReference, Transport};

use crate::{imgstorage::Storage, utils::CommandRunExt};

/// 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 sysroot = crate::cli::get_storage().await?;
let repo = &sysroot.repo();

let images = ostree_ext::container::store::list_images(repo).context("Querying images")?;

println!("# Host images");
for image in images {
println!("{image}");
}
println!("");

println!("# Logically bound images");
let mut listcmd = sysroot.imgstore.new_image_cmd()?;
listcmd.arg("list");
listcmd.run()?;

Ok(())
}

Expand Down Expand Up @@ -64,3 +74,16 @@ pub(crate) async fn push_entrypoint(source: Option<&str>, target: Option<&str>)
println!("Pushed: {target} {r}");
Ok(())
}

/// 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(
storage: &Storage,
arg: &str,
args: &[std::ffi::OsString],
) -> std::result::Result<(), anyhow::Error> {
let mut cmd = storage.new_image_cmd()?;
cmd.arg(arg);
cmd.args(args);
cmd.run()
}
Loading

0 comments on commit e420274

Please sign in to comment.