@@ -26,6 +26,7 @@ use glib::prelude::*;
26
26
use oci_spec:: image:: {
27
27
self as oci_image, Arch , Descriptor , Digest , History , ImageConfiguration , ImageManifest ,
28
28
} ;
29
+ use ocidir:: oci_spec:: distribution:: Reference ;
29
30
use ostree:: prelude:: { Cast , FileEnumeratorExt , FileExt , ToVariant } ;
30
31
use ostree:: { gio, glib} ;
31
32
use std:: collections:: { BTreeMap , BTreeSet , HashMap } ;
@@ -181,9 +182,10 @@ pub struct ImageImporter {
181
182
disable_gc : bool , // If true, don't prune unused image layers
182
183
/// If true, require the image has the bootable flag
183
184
require_bootable : bool ,
185
+ /// Do not attempt to contact the network
186
+ offline : bool ,
184
187
/// If true, we have ostree v2024.3 or newer.
185
188
ostree_v2024_3 : bool ,
186
- pub ( crate ) proxy_img : OpenedImage ,
187
189
188
190
layer_progress : Option < Sender < ImportProgress > > ,
189
191
layer_byte_progress : Option < tokio:: sync:: watch:: Sender < Option < LayerProgress > > > ,
@@ -241,6 +243,8 @@ pub struct PreparedImport {
241
243
pub layers : Vec < ManifestLayerState > ,
242
244
/// OSTree remote signature verification text, if enabled.
243
245
pub verify_text : Option < String > ,
246
+ /// Our open image reference
247
+ proxy_img : OpenedImage ,
244
248
}
245
249
246
250
impl PreparedImport {
@@ -509,17 +513,16 @@ impl ImageImporter {
509
513
& format ! ( "Fetching {}" , imgref) ,
510
514
) ;
511
515
512
- let proxy_img = proxy. open_image ( & imgref. imgref . to_string ( ) ) . await ?;
513
516
let repo = repo. clone ( ) ;
514
517
Ok ( ImageImporter {
515
518
repo,
516
519
proxy,
517
- proxy_img,
518
520
target_imgref : None ,
519
521
no_imgref : false ,
520
522
ostree_v2024_3 : ostree:: check_version ( 2024 , 3 ) ,
521
523
disable_gc : false ,
522
524
require_bootable : false ,
525
+ offline : false ,
523
526
imgref : imgref. clone ( ) ,
524
527
layer_progress : None ,
525
528
layer_byte_progress : None ,
@@ -538,6 +541,11 @@ impl ImageImporter {
538
541
self . no_imgref = true ;
539
542
}
540
543
544
+ /// Do not attempt to contact the network
545
+ pub fn set_offline ( & mut self ) {
546
+ self . offline = true ;
547
+ }
548
+
541
549
/// Require that the image has the bootable metadata field
542
550
pub fn require_bootable ( & mut self ) {
543
551
self . require_bootable = true ;
@@ -619,6 +627,7 @@ impl ImageImporter {
619
627
config : ImageConfiguration ,
620
628
previous_state : Option < Box < LayeredImageState > > ,
621
629
previous_imageid : Option < String > ,
630
+ proxy_img : OpenedImage ,
622
631
) -> Result < Box < PreparedImport > > {
623
632
let config_labels = super :: labels_of ( & config) ;
624
633
if self . require_bootable {
@@ -662,6 +671,7 @@ impl ImageImporter {
662
671
ostree_commit_layer : commit_layer,
663
672
layers : remaining_layers,
664
673
verify_text : None ,
674
+ proxy_img,
665
675
} ;
666
676
Ok ( Box :: new ( imp) )
667
677
}
@@ -681,30 +691,63 @@ impl ImageImporter {
681
691
_ => { }
682
692
}
683
693
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 ?;
685
729
let manifest_digest = Digest :: from_str ( & manifest_digest) ?;
686
730
let new_imageid = manifest. config ( ) . digest ( ) ;
687
731
688
732
// Query for previous stored state
689
733
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
+ } ;
706
749
707
- let config = self . proxy . fetch_config ( & self . proxy_img ) . await ?;
750
+ let config = self . proxy . fetch_config ( & proxy_img) . await ?;
708
751
709
752
// If there is a currently fetched image, cache the new pending manifest+config
710
753
// as detached commit metadata, so that future fetches can query it offline.
@@ -724,6 +767,7 @@ impl ImageImporter {
724
767
config,
725
768
previous_state,
726
769
previous_imageid,
770
+ proxy_img,
727
771
) ?;
728
772
Ok ( PrepareResult :: Ready ( imp) )
729
773
}
@@ -756,7 +800,7 @@ impl ImageImporter {
756
800
}
757
801
return Ok ( ( ) ) ;
758
802
} ;
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 ?;
760
804
for layer in import. ostree_layers . iter_mut ( ) {
761
805
if layer. commit . is_some ( ) {
762
806
continue ;
@@ -767,7 +811,7 @@ impl ImageImporter {
767
811
}
768
812
let ( blob, driver, media_type) = fetch_layer (
769
813
& self . proxy ,
770
- & self . proxy_img ,
814
+ & import . proxy_img ,
771
815
& import. manifest ,
772
816
& layer. layer ,
773
817
self . layer_byte_progress . as_ref ( ) ,
@@ -814,7 +858,7 @@ impl ImageImporter {
814
858
}
815
859
let ( blob, driver, media_type) = fetch_layer (
816
860
& self . proxy ,
817
- & self . proxy_img ,
861
+ & import . proxy_img ,
818
862
& import. manifest ,
819
863
& commit_layer. layer ,
820
864
self . layer_byte_progress . as_ref ( ) ,
@@ -874,7 +918,7 @@ impl ImageImporter {
874
918
self . unencapsulate_base ( & mut prep, true , false ) . await ?;
875
919
// TODO change the imageproxy API to ensure this happens automatically when
876
920
// the image reference is dropped
877
- self . proxy . close_image ( & self . proxy_img ) . await ?;
921
+ self . proxy . close_image ( & prep . proxy_img ) . await ?;
878
922
// SAFETY: We know we have a commit
879
923
let ostree_commit = prep. ostree_commit_layer . unwrap ( ) . commit . unwrap ( ) ;
880
924
let image_digest = prep. manifest_digest ;
@@ -899,9 +943,8 @@ impl ImageImporter {
899
943
// First download all layers for the base image (if necessary) - we need the SELinux policy
900
944
// there to label all following layers.
901
945
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 ?;
903
947
let proxy = self . proxy ;
904
- let proxy_img = self . proxy_img ;
905
948
let target_imgref = self . target_imgref . as_ref ( ) . unwrap_or ( & self . imgref ) ;
906
949
let base_commit = import
907
950
. ostree_commit_layer
@@ -935,7 +978,7 @@ impl ImageImporter {
935
978
}
936
979
let ( blob, driver, media_type) = super :: unencapsulate:: fetch_layer (
937
980
& proxy,
938
- & proxy_img,
981
+ & import . proxy_img ,
939
982
& import. manifest ,
940
983
& layer. layer ,
941
984
self . layer_byte_progress . as_ref ( ) ,
@@ -989,7 +1032,7 @@ impl ImageImporter {
989
1032
990
1033
// TODO change the imageproxy API to ensure this happens automatically when
991
1034
// the image reference is dropped
992
- proxy. close_image ( & proxy_img) . await ?;
1035
+ proxy. close_image ( & import . proxy_img ) . await ?;
993
1036
994
1037
// We're done with the proxy, make sure it didn't have any errors.
995
1038
proxy. finalize ( ) . await ?;
0 commit comments