Skip to content

Commit 1baf1e8

Browse files
committed
WIP: Add --unified to use our container-storage for host images
1 parent 77c607a commit 1baf1e8

File tree

3 files changed

+136
-2
lines changed

3 files changed

+136
-2
lines changed

crates/lib/src/cli.rs

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,11 @@ pub(crate) struct UpgradeOpts {
8989
#[clap(long = "soft-reboot", conflicts_with = "check")]
9090
pub(crate) soft_reboot: Option<SoftRebootMode>,
9191

92+
/// Use podman/skopeo to pull image to additionalimagestore, then read from container storage.
93+
/// This provides a unified approach that leverages existing container tooling.
94+
#[clap(long)]
95+
pub(crate) unified: bool,
96+
9297
#[clap(flatten)]
9398
pub(crate) progress: ProgressOptions,
9499
}
@@ -144,6 +149,11 @@ pub(crate) struct SwitchOpts {
144149
/// Target image to use for the next boot.
145150
pub(crate) target: String,
146151

152+
/// Use podman/skopeo to pull image to additionalimagestore, then read from container storage.
153+
/// This provides a unified approach that leverages existing container tooling.
154+
#[clap(long)]
155+
pub(crate) unified: bool,
156+
147157
#[clap(flatten)]
148158
pub(crate) progress: ProgressOptions,
149159
}
@@ -975,7 +985,11 @@ async fn upgrade(opts: UpgradeOpts) -> Result<()> {
975985
}
976986
}
977987
} else {
978-
let fetched = crate::deploy::pull(repo, imgref, None, opts.quiet, prog.clone()).await?;
988+
let fetched = if opts.unified {
989+
crate::deploy::pull_unified(repo, imgref, None, opts.quiet, prog.clone(), sysroot).await?
990+
} else {
991+
crate::deploy::pull(repo, imgref, None, opts.quiet, prog.clone()).await?
992+
};
979993
let staged_digest = staged_image.map(|s| s.digest().expect("valid digest in status"));
980994
let fetched_digest = &fetched.manifest_digest;
981995
tracing::debug!("staged: {staged_digest:?}");
@@ -1096,7 +1110,11 @@ async fn switch(opts: SwitchOpts) -> Result<()> {
10961110

10971111
let new_spec = RequiredHostSpec::from_spec(&new_spec)?;
10981112

1099-
let fetched = crate::deploy::pull(repo, &target, None, opts.quiet, prog.clone()).await?;
1113+
let fetched = if opts.unified {
1114+
crate::deploy::pull_unified(repo, &target, None, opts.quiet, prog.clone(), sysroot).await?
1115+
} else {
1116+
crate::deploy::pull(repo, &target, None, opts.quiet, prog.clone()).await?
1117+
};
11001118

11011119
if !opts.retain {
11021120
// By default, we prune the previous ostree ref so it will go away after later upgrades

crates/lib/src/deploy.rs

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -380,6 +380,98 @@ pub(crate) async fn prepare_for_pull(
380380
Ok(PreparedPullResult::Ready(Box::new(prepared_image)))
381381
}
382382

383+
/// Unified approach: First pull with podman to containers-storage, then prepare from containers-storage
384+
pub(crate) async fn prepare_for_pull_unified(
385+
repo: &ostree::Repo,
386+
imgref: &ImageReference,
387+
target_imgref: Option<&OstreeImageReference>,
388+
store: &Storage,
389+
) -> Result<PreparedPullResult> {
390+
// Ensure bootc storage is properly initialized before using unified storage
391+
let _imgstore = store.get_ensure_imgstore()?;
392+
393+
// First, pull the image using podman with unified storage
394+
crate::podman::pull_image_unified(&format!("{imgref:#}")).await?;
395+
396+
// Now create a containers-storage reference to the pulled image
397+
let containers_storage_imgref = ImageReference {
398+
transport: "containers-storage".to_string(),
399+
image: imgref.image.clone(),
400+
signature: imgref.signature.clone(),
401+
};
402+
let ostree_imgref = &OstreeImageReference::from(containers_storage_imgref);
403+
404+
// Use the standard preparation flow but reading from containers-storage
405+
let mut imp = new_importer(repo, ostree_imgref).await?;
406+
if let Some(target) = target_imgref {
407+
imp.set_target(target);
408+
}
409+
let prep = match imp.prepare().await? {
410+
PrepareResult::AlreadyPresent(c) => {
411+
println!("No changes in {imgref:#} => {}", c.manifest_digest);
412+
return Ok(PreparedPullResult::AlreadyPresent(Box::new((*c).into())));
413+
}
414+
PrepareResult::Ready(p) => p,
415+
};
416+
check_bootc_label(&prep.config);
417+
if let Some(warning) = prep.deprecated_warning() {
418+
ostree_ext::cli::print_deprecated_warning(warning).await;
419+
}
420+
ostree_ext::cli::print_layer_status(&prep);
421+
let layers_to_fetch = prep.layers_to_fetch().collect::<Result<Vec<_>>>()?;
422+
423+
let prepared_image = PreparedImportMeta {
424+
imp,
425+
n_layers_to_fetch: layers_to_fetch.len(),
426+
layers_total: prep.all_layers().count(),
427+
bytes_to_fetch: layers_to_fetch.iter().map(|(l, _)| l.layer.size()).sum(),
428+
bytes_total: prep.all_layers().map(|l| l.layer.size()).sum(),
429+
digest: prep.manifest_digest.clone(),
430+
prep,
431+
};
432+
433+
Ok(PreparedPullResult::Ready(Box::new(prepared_image)))
434+
}
435+
436+
/// Unified pull: Use podman to pull to containers-storage, then read from there
437+
pub(crate) async fn pull_unified(
438+
repo: &ostree::Repo,
439+
imgref: &ImageReference,
440+
target_imgref: Option<&OstreeImageReference>,
441+
quiet: bool,
442+
prog: ProgressWriter,
443+
store: &Storage,
444+
) -> Result<Box<ImageState>> {
445+
match prepare_for_pull_unified(repo, imgref, target_imgref, store).await? {
446+
PreparedPullResult::AlreadyPresent(existing) => {
447+
// Log that the image was already present (Debug level since it's not actionable)
448+
const IMAGE_ALREADY_PRESENT_ID: &str = "5c4d3e2f1a0b9c8d7e6f5a4b3c2d1e0f9";
449+
tracing::debug!(
450+
message_id = IMAGE_ALREADY_PRESENT_ID,
451+
bootc.image.reference = &imgref.image,
452+
bootc.image.transport = &imgref.transport,
453+
bootc.status = "already_present",
454+
"Image already present: {}",
455+
imgref
456+
);
457+
Ok(existing)
458+
}
459+
PreparedPullResult::Ready(prepared_image_meta) => {
460+
// Log that we're pulling a new image
461+
const PULLING_NEW_IMAGE_ID: &str = "6d5e4f3a2b1c0d9e8f7a6b5c4d3e2f1a0";
462+
tracing::info!(
463+
message_id = PULLING_NEW_IMAGE_ID,
464+
bootc.image.reference = &imgref.image,
465+
bootc.image.transport = &imgref.transport,
466+
bootc.status = "pulling_new",
467+
"Pulling new image: {}",
468+
imgref
469+
);
470+
pull_from_prepared(imgref, quiet, prog, *prepared_image_meta).await
471+
}
472+
}
473+
}
474+
383475
#[context("Pulling")]
384476
pub(crate) async fn pull_from_prepared(
385477
imgref: &ImageReference,

crates/lib/src/podman.rs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,3 +51,27 @@ pub(crate) fn storage_exists(root: &Dir, path: impl AsRef<Utf8Path>) -> Result<b
5151
pub(crate) fn storage_exists_default(root: &Dir) -> Result<bool> {
5252
storage_exists(root, CONTAINER_STORAGE.trim_start_matches('/'))
5353
}
54+
55+
/// Pull an image using podman with additionalimagestore pointing to bootc storage.
56+
/// This allows the image to be available in both regular container storage and bootc storage.
57+
pub(crate) async fn pull_image_unified(imgref: &str) -> Result<()> {
58+
use bootc_utils::CommandRunExt;
59+
60+
// Use podman pull with additionalimagestore pointing to bootc storage
61+
let bootc_storage_path = "/usr/lib/bootc/storage";
62+
63+
tracing::info!("Pulling image via podman with unified storage: {}", imgref);
64+
65+
std::process::Command::new("podman")
66+
.args([
67+
"pull",
68+
"--storage-opt",
69+
&format!("additionalimagestore={}", bootc_storage_path),
70+
imgref,
71+
])
72+
.run_capture_stderr()
73+
.map_err(|e| anyhow::anyhow!("Failed to pull image via podman: {}", e))?;
74+
75+
tracing::info!("Successfully pulled image to unified storage: {}", imgref);
76+
Ok(())
77+
}

0 commit comments

Comments
 (0)