@@ -26,6 +26,7 @@ use glib::prelude::*;
2626use oci_spec:: image:: {
2727 self as oci_image, Arch , Descriptor , Digest , History , ImageConfiguration , ImageManifest ,
2828} ;
29+ use ocidir:: oci_spec:: distribution:: Reference ;
2930use ostree:: prelude:: { Cast , FileEnumeratorExt , FileExt , ToVariant } ;
3031use ostree:: { gio, glib} ;
3132use std:: collections:: { BTreeMap , BTreeSet , HashMap } ;
@@ -181,9 +182,10 @@ pub struct ImageImporter {
181182 disable_gc : bool , // If true, don't prune unused image layers
182183 /// If true, require the image has the bootable flag
183184 require_bootable : bool ,
185+ /// Do not attempt to contact the network
186+ offline : bool ,
184187 /// If true, we have ostree v2024.3 or newer.
185188 ostree_v2024_3 : bool ,
186- pub ( crate ) proxy_img : OpenedImage ,
187189
188190 layer_progress : Option < Sender < ImportProgress > > ,
189191 layer_byte_progress : Option < tokio:: sync:: watch:: Sender < Option < LayerProgress > > > ,
@@ -241,6 +243,8 @@ pub struct PreparedImport {
241243 pub layers : Vec < ManifestLayerState > ,
242244 /// OSTree remote signature verification text, if enabled.
243245 pub verify_text : Option < String > ,
246+ /// Our open image reference
247+ proxy_img : OpenedImage ,
244248}
245249
246250impl PreparedImport {
@@ -509,17 +513,16 @@ impl ImageImporter {
509513 & format ! ( "Fetching {}" , imgref) ,
510514 ) ;
511515
512- let proxy_img = proxy. open_image ( & imgref. imgref . to_string ( ) ) . await ?;
513516 let repo = repo. clone ( ) ;
514517 Ok ( ImageImporter {
515518 repo,
516519 proxy,
517- proxy_img,
518520 target_imgref : None ,
519521 no_imgref : false ,
520522 ostree_v2024_3 : ostree:: check_version ( 2024 , 3 ) ,
521523 disable_gc : false ,
522524 require_bootable : false ,
525+ offline : false ,
523526 imgref : imgref. clone ( ) ,
524527 layer_progress : None ,
525528 layer_byte_progress : None ,
@@ -538,6 +541,11 @@ impl ImageImporter {
538541 self . no_imgref = true ;
539542 }
540543
544+ /// Do not attempt to contact the network
545+ pub fn set_offline ( & mut self ) {
546+ self . offline = true ;
547+ }
548+
541549 /// Require that the image has the bootable metadata field
542550 pub fn require_bootable ( & mut self ) {
543551 self . require_bootable = true ;
@@ -619,6 +627,7 @@ impl ImageImporter {
619627 config : ImageConfiguration ,
620628 previous_state : Option < Box < LayeredImageState > > ,
621629 previous_imageid : Option < String > ,
630+ proxy_img : OpenedImage ,
622631 ) -> Result < Box < PreparedImport > > {
623632 let config_labels = super :: labels_of ( & config) ;
624633 if self . require_bootable {
@@ -662,6 +671,7 @@ impl ImageImporter {
662671 ostree_commit_layer : commit_layer,
663672 layers : remaining_layers,
664673 verify_text : None ,
674+ proxy_img,
665675 } ;
666676 Ok ( Box :: new ( imp) )
667677 }
@@ -681,30 +691,63 @@ impl ImageImporter {
681691 _ => { }
682692 }
683693
684- let ( manifest_digest, manifest) = self . proxy . fetch_manifest ( & self . proxy_img ) . await ?;
694+ // Check if we have an image already pulled
695+ let previous_state = try_query_image ( & self . repo , & self . imgref . imgref ) ?;
696+
697+ // Parse the target reference to see if it's a digested pull
698+ let target_reference = self . imgref . imgref . name . parse :: < Reference > ( ) . ok ( ) ;
699+ let previous_state = if let Some ( target_digest) = target_reference
700+ . as_ref ( )
701+ . and_then ( |v| v. digest ( ) )
702+ . map ( Digest :: from_str)
703+ . transpose ( ) ?
704+ {
705+ if let Some ( previous_state) = previous_state {
706+ // A digested pull spec, and our existing state matches.
707+ if previous_state. manifest_digest == target_digest {
708+ tracing:: debug!( "Digest-based pullspec {:?} already present" , self . imgref) ;
709+ return Ok ( PrepareResult :: AlreadyPresent ( previous_state) ) ;
710+ }
711+ Some ( previous_state)
712+ } else {
713+ None
714+ }
715+ } else {
716+ previous_state
717+ } ;
718+
719+ if self . offline {
720+ anyhow:: bail!( "Manifest fetch required in offline mode" ) ;
721+ }
722+
723+ let proxy_img = self
724+ . proxy
725+ . open_image ( & self . imgref . imgref . to_string ( ) )
726+ . await ?;
727+
728+ let ( manifest_digest, manifest) = self . proxy . fetch_manifest ( & proxy_img) . await ?;
685729 let manifest_digest = Digest :: from_str ( & manifest_digest) ?;
686730 let new_imageid = manifest. config ( ) . digest ( ) ;
687731
688732 // Query for previous stored state
689733
690- let ( previous_state, previous_imageid) =
691- if let Some ( previous_state) = try_query_image ( & self . repo , & self . imgref . imgref ) ? {
692- // If the manifest digests match, we're done.
693- if previous_state. manifest_digest == manifest_digest {
694- return Ok ( PrepareResult :: AlreadyPresent ( previous_state) ) ;
695- }
696- // Failing that, if they have the same imageID, we're also done.
697- let previous_imageid = previous_state. manifest . config ( ) . digest ( ) ;
698- if previous_imageid == new_imageid {
699- return Ok ( PrepareResult :: AlreadyPresent ( previous_state) ) ;
700- }
701- let previous_imageid = previous_imageid. to_string ( ) ;
702- ( Some ( previous_state) , Some ( previous_imageid) )
703- } else {
704- ( None , None )
705- } ;
734+ let ( previous_state, previous_imageid) = if let Some ( previous_state) = previous_state {
735+ // If the manifest digests match, we're done.
736+ if previous_state. manifest_digest == manifest_digest {
737+ return Ok ( PrepareResult :: AlreadyPresent ( previous_state) ) ;
738+ }
739+ // Failing that, if they have the same imageID, we're also done.
740+ let previous_imageid = previous_state. manifest . config ( ) . digest ( ) ;
741+ if previous_imageid == new_imageid {
742+ return Ok ( PrepareResult :: AlreadyPresent ( previous_state) ) ;
743+ }
744+ let previous_imageid = previous_imageid. to_string ( ) ;
745+ ( Some ( previous_state) , Some ( previous_imageid) )
746+ } else {
747+ ( None , None )
748+ } ;
706749
707- let config = self . proxy . fetch_config ( & self . proxy_img ) . await ?;
750+ let config = self . proxy . fetch_config ( & proxy_img) . await ?;
708751
709752 // If there is a currently fetched image, cache the new pending manifest+config
710753 // as detached commit metadata, so that future fetches can query it offline.
@@ -724,6 +767,7 @@ impl ImageImporter {
724767 config,
725768 previous_state,
726769 previous_imageid,
770+ proxy_img,
727771 ) ?;
728772 Ok ( PrepareResult :: Ready ( imp) )
729773 }
@@ -756,7 +800,7 @@ impl ImageImporter {
756800 }
757801 return Ok ( ( ) ) ;
758802 } ;
759- let des_layers = self . proxy . get_layer_info ( & self . proxy_img ) . await ?;
803+ let des_layers = self . proxy . get_layer_info ( & import . proxy_img ) . await ?;
760804 for layer in import. ostree_layers . iter_mut ( ) {
761805 if layer. commit . is_some ( ) {
762806 continue ;
@@ -767,7 +811,7 @@ impl ImageImporter {
767811 }
768812 let ( blob, driver, media_type) = fetch_layer (
769813 & self . proxy ,
770- & self . proxy_img ,
814+ & import . proxy_img ,
771815 & import. manifest ,
772816 & layer. layer ,
773817 self . layer_byte_progress . as_ref ( ) ,
@@ -814,7 +858,7 @@ impl ImageImporter {
814858 }
815859 let ( blob, driver, media_type) = fetch_layer (
816860 & self . proxy ,
817- & self . proxy_img ,
861+ & import . proxy_img ,
818862 & import. manifest ,
819863 & commit_layer. layer ,
820864 self . layer_byte_progress . as_ref ( ) ,
@@ -874,7 +918,7 @@ impl ImageImporter {
874918 self . unencapsulate_base ( & mut prep, true , false ) . await ?;
875919 // TODO change the imageproxy API to ensure this happens automatically when
876920 // the image reference is dropped
877- self . proxy . close_image ( & self . proxy_img ) . await ?;
921+ self . proxy . close_image ( & prep . proxy_img ) . await ?;
878922 // SAFETY: We know we have a commit
879923 let ostree_commit = prep. ostree_commit_layer . unwrap ( ) . commit . unwrap ( ) ;
880924 let image_digest = prep. manifest_digest ;
@@ -899,9 +943,8 @@ impl ImageImporter {
899943 // First download all layers for the base image (if necessary) - we need the SELinux policy
900944 // there to label all following layers.
901945 self . unencapsulate_base ( & mut import, false , true ) . await ?;
902- let des_layers = self . proxy . get_layer_info ( & self . proxy_img ) . await ?;
946+ let des_layers = self . proxy . get_layer_info ( & import . proxy_img ) . await ?;
903947 let proxy = self . proxy ;
904- let proxy_img = self . proxy_img ;
905948 let target_imgref = self . target_imgref . as_ref ( ) . unwrap_or ( & self . imgref ) ;
906949 let base_commit = import
907950 . ostree_commit_layer
@@ -935,7 +978,7 @@ impl ImageImporter {
935978 }
936979 let ( blob, driver, media_type) = super :: unencapsulate:: fetch_layer (
937980 & proxy,
938- & proxy_img,
981+ & import . proxy_img ,
939982 & import. manifest ,
940983 & layer. layer ,
941984 self . layer_byte_progress . as_ref ( ) ,
@@ -989,7 +1032,7 @@ impl ImageImporter {
9891032
9901033 // TODO change the imageproxy API to ensure this happens automatically when
9911034 // the image reference is dropped
992- proxy. close_image ( & proxy_img) . await ?;
1035+ proxy. close_image ( & import . proxy_img ) . await ?;
9931036
9941037 // We're done with the proxy, make sure it didn't have any errors.
9951038 proxy. finalize ( ) . await ?;
0 commit comments