Skip to content

Commit fbaa5b0

Browse files
committed
install: Add support for pulling LBIs during install
Partially solves #846 This adds a new `--bound-images` option to `bootc install` which will allow the user to choose how they want to handle the retrieval of LBIs into the target's container storage. The existing behavior, which will stay the default, is `--bound-images stored` which will resolve the LBIs and verify they exist in the source's container storage before copying them into the target's container storage. The new behavior is `--bound-images pull` which will skip the resolution step and directly pull the LBIs into the target's container storage. The older `--skip-bound-images` option (previously hidden) is now removed and replaced with the new `--bound-images skip` option. Signed-off-by: Omer Tuchfeld <[email protected]>
1 parent b469332 commit fbaa5b0

File tree

2 files changed

+88
-30
lines changed

2 files changed

+88
-30
lines changed

lib/src/boundimage.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,7 @@ pub(crate) fn query_bound_images(root: &Dir) -> Result<Vec<BoundImage>> {
108108

109109
#[cfg(feature = "install")]
110110
impl ResolvedBoundImage {
111+
#[context("resolving bound image {}", src.image)]
111112
pub(crate) async fn from_image(src: &BoundImage) -> Result<Self> {
112113
let proxy = containers_image_proxy::ImageProxy::new().await?;
113114
let img = proxy
@@ -148,7 +149,10 @@ fn parse_container_file(file_contents: &tini::Ini) -> Result<BoundImage> {
148149
}
149150

150151
#[context("Pulling bound images")]
151-
pub(crate) async fn pull_images(sysroot: &Storage, bound_images: Vec<BoundImage>) -> Result<()> {
152+
pub(crate) async fn pull_images(
153+
sysroot: &Storage,
154+
bound_images: Vec<crate::boundimage::BoundImage>,
155+
) -> Result<()> {
152156
tracing::debug!("Pulling bound images: {}", bound_images.len());
153157
// Yes, the usage of NonZeroUsize here is...maybe odd looking, but I find
154158
// it an elegant way to divide (empty vector, non empty vector) since

lib/src/install.rs

Lines changed: 83 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,8 @@ use std::str::FromStr;
2020
use std::sync::Arc;
2121
use std::time::Duration;
2222

23-
use anyhow::Ok;
2423
use anyhow::{anyhow, Context, Result};
24+
use anyhow::{bail, Ok};
2525
use bootc_utils::CommandRunExt;
2626
use camino::Utf8Path;
2727
use camino::Utf8PathBuf;
@@ -44,6 +44,7 @@ use rustix::fs::{FileTypeExt, MetadataExt as _};
4444
use serde::{Deserialize, Serialize};
4545

4646
use self::baseline::InstallBlockDeviceOpts;
47+
use crate::boundimage::{BoundImage, ResolvedBoundImage};
4748
use crate::containerenv::ContainerExecutionInfo;
4849
use crate::mount::Filesystem;
4950
use crate::spec::ImageReference;
@@ -130,6 +131,27 @@ pub(crate) struct InstallSourceOpts {
130131
pub(crate) source_imgref: Option<String>,
131132
}
132133

134+
#[derive(ValueEnum, Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
135+
#[serde(rename_all = "kebab-case")]
136+
pub(crate) enum BoundImagesOpt {
137+
/// Bound images must exist in the source's root container storage (default)
138+
#[default]
139+
Stored,
140+
#[clap(hide = true)]
141+
/// Do not resolve any "logically bound" images at install time.
142+
Skip,
143+
// TODO: Once we implement https://github.com/containers/bootc/issues/863 update this comment
144+
// to mention source's root container storage being used as lookaside cache
145+
/// Bound images will be pulled and stored directly in the target's bootc container storage
146+
Pull,
147+
}
148+
149+
impl std::fmt::Display for BoundImagesOpt {
150+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
151+
self.to_possible_value().unwrap().get_name().fmt(f)
152+
}
153+
}
154+
133155
#[derive(clap::Args, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
134156
pub(crate) struct InstallConfigOpts {
135157
/// Disable SELinux in the target (installed) system.
@@ -165,10 +187,10 @@ pub(crate) struct InstallConfigOpts {
165187
#[serde(default)]
166188
pub(crate) generic_image: bool,
167189

168-
/// Do not pull any "logically bound" images at install time.
169-
#[clap(long, hide = true)]
190+
/// How should logically bound images be retrieved.
191+
#[clap(long)]
170192
#[serde(default)]
171-
pub(crate) skip_bound_images: bool,
193+
pub(crate) bound_images: BoundImagesOpt,
172194

173195
/// The stateroot name to use. Defaults to `default`.
174196
#[clap(long)]
@@ -1271,7 +1293,7 @@ async fn install_with_sysroot(
12711293
rootfs: &RootSetup,
12721294
sysroot: &Storage,
12731295
boot_uuid: &str,
1274-
bound_images: &[crate::boundimage::ResolvedBoundImage],
1296+
bound_images: BoundImages,
12751297
) -> Result<()> {
12761298
// And actually set up the container in that root, returning a deployment and
12771299
// the aleph state (see below).
@@ -1298,18 +1320,66 @@ async fn install_with_sysroot(
12981320
tracing::debug!("Installed bootloader");
12991321

13001322
tracing::debug!("Perfoming post-deployment operations");
1301-
// Note that we *always* initialize this container storage, even
1302-
// if there are no bound images today.
1323+
1324+
// Note that we *always* initialize this container storage, even if there are no bound images
1325+
// today.
13031326
let imgstore = sysroot.get_ensure_imgstore()?;
1304-
// Now copy each bound image from the host's container storage into the target.
1305-
for image in bound_images {
1306-
let image = image.image.as_str();
1307-
imgstore.pull_from_host_storage(image).await?;
1327+
1328+
match bound_images {
1329+
BoundImages::Skip => {}
1330+
BoundImages::Resolved(resolved_bound_images) => {
1331+
// Now copy each bound image from the host's container storage into the target.
1332+
for image in resolved_bound_images {
1333+
let image = image.image.as_str();
1334+
imgstore.pull_from_host_storage(image).await?;
1335+
}
1336+
}
1337+
BoundImages::Unresolved(bound_images) => {
1338+
crate::boundimage::pull_images(sysroot, bound_images)
1339+
.await
1340+
.context("pulling bound images")?;
1341+
}
13081342
}
13091343

13101344
Ok(())
13111345
}
13121346

1347+
enum BoundImages {
1348+
Skip,
1349+
Resolved(Vec<ResolvedBoundImage>),
1350+
Unresolved(Vec<BoundImage>),
1351+
}
1352+
1353+
impl BoundImages {
1354+
async fn from_state(state: &State) -> Result<Self> {
1355+
let bound_images = match state.config_opts.bound_images {
1356+
BoundImagesOpt::Skip => BoundImages::Skip,
1357+
others => {
1358+
let queried_images = crate::boundimage::query_bound_images(&state.container_root)?;
1359+
match others {
1360+
BoundImagesOpt::Stored => {
1361+
// Verify each bound image is present in the container storage
1362+
let mut r = Vec::with_capacity(queried_images.len());
1363+
for image in queried_images {
1364+
let resolved = ResolvedBoundImage::from_image(&image).await?;
1365+
tracing::debug!("Resolved {}: {}", resolved.image, resolved.digest);
1366+
r.push(resolved)
1367+
}
1368+
BoundImages::Resolved(r)
1369+
}
1370+
BoundImagesOpt::Pull => {
1371+
// No need to resolve the images, we will pull them into the target later
1372+
BoundImages::Unresolved(queried_images)
1373+
}
1374+
BoundImagesOpt::Skip => bail!("unreachable error"),
1375+
}
1376+
}
1377+
};
1378+
1379+
Ok(bound_images)
1380+
}
1381+
}
1382+
13131383
async fn install_to_filesystem_impl(state: &State, rootfs: &mut RootSetup) -> Result<()> {
13141384
if matches!(state.selinux_state, SELinuxFinalState::ForceTargetDisabled) {
13151385
rootfs.kargs.push("selinux=0".to_string());
@@ -1336,28 +1406,12 @@ async fn install_to_filesystem_impl(state: &State, rootfs: &mut RootSetup) -> Re
13361406
.ok_or_else(|| anyhow!("No uuid for boot/root"))?;
13371407
tracing::debug!("boot uuid={boot_uuid}");
13381408

1339-
let bound_images = if state.config_opts.skip_bound_images {
1340-
Vec::new()
1341-
} else {
1342-
crate::boundimage::query_bound_images(&state.container_root)?
1343-
};
1344-
tracing::debug!("bound images={bound_images:?}");
1345-
1346-
// Verify each bound image is present in the container storage
1347-
let bound_images = {
1348-
let mut r = Vec::with_capacity(bound_images.len());
1349-
for image in bound_images {
1350-
let resolved = crate::boundimage::ResolvedBoundImage::from_image(&image).await?;
1351-
tracing::debug!("Resolved {}: {}", resolved.image, resolved.digest);
1352-
r.push(resolved)
1353-
}
1354-
r
1355-
};
1409+
let bound_images = BoundImages::from_state(state).await?;
13561410

13571411
// Initialize the ostree sysroot (repo, stateroot, etc.)
13581412
{
13591413
let sysroot = initialize_ostree_root(state, rootfs).await?;
1360-
install_with_sysroot(state, rootfs, &sysroot, &boot_uuid, &bound_images).await?;
1414+
install_with_sysroot(state, rootfs, &sysroot, &boot_uuid, bound_images).await?;
13611415
// We must drop the sysroot here in order to close any open file
13621416
// descriptors.
13631417
}

0 commit comments

Comments
 (0)