88use super :: * ;
99use crate :: chunking:: { self , Chunk } ;
1010use crate :: container:: Decompressor ;
11+ use crate :: json:: JsonOrderedSerialize ;
1112use crate :: logging:: system_repo_journal_print;
1213use crate :: refescape;
1314use crate :: sysroot:: SysrootLock ;
@@ -28,7 +29,7 @@ use oci_spec::image::{
2829} ;
2930use ostree:: prelude:: { Cast , FileEnumeratorExt , FileExt , ToVariant } ;
3031use ostree:: { gio, glib} ;
31- use std:: collections:: { BTreeMap , BTreeSet , HashMap } ;
32+ use std:: collections:: { BTreeMap , BTreeSet } ;
3233use std:: fmt:: Write as _;
3334use std:: iter:: FromIterator ;
3435use std:: num:: NonZeroUsize ;
@@ -59,7 +60,7 @@ const META_CONFIG: &str = "ostree.container.image-config";
5960/// Value of type `a{sa{su}}` containing number of filtered out files
6061pub const META_FILTERED : & str = "ostree.tar-filtered" ;
6162/// The type used to store content filtering information with `META_FILTERED`.
62- pub type MetaFilteredData = HashMap < String , HashMap < String , u32 > > ;
63+ pub type MetaFilteredData = BTreeMap < String , BTreeMap < String , u32 > > ;
6364
6465/// The ref prefixes which point to ostree deployments. (TODO: Add an official API for this)
6566const OSTREE_BASE_DEPLOYMENT_REFS : & [ & str ] = & [ "ostree/0" , "ostree/1" ] ;
@@ -595,9 +596,13 @@ impl ImageImporter {
595596 Self :: CACHED_KEY_MANIFEST_DIGEST ,
596597 manifest_digest. to_string ( ) ,
597598 ) ;
598- let cached_manifest = serde_json:: to_string ( manifest) . context ( "Serializing manifest" ) ?;
599+ let cached_manifest = manifest
600+ . to_json_canonical_string ( )
601+ . context ( "Serializing manifest" ) ?;
599602 commitmeta. insert ( Self :: CACHED_KEY_MANIFEST , cached_manifest) ;
600- let cached_config = serde_json:: to_string ( config) . context ( "Serializing config" ) ?;
603+ let cached_config = config
604+ . to_json_canonical_string ( )
605+ . context ( "Serializing config" ) ?;
601606 commitmeta. insert ( Self :: CACHED_KEY_CONFIG , cached_config) ;
602607 let commitmeta = commitmeta. to_variant ( ) ;
603608 // Clone these to move into blocking method
@@ -921,7 +926,7 @@ impl ImageImporter {
921926 let ostree_ref = ref_for_image ( & target_imgref. imgref ) ?;
922927
923928 let mut layer_commits = Vec :: new ( ) ;
924- let mut layer_filtered_content: MetaFilteredData = HashMap :: new ( ) ;
929+ let mut layer_filtered_content: MetaFilteredData = BTreeMap :: new ( ) ;
925930 let have_derived_layers = !import. layers . is_empty ( ) ;
926931 tracing:: debug!( "Processing layers: {}" , import. layers. len( ) ) ;
927932 for layer in import. layers {
@@ -963,7 +968,7 @@ impl ImageImporter {
963968 . with_context ( || format ! ( "Parsing layer blob {}" , layer. layer. digest( ) ) ) ?;
964969 tracing:: debug!( "Imported layer: {}" , r. commit. as_str( ) ) ;
965970 layer_commits. push ( r. commit ) ;
966- let filtered_owned = HashMap :: from_iter ( r. filtered . clone ( ) ) ;
971+ let filtered_owned = BTreeMap :: from_iter ( r. filtered . clone ( ) ) ;
967972 if let Some ( ( filtered, n_rest) ) = bootc_utils:: collect_until (
968973 r. filtered . iter ( ) ,
969974 const { NonZeroUsize :: new ( 5 ) . unwrap ( ) } ,
@@ -999,9 +1004,10 @@ impl ImageImporter {
9991004 let _ = self . layer_byte_progress . take ( ) ;
10001005 let _ = self . layer_progress . take ( ) ;
10011006
1002- let serialized_manifest = serde_json:: to_string ( & import. manifest ) ?;
1003- let serialized_config = serde_json:: to_string ( & import. config ) ?;
1004- let mut metadata = HashMap :: new ( ) ;
1007+ // Use serde_json::Value to make output reproducible
1008+ let serialized_manifest = import. manifest . to_json_canonical_string ( ) ?;
1009+ let serialized_config = import. config . to_json_canonical_string ( ) ?;
1010+ let mut metadata = BTreeMap :: new ( ) ;
10051011 metadata. insert (
10061012 META_MANIFEST_DIGEST ,
10071013 import. manifest_digest . to_string ( ) . to_variant ( ) ,
0 commit comments