Skip to content

Commit

Permalink
Initialize a containers-storage: owned by bootc
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 26, 2024
1 parent 9db620a commit ffdfc8d
Show file tree
Hide file tree
Showing 8 changed files with 135 additions and 4 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
4 changes: 3 additions & 1 deletion lib/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -430,10 +430,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
85 changes: 85 additions & 0 deletions lib/src/imgstorage.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
//! # bootc-managed container storage
//!
//! The default storage for this project uses ostree, canonically storing all of its state in
//! `/sysroot/ostree`.
//!
//! This containers-storage: which canonically lives in `/sysroot/ostree/bootc`.
use std::sync::Arc;

use anyhow::{Context, Result};
use camino::Utf8Path;
use cap_std_ext::cap_std::fs::Dir;
use cap_std_ext::cmdext::CapStdExtCommandExt;
use cap_std_ext::dirext::CapStdExtDirExt;
use fn_error_context::context;
use std::os::fd::OwnedFd;

use crate::task::Task;

/// The path to the storage, relative to the physical system root.
pub(crate) const SUBPATH: &str = "ostree/bootc/storage";
/// The path to the "runroot" with transient runtime state; this is
/// relative to the /run directory
const RUNROOT: &str = "bootc/storage";
pub(crate) struct Storage {
root: Dir,
#[allow(dead_code)]
run: Dir,
}

impl Storage {
fn podman_task_in(sysroot: OwnedFd, run: OwnedFd) -> Result<crate::task::Task> {
let mut t = Task::new_quiet("podman");
// podman expects absolute paths for these, so use /proc/self/fd
{
let sysroot_fd: Arc<OwnedFd> = Arc::new(sysroot);
t.cmd.take_fd_n(sysroot_fd, 3);
}
{
let run_fd: Arc<OwnedFd> = Arc::new(run);
t.cmd.take_fd_n(run_fd, 4);
}
t = t.args(["--root=/proc/self/fd/3", "--runroot=/proc/self/fd/4"]);
Ok(t)
}

#[allow(dead_code)]
fn podman_task(&self) -> Result<crate::task::Task> {
let sysroot = self.root.try_clone()?.into_std_file().into();
let run = self.run.try_clone()?.into_std_file().into();
Self::podman_task_in(sysroot, run)
}

#[context("Creating imgstorage")]
pub(crate) fn create(sysroot: &Dir, run: &Dir) -> Result<Self> {
let subpath = Utf8Path::new(SUBPATH);
// SAFETY: We know there's a parent
let parent = subpath.parent().unwrap();
if !sysroot.try_exists(subpath)? {
let tmp = format!("{SUBPATH}.tmp");
sysroot.remove_all_optional(&tmp)?;
sysroot.create_dir_all(parent)?;
sysroot.create_dir_all(&tmp).context("Creating tmpdir")?;
// There's no explicit API to initialize a containers-storage:
// root, simply passing a path will attempt to auto-create it.
// We run "podman images" in the new root.
Self::podman_task_in(sysroot.open_dir(&tmp)?.into(), run.try_clone()?.into())?
.arg("images")
.run()?;
sysroot
.rename(&tmp, sysroot, subpath)
.context("Renaming tmpdir")?;
}
Self::open(sysroot, run)
}

#[context("Opening imgstorage")]
pub(crate) fn open(sysroot: &Dir, run: &Dir) -> Result<Self> {
let root = sysroot.open_dir(SUBPATH).context(SUBPATH)?;
// Always auto-create this if missing
run.create_dir_all(RUNROOT)?;
let run = run.open_dir(RUNROOT).context(RUNROOT)?;
Ok(Self { root, run })
}
}
15 changes: 14 additions & 1 deletion lib/src/install.rs
Original file line number Diff line number Diff line change
Expand Up @@ -597,6 +597,19 @@ async fn initialize_ostree_root(state: &State, root_setup: &RootSetup) -> Result
.cwd(rootfs_dir)?
.run()?;

let sysroot = ostree::Sysroot::new(Some(&gio::File::for_path(rootfs)));
sysroot.load(cancellable)?;
let sysroot_dir = Dir::reopen_dir(&crate::utils::sysroot_fd(&sysroot))?;

state.tempdir.create_dir("temp-run")?;
let temp_run = state.tempdir.open_dir("temp-run")?;
sysroot_dir
.create_dir_all(Utf8Path::new(crate::imgstorage::SUBPATH).parent().unwrap())
.context("creating bootc dir")?;
let imgstore = crate::imgstorage::Storage::create(&sysroot_dir, &temp_run)?;
// And drop it again - we'll reopen it after this
drop(imgstore);

// Bootstrap the initial labeling of the /ostree directory as usr_t
if let Some(policy) = sepolicy {
let ostree_dir = rootfs_dir.open_dir("ostree")?;
Expand All @@ -612,7 +625,7 @@ async fn initialize_ostree_root(state: &State, root_setup: &RootSetup) -> Result
let sysroot = ostree::Sysroot::new(Some(&gio::File::for_path(rootfs)));
sysroot.load(cancellable)?;
let sysroot = SysrootLock::new_from_sysroot(&sysroot).await?;
Storage::new(sysroot)
Storage::new(sysroot, &temp_run)
}

