Skip to content

Commit be75055

Browse files
committed
WIP: Use own container-storage for host images if the refspec
On install add flag to enable unified-storage Signed-off-by: Joseph Marrero Corchado <[email protected]>
1 parent 24f2dd0 commit be75055

File tree

3 files changed

+194
-18
lines changed

3 files changed

+194
-18
lines changed

crates/lib/src/cli.rs

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -928,7 +928,19 @@ async fn upgrade(opts: UpgradeOpts) -> Result<()> {
928928
}
929929
}
930930
} else {
931-
let fetched = crate::deploy::pull(repo, imgref, None, opts.quiet, prog.clone()).await?;
931+
// Check if image exists in bootc storage (/usr/lib/bootc/storage)
932+
let imgstore = sysroot.get_ensure_imgstore()?;
933+
let use_unified = imgstore
934+
.exists(&format!("{imgref:#}"))
935+
.await
936+
.unwrap_or(false);
937+
938+
let fetched = if use_unified {
939+
crate::deploy::pull_unified(repo, imgref, None, opts.quiet, prog.clone(), sysroot)
940+
.await?
941+
} else {
942+
crate::deploy::pull(repo, imgref, None, opts.quiet, prog.clone()).await?
943+
};
932944
let staged_digest = staged_image.map(|s| s.digest().expect("valid digest in status"));
933945
let fetched_digest = &fetched.manifest_digest;
934946
tracing::debug!("staged: {staged_digest:?}");
@@ -1056,7 +1068,18 @@ async fn switch(opts: SwitchOpts) -> Result<()> {
10561068

10571069
let new_spec = RequiredHostSpec::from_spec(&new_spec)?;
10581070

1059-
let fetched = crate::deploy::pull(repo, &target, None, opts.quiet, prog.clone()).await?;
1071+
// Check if image exists in bootc storage (/usr/lib/bootc/storage)
1072+
let imgstore = sysroot.get_ensure_imgstore()?;
1073+
let use_unified = imgstore
1074+
.exists(&format!("{target:#}"))
1075+
.await
1076+
.unwrap_or(false);
1077+
1078+
let fetched = if use_unified {
1079+
crate::deploy::pull_unified(repo, &target, None, opts.quiet, prog.clone(), sysroot).await?
1080+
} else {
1081+
crate::deploy::pull(repo, &target, None, opts.quiet, prog.clone()).await?
1082+
};
10601083

10611084
if !opts.retain {
10621085
// By default, we prune the previous ostree ref so it will go away after later upgrades
@@ -1422,7 +1445,10 @@ async fn run_from_opt(opt: Opt) -> Result<()> {
14221445
let mut w = SplitStreamWriter::new(&cfs, None, Some(testdata_digest));
14231446
w.write_inline(testdata);
14241447
let object = cfs.write_stream(w, Some("testobject"))?.to_hex();
1425-
assert_eq!(object, "5d94ceb0b2bb3a78237e0a74bc030a262239ab5f47754a5eb2e42941056b64cb21035d64a8f7c2f156e34b820802fa51884de2b1f7dc3a41b9878fc543cd9b07");
1448+
assert_eq!(
1449+
object,
1450+
"5d94ceb0b2bb3a78237e0a74bc030a262239ab5f47754a5eb2e42941056b64cb21035d64a8f7c2f156e34b820802fa51884de2b1f7dc3a41b9878fc543cd9b07"
1451+
);
14261452
Ok(())
14271453
}
14281454
// We don't depend on fsverity-utils today, so re-expose some helpful CLI tools.

crates/lib/src/deploy.rs

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

383+
/// Unified approach: Use bootc's CStorage to pull the image, then prepare from containers-storage.
384+
/// This reuses the same infrastructure as LBIs.
385+
pub(crate) async fn prepare_for_pull_unified(
386+
repo: &ostree::Repo,
387+
imgref: &ImageReference,
388+
target_imgref: Option<&OstreeImageReference>,
389+
store: &Storage,
390+
) -> Result<PreparedPullResult> {
391+
// Get or initialize the bootc container storage (same as used for LBIs)
392+
let imgstore = store.get_ensure_imgstore()?;
393+
394+
let image_ref_str = format!("{imgref:#}");
395+
396+
// Log the original transport being used for the pull
397+
tracing::info!(
398+
"Unified pull: pulling from transport '{}' to bootc storage",
399+
&imgref.transport
400+
);
401+
402+
// Pull the image to bootc storage using the same method as LBIs
403+
imgstore
404+
.pull(&image_ref_str, crate::podstorage::PullMode::Always)
405+
.await?;
406+
407+
// Now create a containers-storage reference to read from bootc storage
408+
tracing::info!("Unified pull: now importing from containers-storage transport");
409+
let containers_storage_imgref = ImageReference {
410+
transport: "containers-storage".to_string(),
411+
image: imgref.image.clone(),
412+
signature: imgref.signature.clone(),
413+
};
414+
let ostree_imgref = OstreeImageReference::from(containers_storage_imgref);
415+
416+
// Use the standard preparation flow but reading from containers-storage
417+
let mut imp = new_importer(repo, &ostree_imgref).await?;
418+
if let Some(target) = target_imgref {
419+
imp.set_target(target);
420+
}
421+
let prep = match imp.prepare().await? {
422+
PrepareResult::AlreadyPresent(c) => {
423+
println!("No changes in {imgref:#} => {}", c.manifest_digest);
424+
return Ok(PreparedPullResult::AlreadyPresent(Box::new((*c).into())));
425+
}
426+
PrepareResult::Ready(p) => p,
427+
};
428+
check_bootc_label(&prep.config);
429+
if let Some(warning) = prep.deprecated_warning() {
430+
ostree_ext::cli::print_deprecated_warning(warning).await;
431+
}
432+
ostree_ext::cli::print_layer_status(&prep);
433+
let layers_to_fetch = prep.layers_to_fetch().collect::<Result<Vec<_>>>()?;
434+
435+
// Log that we're importing a new image from containers-storage
436+
const PULLING_NEW_IMAGE_ID: &str = "6d5e4f3a2b1c0d9e8f7a6b5c4d3e2f1a0";
437+
tracing::info!(
438+
message_id = PULLING_NEW_IMAGE_ID,
439+
bootc.image.reference = &imgref.image,
440+
bootc.image.transport = "containers-storage",
441+
bootc.original_transport = &imgref.transport,
442+
bootc.status = "importing_from_storage",
443+
"Importing image from bootc storage: {}",
444+
ostree_imgref
445+
);
446+
447+
let prepared_image = PreparedImportMeta {
448+
imp,
449+
n_layers_to_fetch: layers_to_fetch.len(),
450+
layers_total: prep.all_layers().count(),
451+
bytes_to_fetch: layers_to_fetch.iter().map(|(l, _)| l.layer.size()).sum(),
452+
bytes_total: prep.all_layers().map(|l| l.layer.size()).sum(),
453+
digest: prep.manifest_digest.clone(),
454+
prep,
455+
};
456+
457+
Ok(PreparedPullResult::Ready(Box::new(prepared_image)))
458+
}
459+
460+
/// Unified pull: Use podman to pull to containers-storage, then read from there
461+
pub(crate) async fn pull_unified(
462+
repo: &ostree::Repo,
463+
imgref: &ImageReference,
464+
target_imgref: Option<&OstreeImageReference>,
465+
quiet: bool,
466+
prog: ProgressWriter,
467+
store: &Storage,
468+
) -> Result<Box<ImageState>> {
469+
match prepare_for_pull_unified(repo, imgref, target_imgref, store).await? {
470+
PreparedPullResult::AlreadyPresent(existing) => {
471+
// Log that the image was already present (Debug level since it's not actionable)
472+
const IMAGE_ALREADY_PRESENT_ID: &str = "5c4d3e2f1a0b9c8d7e6f5a4b3c2d1e0f9";
473+
tracing::debug!(
474+
message_id = IMAGE_ALREADY_PRESENT_ID,
475+
bootc.image.reference = &imgref.image,
476+
bootc.image.transport = &imgref.transport,
477+
bootc.status = "already_present",
478+
"Image already present: {}",
479+
imgref
480+
);
481+
Ok(existing)
482+
}
483+
PreparedPullResult::Ready(prepared_image_meta) => {
484+
pull_from_prepared(imgref, quiet, prog, *prepared_image_meta).await
485+
}
486+
}
487+
}
488+
383489
#[context("Pulling")]
384490
pub(crate) async fn pull_from_prepared(
385491
imgref: &ImageReference,
@@ -429,18 +535,21 @@ pub(crate) async fn pull_from_prepared(
429535
let imgref_canonicalized = imgref.clone().canonicalize()?;
430536
tracing::debug!("Canonicalized image reference: {imgref_canonicalized:#}");
431537

432-
// Log successful import completion
433-
const IMPORT_COMPLETE_JOURNAL_ID: &str = "4d3e2f1a0b9c8d7e6f5a4b3c2d1e0f9a8";
434-
435-
tracing::info!(
436-
message_id = IMPORT_COMPLETE_JOURNAL_ID,
437-
bootc.image.reference = &imgref.image,
438-
bootc.image.transport = &imgref.transport,
439-
bootc.manifest_digest = import.manifest_digest.as_ref(),
440-
bootc.ostree_commit = &import.merge_commit,
441-
"Successfully imported image: {}",
442-
imgref
443-
);
538+
// Log successful import completion (skip if using unified storage to avoid double logging)
539+
let is_unified_path = imgref.transport == "containers-storage";
540+
if !is_unified_path {
541+
const IMPORT_COMPLETE_JOURNAL_ID: &str = "4d3e2f1a0b9c8d7e6f5a4b3c2d1e0f9a8";
542+
543+
tracing::info!(
544+
message_id = IMPORT_COMPLETE_JOURNAL_ID,
545+
bootc.image.reference = &imgref.image,
546+
bootc.image.transport = &imgref.transport,
547+
bootc.manifest_digest = import.manifest_digest.as_ref(),
548+
bootc.ostree_commit = &import.merge_commit,
549+
"Successfully imported image: {}",
550+
imgref
551+
);
552+
}
444553

445554
if let Some(msg) =
446555
ostree_container::store::image_filtered_content_warning(&import.filtered_files)

crates/lib/src/install.rs

Lines changed: 44 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,15 @@ pub(crate) struct InstallTargetOpts {
166166
#[clap(long)]
167167
#[serde(default)]
168168
pub(crate) skip_fetch_check: bool,
169+
170+
/// Use unified storage path to pull images (experimental)
171+
///
172+
/// When enabled, this uses bootc's container storage (/usr/lib/bootc/storage) to pull
173+
/// the image first, then imports it from there. This is the same approach used for
174+
/// logically bound images.
175+
#[clap(long)]
176+
#[serde(default)]
177+
pub(crate) unified_storage_exp: bool,
169178
}
170179

171180
#[derive(clap::Args, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
@@ -426,6 +435,7 @@ pub(crate) struct State {
426435
pub(crate) selinux_state: SELinuxFinalState,
427436
#[allow(dead_code)]
428437
pub(crate) config_opts: InstallConfigOpts,
438+
pub(crate) target_opts: InstallTargetOpts,
429439
pub(crate) target_imgref: ostree_container::OstreeImageReference,
430440
#[allow(dead_code)]
431441
pub(crate) prepareroot_config: HashMap<String, String>,
@@ -787,6 +797,7 @@ async fn install_container(
787797
state: &State,
788798
root_setup: &RootSetup,
789799
sysroot: &ostree::Sysroot,
800+
storage: &Storage,
790801
has_ostree: bool,
791802
) -> Result<(ostree::Deployment, InstallAleph)> {
792803
let sepolicy = state.load_policy()?;
@@ -826,9 +837,38 @@ async fn install_container(
826837
let repo = &sysroot.repo();
827838
repo.set_disable_fsync(true);
828839

829-
let pulled_image = match prepare_for_pull(repo, &spec_imgref, Some(&state.target_imgref))
840+
// Determine whether to use unified storage path
841+
let use_unified = if state.target_opts.unified_storage_exp {
842+
// Explicit flag always uses unified path
843+
true
844+
} else {
845+
// Auto-detect: check if image exists in bootc storage (same as upgrade/switch)
846+
let imgstore = storage.get_ensure_imgstore()?;
847+
imgstore
848+
.exists(&format!("{spec_imgref:#}"))
849+
.await
850+
.unwrap_or_else(|e| {
851+
tracing::warn!(
852+
"Failed to check bootc storage for image: {e}; falling back to standard pull"
853+
);
854+
false
855+
})
856+
};
857+
858+
let prepared = if use_unified {
859+
tracing::info!("Using unified storage path for installation");
860+
crate::deploy::prepare_for_pull_unified(
861+
repo,
862+
&spec_imgref,
863+
Some(&state.target_imgref),
864+
storage,
865+
)
830866
.await?
831-
{
867+
} else {
868+
prepare_for_pull(repo, &spec_imgref, Some(&state.target_imgref)).await?
869+
};
870+
871+
let pulled_image = match prepared {
832872
PreparedPullResult::AlreadyPresent(existing) => existing,
833873
PreparedPullResult::Ready(image_meta) => {
834874
check_disk_space(root_setup.physical_root.as_fd(), &image_meta, &spec_imgref)?;
@@ -1364,6 +1404,7 @@ async fn prepare_install(
13641404
selinux_state,
13651405
source,
13661406
config_opts,
1407+
target_opts,
13671408
target_imgref,
13681409
install_config,
13691410
prepareroot_config,
@@ -1395,7 +1436,7 @@ async fn install_with_sysroot(
13951436

13961437
// And actually set up the container in that root, returning a deployment and
13971438
// the aleph state (see below).
1398-
let (deployment, aleph) = install_container(state, rootfs, ostree, has_ostree).await?;
1439+
let (deployment, aleph) = install_container(state, rootfs, ostree, storage, has_ostree).await?;
13991440
// Write the aleph data that captures the system state at the time of provisioning for aid in future debugging.
14001441
aleph.write_to(&rootfs.physical_root)?;
14011442

0 commit comments

Comments
 (0)