Skip to content

Commit e2f833f

Browse files
committed
install: Fetch bound images from host's /var/lib/containers
Instead of fetching images from the network at install time, require them to be present in the host's container storage (the same place we expect to find the bootc image). There are numerous advantages to this, such as the basic fact that this helps one avoid re-pulling images at each install. But more importantly it aligns with the needs of "hermetic builds" which want to pre-fetch content from the network into the target environment. For example when generating a disk image with bootc-image-builder or so, a build tool can consistently preload all the input images (including the bootc base) into the container store, and know that the install process to generate the disk will use those images. Signed-off-by: Colin Walters <[email protected]>
1 parent 60543cf commit e2f833f

File tree

2 files changed

+78
-19
lines changed

2 files changed

+78
-19
lines changed

lib/src/boundimage.rs

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ use camino::Utf8Path;
1111
use cap_std_ext::cap_std::fs::Dir;
1212
use cap_std_ext::dirext::CapStdExtDirExt;
1313
use fn_error_context::context;
14+
use ostree_ext::containers_image_proxy;
1415
use ostree_ext::ostree::Deployment;
1516
use ostree_ext::sysroot::SysrootLock;
1617

@@ -23,12 +24,18 @@ const BOUND_IMAGE_DIR: &str = "usr/lib/bootc/bound-images.d";
2324
///
2425
/// In the future this may be extended to include e.g. certificates or
2526
/// other pull options.
26-
#[derive(PartialEq, Eq)]
27+
#[derive(Debug, PartialEq, Eq)]
2728
pub(crate) struct BoundImage {
2829
image: String,
2930
auth_file: Option<String>,
3031
}
3132

