Skip to content

Commit d2eddef

Browse files
committed
Initialize a containers-storage: owned by bootc, use for bound images
Initial work for: #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]>
1 parent c39b664 commit d2eddef

File tree

13 files changed

+432
-76
lines changed

13 files changed

+432
-76
lines changed

Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ install:
1111
install -D -m 0755 -t $(DESTDIR)$(prefix)/bin target/release/bootc
1212
install -d -m 0755 $(DESTDIR)$(prefix)/lib/bootc/bound-images.d
1313
install -d -m 0755 $(DESTDIR)$(prefix)/lib/bootc/kargs.d
14+
ln -s /sysroot/ostree/bootc/storage $(DESTDIR)$(prefix)/lib/bootc/storage
1415
install -d -m 0755 $(DESTDIR)$(prefix)/lib/systemd/system-generators/
1516
ln -f $(DESTDIR)$(prefix)/bin/bootc $(DESTDIR)$(prefix)/lib/systemd/system-generators/bootc-systemd-generator
1617
install -d $(DESTDIR)$(prefix)/lib/bootc/install

lib/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ liboverdrop = "0.1.0"
3030
libsystemd = "0.7"
3131
openssl = "^0.10.64"
3232
regex = "1.10.4"
33-
rustix = { "version" = "0.38.34", features = ["thread", "fs", "system", "process"] }
33+
rustix = { "version" = "0.38.34", features = ["thread", "fs", "system", "process", "mount"] }
3434
schemars = { version = "0.8.17", features = ["chrono"] }
3535
serde = { workspace = true, features = ["derive"] }
3636
serde_ignored = "0.1.10"

lib/src/boundimage.rs

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,16 @@
55
//! pre-pulled (and in the future, pinned) before a new image root
66
//! is considered ready.
77
8-
use crate::task::Task;
98
use anyhow::{Context, Result};
109
use camino::Utf8Path;
1110
use cap_std_ext::cap_std::fs::Dir;
1211
use cap_std_ext::dirext::CapStdExtDirExt;
1312
use fn_error_context::context;
1413
use ostree_ext::containers_image_proxy;
1514
use ostree_ext::ostree::Deployment;
16-
use ostree_ext::sysroot::SysrootLock;
15+
16+
use crate::imgstorage::PullMode;
17+
use crate::store::Storage;
1718

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

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

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

