Skip to content

Commit

Permalink
Merge pull request #737 from cgwalters/bib-bound-more
Browse files Browse the repository at this point in the history
install: Work around bootc-image-builder using /run/osbuild/containers
  • Loading branch information
cgwalters authored Jul 29, 2024
2 parents cf6c028 + aaf0d7e commit a947c9d
Show file tree
Hide file tree
Showing 3 changed files with 95 additions and 31 deletions.
33 changes: 2 additions & 31 deletions lib/src/install.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
// and filesystem setup.
pub(crate) mod baseline;
pub(crate) mod config;
mod osbuild;
pub(crate) mod osconfig;

use std::io::Write;
Expand Down Expand Up @@ -997,36 +998,6 @@ fn ensure_var() -> Result<()> {
Ok(())
}

/// Unfortunately today podman requires that /etc be writable for
/// `/etc/containers/networks`. Detect the situation where it's not
/// (the main usual cause will be how bootc-image-builder runs us
/// via a custom bwrap container today) and work around it by
/// mounting a writable transient overlayfs.
#[context("Ensuring writable /etc")]
fn ensure_writable_etc_containers(tempdir: &Dir) -> Result<()> {
let etc_containers = Utf8Path::new("/etc/containers");
// If there's no /etc/containers, nothing to do
if !etc_containers.try_exists()? {
return Ok(());
}
if rustix::fs::access(etc_containers.as_std_path(), rustix::fs::Access::WRITE_OK).is_ok() {
return Ok(());
}
// Create dirs for the overlayfs upper and work in the install-global tmpdir.
tempdir.create_dir_all("etc-ovl/upper")?;
tempdir.create_dir("etc-ovl/work")?;
let opts = format!("lowerdir={etc_containers},workdir=etc-ovl/work,upperdir=etc-ovl/upper");
let mut t = Task::new(
&format!("Mount transient overlayfs for {etc_containers}"),
"mount",
)
.args(["-t", "overlay", "overlay", "-o", opts.as_str()])
.arg(etc_containers);
t.cmd.cwd_dir(tempdir.try_clone()?);
t.run()?;
Ok(())
}

/// We want to have proper /tmp and /var/tmp without requiring the caller to set them up
/// in advance by manually specifying them via `podman run -v /tmp:/tmp` etc.
/// Unfortunately, it's quite complex right now to "gracefully" dynamically reconfigure
Expand Down Expand Up @@ -1214,7 +1185,7 @@ async fn prepare_install(
// creating multiple.
let tempdir = cap_std_ext::cap_tempfile::TempDir::new(cap_std::ambient_authority())?;
// And continue to init global state
ensure_writable_etc_containers(&tempdir)?;
osbuild::adjust_for_bootc_image_builder(&rootfs, &tempdir)?;

if !target_opts.skip_fetch_check {
verify_target_fetch(&tempdir, &target_imgref).await?;
Expand Down
73 changes: 73 additions & 0 deletions lib/src/install/osbuild.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
//! # Helper APIs for interacting with bootc-image-builder
//!
//! See <https://github.com/osbuild/bootc-image-builder>
//!

use anyhow::Result;
use camino::Utf8Path;
use cap_std_ext::{cap_std::fs::Dir, cmdext::CapStdExtCommandExt};
use fn_error_context::context;

use crate::task::Task;

/// Handle /etc/containers readonly mount.
///
/// Ufortunately today podman requires that /etc be writable for
/// `/etc/containers/networks`. bib today creates this as a readonly mount:
/// https://github.com/osbuild/osbuild/blob/4edbe227d41c767441b9bf4390398afc6dc8f901/osbuild/buildroot.py#L243
///
/// Work around that by adding a transient, writable overlayfs.
fn adjust_etc_containers(tempdir: &Dir) -> Result<()> {
let etc_containers = Utf8Path::new("/etc/containers");
// If there's no /etc/containers, nothing to do
if !etc_containers.try_exists()? {
return Ok(());
}
if rustix::fs::access(etc_containers.as_std_path(), rustix::fs::Access::WRITE_OK).is_ok() {
return Ok(());
}
// Create dirs for the overlayfs upper and work in the install-global tmpdir.
tempdir.create_dir_all("etc-ovl/upper")?;
tempdir.create_dir("etc-ovl/work")?;
let opts = format!("lowerdir={etc_containers},workdir=etc-ovl/work,upperdir=etc-ovl/upper");
let mut t = Task::new(
&format!("Mount transient overlayfs for {etc_containers}"),
"mount",
)
.args(["-t", "overlay", "overlay", "-o", opts.as_str()])
.arg(etc_containers);
t.cmd.cwd_dir(tempdir.try_clone()?);
t.run()?;
Ok(())
}

/// osbuild mounts the host's /var/lib/containers at /run/osbuild/containers; mount
/// it back to /var/lib/containers where the default container stack expects to find it.
fn propagate_run_osbuild_containers(root: &Dir) -> Result<()> {
let osbuild_run_containers = Utf8Path::new("run/osbuild/containers");
// If we're not apparently running under osbuild, then we no-op.
if !root.try_exists(osbuild_run_containers)? {
return Ok(());
}
// If we do seem to have a valid container store though, use that
if crate::podman::storage_exists_default(root)? {
return Ok(());
}
let relative_storage = Utf8Path::new(crate::podman::CONTAINER_STORAGE.trim_start_matches('/'));
root.create_dir_all(relative_storage)?;
Task::new("Creating bind mount for run/osbuild/containers", "mount")
.arg("--rbind")
.args([osbuild_run_containers, relative_storage])
.cwd(root)?
.run()?;
Ok(())
}

/// bootc-image-builder today does a few things that we need to
/// deal with.
#[context("bootc-image-builder adjustments")]
pub(crate) fn adjust_for_bootc_image_builder(root: &Dir, tempdir: &Dir) -> Result<()> {
adjust_etc_containers(tempdir)?;
propagate_run_osbuild_containers(root)?;
Ok(())
}
20 changes: 20 additions & 0 deletions lib/src/podman.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
use anyhow::{anyhow, Result};
use camino::Utf8Path;
use cap_std_ext::cap_std::fs::Dir;
use serde::Deserialize;

use crate::install::run_in_host_mountns;
Expand Down Expand Up @@ -27,3 +29,21 @@ pub(crate) fn imageid_to_digest(imgid: &str) -> Result<String> {
.ok_or_else(|| anyhow!("No images returned for inspect"))?;
Ok(i.digest)
}

/// Return true if there is apparently an active container store at the target path.
pub(crate) fn storage_exists(root: &Dir, path: impl AsRef<Utf8Path>) -> Result<bool> {
fn impl_storage_exists(root: &Dir, path: &Utf8Path) -> Result<bool> {
let lock = "storage.lock";
root.try_exists(path.join(lock)).map_err(Into::into)
}
impl_storage_exists(root, path.as_ref())
}

/// Return true if there is apparently an active container store in the default path
/// for the target root.
///
/// Note this does not attempt to parse the root filesystem's container storage configuration,
/// this uses a hardcoded default path.
pub(crate) fn storage_exists_default(root: &Dir) -> Result<bool> {
storage_exists(root, CONTAINER_STORAGE.trim_start_matches('/'))
}

0 comments on commit a947c9d

Please sign in to comment.