@@ -358,6 +358,25 @@ impl OciDir {
358358 annotations : Option < impl Into < HashMap < String , String > > > ,
359359 description : & str ,
360360 created : chrono:: DateTime < chrono:: Utc > ,
361+ ) {
362+ let history = oci_image:: HistoryBuilder :: default ( )
363+ . created ( created. to_rfc3339_opts ( chrono:: SecondsFormat :: Secs , true ) )
364+ . created_by ( description. to_string ( ) )
365+ . build ( )
366+ . unwrap ( ) ;
367+ self . push_layer_with_history_annotated ( manifest, config, layer, annotations, Some ( history) ) ;
368+ }
369+
370+ /// Add a layer to the top of the image stack with optional annotations and desired history entry.
371+ ///
372+ /// This is otherwise equivalent to [`Self::push_layer_annotated`].
373+ pub fn push_layer_with_history_annotated (
374+ & self ,
375+ manifest : & mut oci_image:: ImageManifest ,
376+ config : & mut oci_image:: ImageConfiguration ,
377+ layer : Layer ,
378+ annotations : Option < impl Into < HashMap < String , String > > > ,
379+ history : Option < oci_image:: History > ,
361380 ) {
362381 let mut builder = layer. descriptor ( ) ;
363382 if let Some ( annotations) = annotations {
@@ -370,12 +389,26 @@ impl OciDir {
370389 . diff_ids_mut ( )
371390 . push ( layer. uncompressed_sha256_as_digest ( ) . to_string ( ) ) ;
372391 config. set_rootfs ( rootfs) ;
373- let h = oci_image:: HistoryBuilder :: default ( )
374- . created ( created. to_rfc3339_opts ( chrono:: SecondsFormat :: Secs , true ) )
375- . created_by ( description. to_string ( ) )
376- . build ( )
377- . unwrap ( ) ;
378- config. history_mut ( ) . push ( h) ;
392+ let history = if let Some ( history) = history {
393+ history
394+ } else {
395+ oci_image:: HistoryBuilder :: default ( ) . build ( ) . unwrap ( )
396+ } ;
397+ config. history_mut ( ) . push ( history) ;
398+ }
399+
400+ /// Add a layer to the top of the image stack with desired history entry.
401+ ///
402+ /// This is otherwise equivalent to [`Self::push_layer`].
403+ pub fn push_layer_with_history (
404+ & self ,
405+ manifest : & mut oci_image:: ImageManifest ,
406+ config : & mut oci_image:: ImageConfiguration ,
407+ layer : Layer ,
408+ history : Option < oci_image:: History > ,
409+ ) {
410+ let annotations: Option < HashMap < _ , _ > > = None ;
411+ self . push_layer_with_history_annotated ( manifest, config, layer, annotations, history) ;
379412 }
380413
381414 fn parse_descriptor_to_path ( desc : & oci_spec:: image:: Descriptor ) -> Result < PathBuf > {
@@ -797,6 +830,7 @@ where
797830#[ cfg( test) ]
798831mod tests {
799832 use cap_std:: fs:: OpenOptions ;
833+ use oci_spec:: image:: HistoryBuilder ;
800834
801835 use super :: * ;
802836
@@ -867,6 +901,16 @@ mod tests {
867901 . unwrap ( ) ;
868902 let annotations: Option < HashMap < String , String > > = None ;
869903 w. push_layer ( & mut manifest, & mut config, root_layer, "root" , annotations) ;
904+ {
905+ let history = config. history ( ) . first ( ) . unwrap ( ) ;
906+ assert_eq ! ( history. created_by( ) . as_ref( ) . unwrap( ) , "root" ) ;
907+ let created = history. created ( ) . as_deref ( ) . unwrap ( ) ;
908+ let ts = chrono:: DateTime :: parse_from_rfc3339 ( created)
909+ . unwrap ( )
910+ . to_utc ( ) ;
911+ let now = chrono:: offset:: Utc :: now ( ) ;
912+ assert_eq ! ( now. years_since( ts) . unwrap( ) , 0 ) ;
913+ }
870914 let config = w. write_config ( config) ?;
871915 manifest. set_config ( config) ;
872916 w. replace_with_single_manifest ( manifest. clone ( ) , oci_image:: Platform :: default ( ) ) ?;
@@ -985,4 +1029,30 @@ mod tests {
9851029 assert_eq ! ( w. fsck( ) ?, 2 ) ;
9861030 Ok ( ( ) )
9871031 }
1032+
1033+ #[ test]
1034+ fn test_push_layer_with_history ( ) -> Result < ( ) > {
1035+ let td = cap_tempfile:: tempdir ( cap_std:: ambient_authority ( ) ) ?;
1036+ let w = OciDir :: ensure ( td. try_clone ( ) ?) ?;
1037+
1038+ let mut manifest = w. new_empty_manifest ( ) ?. build ( ) ?;
1039+ let mut config = oci_image:: ImageConfigurationBuilder :: default ( )
1040+ . build ( )
1041+ . unwrap ( ) ;
1042+ let mut layerw = w. create_gzip_layer ( None ) ?;
1043+ layerw. write_all ( b"pretend this is a tarball" ) ?;
1044+ let root_layer = layerw. complete ( ) ?;
1045+
1046+ let history = HistoryBuilder :: default ( )
1047+ . created_by ( "/bin/pretend-tar" )
1048+ . build ( )
1049+ . unwrap ( ) ;
1050+ w. push_layer_with_history ( & mut manifest, & mut config, root_layer, Some ( history) ) ;
1051+ {
1052+ let history = config. history ( ) . first ( ) . unwrap ( ) ;
1053+ assert_eq ! ( history. created_by( ) . as_deref( ) . unwrap( ) , "/bin/pretend-tar" ) ;
1054+ assert_eq ! ( history. created( ) . as_ref( ) , None ) ;
1055+ }
1056+ Ok ( ( ) )
1057+ }
9881058}
0 commit comments