#[context("Creating ostree deployment")]
Expand Down
1 change: 1 addition & 0 deletions lib/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,3 +44,4 @@ pub mod spec;

#[cfg(feature = "docgen")]
mod docgen;
mod imgstorage;
14 changes: 12 additions & 2 deletions lib/src/store/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use std::env;
use std::ops::Deref;

use anyhow::Result;
use cap_std_ext::cap_std::fs::Dir;
use clap::ValueEnum;

use ostree_ext::container::OstreeImageReference;
Expand All @@ -15,6 +16,8 @@ mod ostree_container;

pub(crate) struct Storage {
pub sysroot: SysrootLock,
#[allow(dead_code)]
pub imgstore: crate::imgstorage::Storage,
pub store: Box<dyn ContainerImageStoreImpl>,
}

Expand Down Expand Up @@ -48,7 +51,7 @@ impl Deref for Storage {
}

impl Storage {
pub fn new(sysroot: SysrootLock) -> Result<Self> {
pub fn new(sysroot: SysrootLock, run: &Dir) -> Result<Self> {
let store = match env::var("BOOTC_STORAGE") {
Ok(val) => crate::spec::Store::from_str(&val, true).unwrap_or_else(|_| {
let default = crate::spec::Store::default();
Expand All @@ -58,9 +61,16 @@ impl Storage {
Err(_) => crate::spec::Store::default(),
};

let sysroot_dir = Dir::reopen_dir(&crate::utils::sysroot_fd(&sysroot))?;
let imgstore = crate::imgstorage::Storage::open(&sysroot_dir, run)?;

let store = load(store);

Ok(Self { sysroot, store })
Ok(Self {
sysroot,
store,
imgstore,
})
}
}

Expand Down
11 changes: 11 additions & 0 deletions tests-integration/src/install.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use std::path::Path;
use std::{os::fd::AsRawFd, path::PathBuf};

use anyhow::Result;
use camino::Utf8Path;
use cap_std_ext::cap_std;
use cap_std_ext::cap_std::fs::Dir;
use fn_error_context::context;
Expand Down Expand Up @@ -53,6 +54,12 @@ fn find_deployment_root() -> Result<Dir> {
anyhow::bail!("Failed to find deployment root")
}

// Hook relatively cheap post-install tests here
fn generic_post_install_verification() -> Result<()> {
assert!(Utf8Path::new("/ostree/bootc/storage/overlay").try_exists()?);
Ok(())
}

#[context("Install tests")]
pub(crate) fn run_alongside(image: &str, mut testargs: libtest_mimic::Arguments) -> Result<()> {
// Force all of these tests to be serial because they mutate global state
Expand Down Expand Up @@ -88,6 +95,8 @@ pub(crate) fn run_alongside(image: &str, mut testargs: libtest_mimic::Arguments)
std::fs::write(&tmp_keys, b"ssh-ed25519 ABC0123 [email protected]")?;
cmd!(sh, "sudo {BASE_ARGS...} {target_args...} -v {tmp_keys}:/test_authorized_keys {image} bootc install to-filesystem {generic_inst_args...} --acknowledge-destructive --karg=foo=bar --replace=alongside --root-ssh-authorized-keys=/test_authorized_keys /target").run()?;

generic_post_install_verification()?;

// Test kargs injected via CLI
cmd!(
sh,
Expand Down Expand Up @@ -120,6 +129,7 @@ pub(crate) fn run_alongside(image: &str, mut testargs: libtest_mimic::Arguments)
let sh = &xshell::Shell::new()?;
reset_root(sh)?;
cmd!(sh, "sudo {BASE_ARGS...} {target_args...} {image} bootc install to-existing-root --acknowledge-destructive {generic_inst_args...}").run()?;
generic_post_install_verification()?;
let root = &Dir::open_ambient_dir("/ostree", cap_std::ambient_authority()).unwrap();
let mut path = PathBuf::from(".");
crate::selinux::verify_selinux_recurse(root, &mut path, false)?;
Expand All @@ -131,6 +141,7 @@ pub(crate) fn run_alongside(image: &str, mut testargs: libtest_mimic::Arguments)
let empty = sh.create_temp_dir()?;
let empty = empty.path().to_str().unwrap();
cmd!(sh, "sudo {BASE_ARGS...} {target_args...} -v {empty}:/usr/lib/bootc/install {image} bootc install to-existing-root {generic_inst_args...}").run()?;
generic_post_install_verification()?;
Ok(())
}),
];
Expand Down
8 changes: 8 additions & 0 deletions tests/booted/010-test-bootc-container-store.nu
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
use std assert
use tap.nu

tap begin "verify bootc-owned container storage"

# This should currently be empty by default...
podman --storage-opt=additionalimagestore=/usr/lib/bootc/storage images
tap ok

0 comments on commit ffdfc8d

Please sign in to comment.