Skip to content

Commit a947c9d

Browse files
authored
Merge pull request #737 from cgwalters/bib-bound-more
install: Work around bootc-image-builder using /run/osbuild/containers
2 parents cf6c028 + aaf0d7e commit a947c9d

File tree

3 files changed

+95
-31
lines changed

3 files changed

+95
-31
lines changed

lib/src/install.rs

Lines changed: 2 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
// and filesystem setup.
99
pub(crate) mod baseline;
1010
pub(crate) mod config;
11+
mod osbuild;
1112
pub(crate) mod osconfig;
1213

1314
use std::io::Write;
@@ -997,36 +998,6 @@ fn ensure_var() -> Result<()> {
997998
Ok(())
998999
}
9991000

1000-
/// Unfortunately today podman requires that /etc be writable for
1001-
/// `/etc/containers/networks`. Detect the situation where it's not
1002-
/// (the main usual cause will be how bootc-image-builder runs us
1003-
/// via a custom bwrap container today) and work around it by
1004-
/// mounting a writable transient overlayfs.
1005-
#[context("Ensuring writable /etc")]
1006-
fn ensure_writable_etc_containers(tempdir: &Dir) -> Result<()> {
1007-
let etc_containers = Utf8Path::new("/etc/containers");
1008-
// If there's no /etc/containers, nothing to do
1009-
if !etc_containers.try_exists()? {
1010-
return Ok(());
1011-
}
1012-
if rustix::fs::access(etc_containers.as_std_path(), rustix::fs::Access::WRITE_OK).is_ok() {
1013-
return Ok(());
1014-
}
1015-
// Create dirs for the overlayfs upper and work in the install-global tmpdir.
1016-
tempdir.create_dir_all("etc-ovl/upper")?;
1017-
tempdir.create_dir("etc-ovl/work")?;
1018-
let opts = format!("lowerdir={etc_containers},workdir=etc-ovl/work,upperdir=etc-ovl/upper");
1019-
let mut t = Task::new(
1020-
&format!("Mount transient overlayfs for {etc_containers}"),
1021-
"mount",
1022-
)
1023-
.args(["-t", "overlay", "overlay", "-o", opts.as_str()])
1024-
.arg(etc_containers);
1025-
t.cmd.cwd_dir(tempdir.try_clone()?);
1026-
t.run()?;
1027-
Ok(())
1028-
}
1029-
10301001
/// We want to have proper /tmp and /var/tmp without requiring the caller to set them up
10311002
/// in advance by manually specifying them via `podman run -v /tmp:/tmp` etc.
10321003
/// Unfortunately, it's quite complex right now to "gracefully" dynamically reconfigure
@@ -1214,7 +1185,7 @@ async fn prepare_install(
12141185
// creating multiple.
12151186
let tempdir = cap_std_ext::cap_tempfile::TempDir::new(cap_std::ambient_authority())?;
12161187
// And continue to init global state
1217-
ensure_writable_etc_containers(&tempdir)?;
1188+
osbuild::adjust_for_bootc_image_builder(&rootfs, &tempdir)?;
12181189

12191190
if !target_opts.skip_fetch_check {
12201191
verify_target_fetch(&tempdir, &target_imgref).await?;

lib/src/install/osbuild.rs

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
//! # Helper APIs for interacting with bootc-image-builder
2+
//!
3+
//! See <https://github.com/osbuild/bootc-image-builder>
4+
//!
5+
6+
use anyhow::Result;
7+
use camino::Utf8Path;
8+
use cap_std_ext::{cap_std::fs::Dir, cmdext::CapStdExtCommandExt};
9+
use fn_error_context::context;
10+
11+
use crate::task::Task;
12+
13+
/// Handle /etc/containers readonly mount.
14+
///
15+
/// Ufortunately today podman requires that /etc be writable for
16+
/// `/etc/containers/networks`. bib today creates this as a readonly mount:
17+
/// https://github.com/osbuild/osbuild/blob/4edbe227d41c767441b9bf4390398afc6dc8f901/osbuild/buildroot.py#L243
18+
///
19+
/// Work around that by adding a transient, writable overlayfs.
20+
fn adjust_etc_containers(tempdir: &Dir) -> Result<()> {
21+
let etc_containers = Utf8Path::new("/etc/containers");
22+
// If there's no /etc/containers, nothing to do
23+
if !etc_containers.try_exists()? {
24+
return Ok(());
25+
}
26+
if rustix::fs::access(etc_containers.as_std_path(), rustix::fs::Access::WRITE_OK).is_ok() {
27+
return Ok(());
28+
}
29+
// Create dirs for the overlayfs upper and work in the install-global tmpdir.
30+
tempdir.create_dir_all("etc-ovl/upper")?;
31+
tempdir.create_dir("etc-ovl/work")?;
32+
let opts = format!("lowerdir={etc_containers},workdir=etc-ovl/work,upperdir=etc-ovl/upper");
33+
let mut t = Task::new(
34+
&format!("Mount transient overlayfs for {etc_containers}"),
35+
"mount",
36+
)
37+
.args(["-t", "overlay", "overlay", "-o", opts.as_str()])
38+
.arg(etc_containers);
39+
t.cmd.cwd_dir(tempdir.try_clone()?);
40+
t.run()?;
41+
Ok(())
42+
}
43+
44+
/// osbuild mounts the host's /var/lib/containers at /run/osbuild/containers; mount
45+
/// it back to /var/lib/containers where the default container stack expects to find it.
46+
fn propagate_run_osbuild_containers(root: &Dir) -> Result<()> {
47+
let osbuild_run_containers = Utf8Path::new("run/osbuild/containers");
48+
// If we're not apparently running under osbuild, then we no-op.
49+
if !root.try_exists(osbuild_run_containers)? {
50+
return Ok(());
51+
}
52+
// If we do seem to have a valid container store though, use that
53+
if crate::podman::storage_exists_default(root)? {
54+
return Ok(());
55+
}
56+
let relative_storage = Utf8Path::new(crate::podman::CONTAINER_STORAGE.trim_start_matches('/'));
57+
root.create_dir_all(relative_storage)?;
58+
Task::new("Creating bind mount for run/osbuild/containers", "mount")
59+
.arg("--rbind")
60+
.args([osbuild_run_containers, relative_storage])
61+
.cwd(root)?
62+
.run()?;
63+
Ok(())
64+
}
65+
66+
/// bootc-image-builder today does a few things that we need to
67+
/// deal with.
68+
#[context("bootc-image-builder adjustments")]
69+
pub(crate) fn adjust_for_bootc_image_builder(root: &Dir, tempdir: &Dir) -> Result<()> {
70+
adjust_etc_containers(tempdir)?;
71+
propagate_run_osbuild_containers(root)?;
72+
Ok(())
73+
}

lib/src/podman.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
use anyhow::{anyhow, Result};
2+
use camino::Utf8Path;
3+
use cap_std_ext::cap_std::fs::Dir;
24
use serde::Deserialize;
35

46
use crate::install::run_in_host_mountns;
@@ -27,3 +29,21 @@ pub(crate) fn imageid_to_digest(imgid: &str) -> Result<String> {
2729
.ok_or_else(|| anyhow!("No images returned for inspect"))?;
2830
Ok(i.digest)
2931
}
32+
33+
/// Return true if there is apparently an active container store at the target path.
34+
pub(crate) fn storage_exists(root: &Dir, path: impl AsRef<Utf8Path>) -> Result<bool> {
35+
fn impl_storage_exists(root: &Dir, path: &Utf8Path) -> Result<bool> {
36+
let lock = "storage.lock";
37+
root.try_exists(path.join(lock)).map_err(Into::into)
38+
}
39+
impl_storage_exists(root, path.as_ref())
40+
}
41+
42+
/// Return true if there is apparently an active container store in the default path
43+
/// for the target root.
44+
///
45+
/// Note this does not attempt to parse the root filesystem's container storage configuration,
46+
/// this uses a hardcoded default path.
47+
pub(crate) fn storage_exists_default(root: &Dir) -> Result<bool> {
48+
storage_exists(root, CONTAINER_STORAGE.trim_start_matches('/'))
49+
}

0 commit comments

Comments
 (0)