@@ -93,6 +93,17 @@ pub(crate) async fn new_importer(
9393 Ok ( imp)
9494}
9595
96+ /// Wrapper for pulling a container image with a custom proxy config (e.g. for unified storage).
97+ pub ( crate ) async fn new_importer_with_config (
98+ repo : & ostree:: Repo ,
99+ imgref : & ostree_container:: OstreeImageReference ,
100+ config : ostree_ext:: containers_image_proxy:: ImageProxyConfig ,
101+ ) -> Result < ostree_container:: store:: ImageImporter > {
102+ let mut imp = ostree_container:: store:: ImageImporter :: new ( repo, imgref, config) . await ?;
103+ imp. require_bootable ( ) ;
104+ Ok ( imp)
105+ }
106+
96107pub ( crate ) fn check_bootc_label ( config : & ostree_ext:: oci_spec:: image:: ImageConfiguration ) {
97108 if let Some ( label) =
98109 labels_of_config ( config) . and_then ( |labels| labels. get ( crate :: metadata:: BOOTC_COMPAT_LABEL ) )
@@ -316,6 +327,16 @@ pub(crate) async fn prune_container_store(sysroot: &Storage) -> Result<()> {
316327 for deployment in deployments {
317328 let bound = crate :: boundimage:: query_bound_images_for_deployment ( ostree, & deployment) ?;
318329 all_bound_images. extend ( bound. into_iter ( ) ) ;
330+ // Also include the host image itself
331+ if let Some ( host_image) = crate :: status:: boot_entry_from_deployment ( ostree, & deployment) ?
332+ . image
333+ . map ( |i| i. image )
334+ {
335+ all_bound_images. push ( crate :: boundimage:: BoundImage {
336+ image : crate :: utils:: imageref_to_container_ref ( & host_image) ,
337+ auth_file : None ,
338+ } ) ;
339+ }
319340 }
320341 // Convert to a hashset of just the image names
321342 let image_names = HashSet :: from_iter ( all_bound_images. iter ( ) . map ( |img| img. image . as_str ( ) ) ) ;
@@ -381,6 +402,127 @@ pub(crate) async fn prepare_for_pull(
381402 Ok ( PreparedPullResult :: Ready ( Box :: new ( prepared_image) ) )
382403}
383404
405+ /// Unified approach: Use bootc's CStorage to pull the image, then prepare from containers-storage.
406+ /// This reuses the same infrastructure as LBIs.
407+ pub ( crate ) async fn prepare_for_pull_unified (
408+ repo : & ostree:: Repo ,
409+ imgref : & ImageReference ,
410+ target_imgref : Option < & OstreeImageReference > ,
411+ store : & Storage ,
412+ ) -> Result < PreparedPullResult > {
413+ // Get or initialize the bootc container storage (same as used for LBIs)
414+ let imgstore = store. get_ensure_imgstore ( ) ?;
415+
416+ let image_ref_str = crate :: utils:: imageref_to_container_ref ( imgref) ;
417+
418+ // Log the original transport being used for the pull
419+ tracing:: info!(
420+ "Unified pull: pulling from transport '{}' to bootc storage" ,
421+ & imgref. transport
422+ ) ;
423+
424+ // Pull the image to bootc storage using the same method as LBIs
425+ imgstore
426+ . pull ( & image_ref_str, crate :: podstorage:: PullMode :: Always )
427+ . await ?;
428+
429+ // Now create a containers-storage reference to read from bootc storage
430+ tracing:: info!( "Unified pull: now importing from containers-storage transport" ) ;
431+ let containers_storage_imgref = ImageReference {
432+ transport : "containers-storage" . to_string ( ) ,
433+ image : imgref. image . clone ( ) ,
434+ signature : imgref. signature . clone ( ) ,
435+ } ;
436+ let ostree_imgref = OstreeImageReference :: from ( containers_storage_imgref) ;
437+
438+ // Configure the importer to use bootc storage as an additional image store
439+ use std:: process:: Command ;
440+ let mut config = ostree_ext:: containers_image_proxy:: ImageProxyConfig :: default ( ) ;
441+ let mut cmd = Command :: new ( "skopeo" ) ;
442+ // Use the actual physical path to bootc storage, not the alias
443+ let storage_path = format ! ( "/sysroot/{}" , crate :: podstorage:: CStorage :: subpath( ) ) ;
444+ crate :: podstorage:: set_additional_image_store ( & mut cmd, & storage_path) ;
445+ config. skopeo_cmd = Some ( cmd) ;
446+
447+ // Use the preparation flow with the custom config
448+ let mut imp = new_importer_with_config ( repo, & ostree_imgref, config) . await ?;
449+ if let Some ( target) = target_imgref {
450+ imp. set_target ( target) ;
451+ }
452+ let prep = match imp. prepare ( ) . await ? {
453+ PrepareResult :: AlreadyPresent ( c) => {
454+ println ! ( "No changes in {imgref:#} => {}" , c. manifest_digest) ;
455+ return Ok ( PreparedPullResult :: AlreadyPresent ( Box :: new ( ( * c) . into ( ) ) ) ) ;
456+ }
457+ PrepareResult :: Ready ( p) => p,
458+ } ;
459+ check_bootc_label ( & prep. config ) ;
460+ if let Some ( warning) = prep. deprecated_warning ( ) {
461+ ostree_ext:: cli:: print_deprecated_warning ( warning) . await ;
462+ }
463+ ostree_ext:: cli:: print_layer_status ( & prep) ;
464+ let layers_to_fetch = prep. layers_to_fetch ( ) . collect :: < Result < Vec < _ > > > ( ) ?;
465+
466+ // Log that we're importing a new image from containers-storage
467+ const PULLING_NEW_IMAGE_ID : & str = "6d5e4f3a2b1c0d9e8f7a6b5c4d3e2f1a0" ;
468+ tracing:: info!(
469+ message_id = PULLING_NEW_IMAGE_ID ,
470+ bootc. image. reference = & imgref. image,
471+ bootc. image. transport = "containers-storage" ,
472+ bootc. original_transport = & imgref. transport,
473+ bootc. status = "importing_from_storage" ,
474+ "Importing image from bootc storage: {}" ,
475+ ostree_imgref
476+ ) ;
477+
478+ let prepared_image = PreparedImportMeta {
479+ imp,
480+ n_layers_to_fetch : layers_to_fetch. len ( ) ,
481+ layers_total : prep. all_layers ( ) . count ( ) ,
482+ bytes_to_fetch : layers_to_fetch. iter ( ) . map ( |( l, _) | l. layer . size ( ) ) . sum ( ) ,
483+ bytes_total : prep. all_layers ( ) . map ( |l| l. layer . size ( ) ) . sum ( ) ,
484+ digest : prep. manifest_digest . clone ( ) ,
485+ prep,
486+ } ;
487+
488+ Ok ( PreparedPullResult :: Ready ( Box :: new ( prepared_image) ) )
489+ }
490+
491+ /// Unified pull: Use podman to pull to containers-storage, then read from there
492+ pub ( crate ) async fn pull_unified (
493+ repo : & ostree:: Repo ,
494+ imgref : & ImageReference ,
495+ target_imgref : Option < & OstreeImageReference > ,
496+ quiet : bool ,
497+ prog : ProgressWriter ,
498+ store : & Storage ,
499+ ) -> Result < Box < ImageState > > {
500+ match prepare_for_pull_unified ( repo, imgref, target_imgref, store) . await ? {
501+ PreparedPullResult :: AlreadyPresent ( existing) => {
502+ // Log that the image was already present (Debug level since it's not actionable)
503+ const IMAGE_ALREADY_PRESENT_ID : & str = "5c4d3e2f1a0b9c8d7e6f5a4b3c2d1e0f9" ;
504+ tracing:: debug!(
505+ message_id = IMAGE_ALREADY_PRESENT_ID ,
506+ bootc. image. reference = & imgref. image,
507+ bootc. image. transport = & imgref. transport,
508+ bootc. status = "already_present" ,
509+ "Image already present: {}" ,
510+ imgref
511+ ) ;
512+ Ok ( existing)
513+ }
514+ PreparedPullResult :: Ready ( prepared_image_meta) => {
515+ // To avoid duplicate success logs, pass a containers-storage imgref to the importer
516+ let cs_imgref = ImageReference {
517+ transport : "containers-storage" . to_string ( ) ,
518+ image : imgref. image . clone ( ) ,
519+ signature : imgref. signature . clone ( ) ,
520+ } ;
521+ pull_from_prepared ( & cs_imgref, quiet, prog, * prepared_image_meta) . await
522+ }
523+ }
524+ }
525+
384526#[ context( "Pulling" ) ]
385527pub ( crate ) async fn pull_from_prepared (
386528 imgref : & ImageReference ,
@@ -430,18 +572,21 @@ pub(crate) async fn pull_from_prepared(
430572 let imgref_canonicalized = imgref. clone ( ) . canonicalize ( ) ?;
431573 tracing:: debug!( "Canonicalized image reference: {imgref_canonicalized:#}" ) ;
432574
433- // Log successful import completion
434- const IMPORT_COMPLETE_JOURNAL_ID : & str = "4d3e2f1a0b9c8d7e6f5a4b3c2d1e0f9a8" ;
435-
436- tracing:: info!(
437- message_id = IMPORT_COMPLETE_JOURNAL_ID ,
438- bootc. image. reference = & imgref. image,
439- bootc. image. transport = & imgref. transport,
440- bootc. manifest_digest = import. manifest_digest. as_ref( ) ,
441- bootc. ostree_commit = & import. merge_commit,
442- "Successfully imported image: {}" ,
443- imgref
444- ) ;
575+ // Log successful import completion (skip if using unified storage to avoid double logging)
576+ let is_unified_path = imgref. transport == "containers-storage" ;
577+ if !is_unified_path {
578+ const IMPORT_COMPLETE_JOURNAL_ID : & str = "4d3e2f1a0b9c8d7e6f5a4b3c2d1e0f9a8" ;
579+
580+ tracing:: info!(
581+ message_id = IMPORT_COMPLETE_JOURNAL_ID ,
582+ bootc. image. reference = & imgref. image,
583+ bootc. image. transport = & imgref. transport,
584+ bootc. manifest_digest = import. manifest_digest. as_ref( ) ,
585+ bootc. ostree_commit = & import. merge_commit,
586+ "Successfully imported image: {}" ,
587+ imgref
588+ ) ;
589+ }
445590
446591 if let Some ( msg) =
447592 ostree_container:: store:: image_filtered_content_warning ( & import. filtered_files )
@@ -490,6 +635,9 @@ pub(crate) async fn pull(
490635 }
491636}
492637
638+ /// Pull selecting unified vs standard path based on persistent storage config.
639+ // pull_auto was reverted per request; keep explicit callers branching.
640+
493641pub ( crate ) async fn wipe_ostree ( sysroot : Sysroot ) -> Result < ( ) > {
494642 tokio:: task:: spawn_blocking ( move || {
495643 sysroot
0 commit comments