136-
#[context("pull bound images")]
137-
pub(crate) fn pull_images(_deployment_root: &Dir, bound_images: Vec<BoundImage>) -> Result<()> {
137+
#[context("Pulling bound images")]
138+
pub(crate) async fn pull_images(sysroot: &Storage, bound_images: Vec<BoundImage>) -> Result<()> {
138139
tracing::debug!("Pulling bound images: {}", bound_images.len());
139140
//TODO: do this in parallel
140141
for bound_image in bound_images {
141-
let mut task = Task::new("Pulling bound image", "/usr/bin/podman")
142-
.arg("pull")
143-
.arg(&bound_image.image);
144-
if let Some(auth_file) = &bound_image.auth_file {
145-
task = task.arg("--authfile").arg(auth_file);
146-
}
147-
task.run()?;
142+
let image = &bound_image.image;
143+
let desc = format!("Updating bound image: {image}");
144+
crate::utils::async_task_with_spinner(&desc, async move {
145+
sysroot
146+
.imgstore
147+
.pull(&bound_image.image, PullMode::IfNotExists)
148+
.await
149+
})
150+
.await?;
148151
}
149152

150153
Ok(())

lib/src/cli.rs

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,31 @@ pub(crate) enum ContainerOpts {
199199
Lint,
200200
}
201201

202+
/// Subcommands which operate on images.
203+
#[derive(Debug, clap::Subcommand, PartialEq, Eq)]
204+
pub(crate) enum ImageCmdOpts {
205+
/// Wrapper for `podman image list` in bootc storage.
206+
List {
207+
#[clap(allow_hyphen_values = true)]
208+
args: Vec<OsString>,
209+
},
210+
/// Wrapper for `podman image build` in bootc storage.
211+
Build {
212+
#[clap(allow_hyphen_values = true)]
213+
args: Vec<OsString>,
214+
},
215+
/// Wrapper for `podman image pull` in bootc storage.
216+
Pull {
217+
#[clap(allow_hyphen_values = true)]
218+
args: Vec<OsString>,
219+
},
220+
/// Wrapper for `podman image push` in bootc storage.
221+
Push {
222+
#[clap(allow_hyphen_values = true)]
223+
args: Vec<OsString>,
224+
},
225+
}
226+
202227
/// Subcommands which operate on images.
203228
#[derive(Debug, clap::Subcommand, PartialEq, Eq)]
204229
pub(crate) enum ImageOpts {
@@ -232,6 +257,16 @@ pub(crate) enum ImageOpts {
232257
/// this will make the image accessible via e.g. `podman run localhost/bootc` and for builds.
233258
target: Option<String>,
234259
},
260+
/// Copy a container image from the default `containers-storage:` to the bootc-owned container storage.
261+
PullFromDefaultStorage {
262+
/// The image to pull
263+
image: String,
264+
},
265+
/// List fetched images stored in the bootc storage.
266+
///
267+
/// Note that these are distinct from images stored via e.g. `podman`.
268+
#[clap(subcommand)]
269+
Cmd(ImageCmdOpts),
235270
}
236271

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

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

439476
#[context("Querying root privilege")]
@@ -798,6 +835,27 @@ async fn run_from_opt(opt: Opt) -> Result<()> {
798835
ImageOpts::CopyToStorage { source, target } => {
799836
crate::image::push_entrypoint(source.as_deref(), target.as_deref()).await
800837
}
838+
ImageOpts::PullFromDefaultStorage { image } => {
839+
let sysroot = get_storage().await?;
840+
sysroot.imgstore.pull_from_host_storage(&image).await
841+
}
842+
ImageOpts::Cmd(opt) => {
843+
let sysroot = get_storage().await?;
844+
match opt {
845+
ImageCmdOpts::List { args } => {
846+
crate::image::imgcmd_entrypoint(&sysroot.imgstore, "list", &args).await
847+
}
848+
ImageCmdOpts::Build { args } => {
849+
crate::image::imgcmd_entrypoint(&sysroot.imgstore, "build", &args).await
850+
}
851+
ImageCmdOpts::Pull { args } => {
852+
crate::image::imgcmd_entrypoint(&sysroot.imgstore, "pull", &args).await
853+
}
854+
ImageCmdOpts::Push { args } => {
855+
crate::image::imgcmd_entrypoint(&sysroot.imgstore, "push", &args).await
856+
}
857+
}
858+
}
801859
},
802860
#[cfg(feature = "install")]
803861
Opt::Install(opts) => match opts {

lib/src/deploy.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -399,7 +399,7 @@ pub(crate) async fn stage(
399399
)
400400
.await?;
401401

402-
crate::boundimage::pull_bound_images(sysroot, &deployment)?;
402+
crate::boundimage::pull_bound_images(sysroot, &deployment).await?;
403403

404404
crate::deploy::cleanup(sysroot).await?;
405405
println!("Queued for next boot: {:#}", spec.image);

lib/src/image.rs

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,19 +6,29 @@ use anyhow::{Context, Result};
66
use fn_error_context::context;
77
use ostree_ext::container::{ImageReference, Transport};
88

9+
use crate::{imgstorage::Storage, utils::CommandRunExt};
10+
911
/// The name of the image we push to containers-storage if nothing is specified.
1012
const IMAGE_DEFAULT: &str = "localhost/bootc";
1113

1214
#[context("Listing images")]
1315
pub(crate) async fn list_entrypoint() -> Result<()> {
14-
let sysroot = crate::cli::get_locked_sysroot().await?;
16+
let sysroot = crate::cli::get_storage().await?;
1517
let repo = &sysroot.repo();
1618

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

21+
println!("# Host images");
1922
for image in images {
2023
println!("{image}");
2124
}
25+
println!("");
26+
27+
println!("# Logically bound images");
28+
let mut listcmd = sysroot.imgstore.new_image_cmd()?;
29+
listcmd.arg("list");
30+
listcmd.run()?;
31+
2232
Ok(())
2333
}
2434

@@ -64,3 +74,16 @@ pub(crate) async fn push_entrypoint(source: Option<&str>, target: Option<&str>)
6474
println!("Pushed: {target} {r}");
6575
Ok(())
6676
}
77+
78+
/// Thin wrapper for invoking `podman image <X>` but set up for our internal
79+
/// image store (as distinct from /var/lib/containers default).
80+
pub(crate) async fn imgcmd_entrypoint(
81+
storage: &Storage,
82+
arg: &str,
83+
args: &[std::ffi::OsString],
84+
) -> std::result::Result<(), anyhow::Error> {
85+
let mut cmd = storage.new_image_cmd()?;
86+
cmd.arg(arg);
87+
cmd.args(args);
88+
cmd.run()
89+
}

0 commit comments

Comments
 (0)