33+
#[derive(Debug, PartialEq, Eq)]
34+
pub(crate) struct ResolvedBoundImage {
35+
pub(crate) image: String,
36+
pub(crate) digest: String,
37+
}
38+
3239
/// Given a deployment, pull all container images it references.
3340
pub(crate) fn pull_bound_images(sysroot: &SysrootLock, deployment: &Deployment) -> Result<()> {
3441
let deployment_root = &crate::utils::deployment_fd(sysroot, deployment)?;
@@ -86,6 +93,20 @@ pub(crate) fn query_bound_images(root: &Dir) -> Result<Vec<BoundImage>> {
8693
Ok(bound_images)
8794
}
8895

96+
impl ResolvedBoundImage {
97+
pub(crate) async fn from_image(src: &BoundImage) -> Result<Self> {
98+
let proxy = containers_image_proxy::ImageProxy::new().await?;
99+
let img = proxy
100+
.open_image(&format!("containers-storage:{}", src.image))
101+
.await?;
102+
let digest = proxy.fetch_manifest(&img).await?.0;
103+
Ok(Self {
104+
image: src.image.clone(),
105+
digest,
106+
})
107+
}
108+
}
109+
89110
fn parse_image_file(file_contents: &tini::Ini) -> Result<BoundImage> {
90111
let image: String = file_contents
91112
.get("Image", "Image")

lib/src/install.rs

Lines changed: 56 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ pub(crate) mod config;
1111
pub(crate) mod osconfig;
1212

1313
use std::io::Write;
14-
use std::os::fd::AsFd;
14+
use std::os::fd::{AsFd, OwnedFd};
1515
use std::os::unix::process::CommandExt;
1616
use std::path::Path;
1717
use std::process::Command;
@@ -26,6 +26,7 @@ use camino::Utf8PathBuf;
2626
use cap_std::fs::{Dir, MetadataExt};
2727
use cap_std_ext::cap_std;
2828
use cap_std_ext::cap_std::fs_utf8::DirEntry as DirEntryUtf8;
29+
use cap_std_ext::cmdext::CapStdExtCommandExt;
2930
use cap_std_ext::prelude::CapStdExtDirExt;
3031
use chrono::prelude::*;
3132
use clap::ValueEnum;
@@ -1271,6 +1272,7 @@ async fn install_with_sysroot(
12711272
rootfs: &RootSetup,
12721273
sysroot: &ostree::Sysroot,
12731274
boot_uuid: &str,
1275+
bound_images: &[crate::boundimage::ResolvedBoundImage],
12741276
) -> Result<()> {
12751277
let sysroot = SysrootLock::new_from_sysroot(&sysroot).await?;
12761278
// And actually set up the container in that root, returning a deployment and
@@ -1299,33 +1301,51 @@ async fn install_with_sysroot(
12991301
tracing::debug!("Installed bootloader");
13001302

13011303
tracing::debug!("Perfoming post-deployment operations");
1302-
let deployment_root = crate::utils::deployment_fd(&sysroot, &deployment)?;
1303-
let bound_images = if state.config_opts.skip_bound_images {
1304-
Vec::new()
1305-
} else {
1306-
crate::boundimage::query_bound_images(&deployment_root)?
1307-
};
13081304
if !bound_images.is_empty() {
1305+
// TODO: We shouldn't hardcode the overlay driver for source or
1306+
// target, but we currently need to in order to reference the location.
1307+
// For this one, containers-storage: is actually the *host*'s /var/lib/containers
1308+
// which we are accessing directly.
1309+
let storage_src = "containers-storage:";
13091310
// TODO: We only do this dance to initialize `/var` at install time if
13101311
// there are bound images today; it minimizes side effects.
13111312
// However going forward we really do need to handle a separate /var partition...
13121313
// and to do that we may in the general case need to run the `var.mount`
13131314
// target from the new root.
1315+
// Probably the best fix is for us to switch bound images to use the bootc storage.
13141316
let varpath = format!("ostree/deploy/{stateroot}/var");
13151317
let var = rootfs
13161318
.rootfs_fd
13171319
.open_dir(&varpath)
13181320
.with_context(|| format!("Opening {varpath}"))?;
1319-
Task::new("Mounting deployment /var", "mount")
1320-
.args(["--bind", ".", "/var"])
1321-
.cwd(&var)?
1322-
.run()?;
1323-
// podman needs this
1324-
Task::new("Initializing /var/tmp", "systemd-tmpfiles")
1325-
.args(["--create", "--boot", "--prefix=/var/tmp"])
1326-
.verbose()
1327-
.run()?;
1328-
crate::boundimage::pull_images(&deployment_root, bound_images)?;
1321+
1322+
// The skopeo API expects absolute paths, so we make a temporary bind
1323+
let tmp_dest_var_abs = tempfile::tempdir()?;
1324+
let tmp_dest_var_abs: &Utf8Path = tmp_dest_var_abs.path().try_into()?;
1325+
let mut t = Task::new("Mounting deployment /var", "mount")
1326+
.args(["--bind", "/proc/self/fd/3"])
1327+
.arg(tmp_dest_var_abs);
1328+
t.cmd.take_fd_n(Arc::new(OwnedFd::from(var)), 3);
1329+
t.run()?;
1330+
1331+
// And an ephemeral place for the transient state
1332+
let tmp_runroot = tempfile::tempdir()?;
1333+
let tmp_runroot: &Utf8Path = tmp_runroot.path().try_into()?;
1334+
1335+
// The destination (target stateroot) + container storage dest
1336+
let storage_dest = &format!(
1337+
"containers-storage:[overlay@{tmp_dest_var_abs}/lib/containers/storage+{tmp_runroot}]"
1338+
);
1339+
1340+
// Now copy each bound image from the host's container storage into the target.
1341+
for image in bound_images {
1342+
let image = image.image.as_str();
1343+
Task::new(format!("Copying image to target: {}", image), "skopeo")
1344+
.arg("copy")
1345+
.arg(format!("{storage_src}{image}"))
1346+
.arg(format!("{storage_dest}{image}"))
1347+
.run()?;
1348+
}
13291349
}
13301350

13311351
Ok(())
@@ -1357,10 +1377,28 @@ async fn install_to_filesystem_impl(state: &State, rootfs: &mut RootSetup) -> Re
13571377
.ok_or_else(|| anyhow!("No uuid for boot/root"))?;
13581378
tracing::debug!("boot uuid={boot_uuid}");
13591379

1380+
let bound_images = if state.config_opts.skip_bound_images {
1381+
Vec::new()
1382+
} else {
1383+
crate::boundimage::query_bound_images(&state.container_root)?
1384+
};
1385+
tracing::debug!("bound images={bound_images:?}");
1386+
1387+
// Verify each bound image is present in the container storage
1388+
let bound_images = {
1389+
let mut r = Vec::with_capacity(bound_images.len());
1390+
for image in bound_images {
1391+
let resolved = crate::boundimage::ResolvedBoundImage::from_image(&image).await?;
1392+
tracing::debug!("Resolved {}: {}", resolved.image, resolved.digest);
1393+
r.push(resolved)
1394+
}
1395+
r
1396+
};
1397+
13601398
// Initialize the ostree sysroot (repo, stateroot, etc.)
13611399
{
13621400
let sysroot = initialize_ostree_root(state, rootfs).await?;
1363-
install_with_sysroot(state, rootfs, &sysroot, &boot_uuid).await?;
1401+
install_with_sysroot(state, rootfs, &sysroot, &boot_uuid, &bound_images).await?;
13641402
// We must drop the sysroot here in order to close any open file
13651403
// descriptors.
13661404
}

0 commit comments

Comments
 (0)