Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

install: Work around bootc-image-builder using /run/osbuild/containers #737

Merged
merged 2 commits into from
Jul 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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('/'))
}