@@ -11,7 +11,7 @@ use oci_spec::image::{
1111} ;
1212use olpc_cjson:: CanonicalFormatter ;
1313use openssl:: hash:: { Hasher , MessageDigest } ;
14- use serde:: Serialize ;
14+ use serde:: { Deserialize , Serialize } ;
1515use std:: collections:: { HashMap , HashSet } ;
1616use std:: fmt:: Debug ;
1717use std:: fs:: File ;
@@ -57,6 +57,9 @@ pub enum Error {
5757 #[ error( "Cannot find the Image Index (index.json)" ) ]
5858 /// Returned when the OCI Image Index (index.json) is missing
5959 MissingImageIndex ,
60+ #[ error( "Unexpected media type {media_type}" ) ]
61+ /// Returned when the OCI Image Index (index.json) is missing
62+ UnexpectedMediaType { media_type : MediaType } ,
6063 #[ error( "error" ) ]
6164 /// An unknown other error
6265 Other ( Box < str > ) ,
@@ -77,6 +80,11 @@ impl From<openssl::error::ErrorStack> for Error {
7780 }
7881}
7982
83+ // This is intentionally an empty struct
84+ // See https://github.com/opencontainers/image-spec/blob/main/manifest.md#guidance-for-an-empty-descriptor
85+ #[ derive( Serialize , Deserialize ) ]
86+ struct EmptyDescriptor { }
87+
8088/// Completed blob metadata
8189#[ derive( Debug ) ]
8290pub struct Blob {
@@ -161,32 +169,6 @@ pub struct OciDir {
161169 blobs_dir : Dir ,
162170}
163171
164- /// Create a dummy config descriptor.
165- /// Our API right now always mutates a manifest, which means we need
166- /// a "valid" manifest, which requires a "valid" config descriptor.
167- /// This digest should never actually be used for anything.
168- fn empty_config_descriptor ( ) -> oci_image:: Descriptor {
169- oci_image:: DescriptorBuilder :: default ( )
170- . media_type ( MediaType :: ImageConfig )
171- . size ( 7023u64 )
172- . digest (
173- Sha256Digest :: from_str (
174- "a5b2b2c507a0944348e0303114d8d93aaaa081732b86451d9bce1f432a537bc7" ,
175- )
176- . unwrap ( ) ,
177- )
178- . build ( )
179- . unwrap ( )
180- }
181-
182- /// Generate a "valid" empty manifest. See above.
183- pub fn new_empty_manifest ( ) -> oci_image:: ImageManifestBuilder {
184- oci_image:: ImageManifestBuilder :: default ( )
185- . schema_version ( oci_image:: SCHEMA_VERSION )
186- . config ( empty_config_descriptor ( ) )
187- . layers ( Vec :: new ( ) )
188- }
189-
190172fn sha256_of_descriptor ( desc : & Descriptor ) -> Result < & str > {
191173 desc. as_digest_sha256 ( )
192174 . ok_or_else ( || Error :: UnsupportedDigestAlgorithm {
@@ -195,6 +177,40 @@ fn sha256_of_descriptor(desc: &Descriptor) -> Result<&str> {
195177}
196178
197179impl OciDir {
180+ /// Create an empty config descriptor.
181+ /// See https://github.com/opencontainers/image-spec/blob/main/manifest.md#guidance-for-an-empty-descriptor
182+ /// Our API right now always mutates a manifest, which means we need
183+ /// a "valid" manifest, which requires a "valid" config descriptor.
184+ fn empty_config_descriptor ( & self ) -> Result < oci_image:: Descriptor > {
185+ let empty_descriptor = oci_image:: DescriptorBuilder :: default ( )
186+ . media_type ( MediaType :: EmptyJSON )
187+ . size ( 2_u32 )
188+ . digest ( Sha256Digest :: from_str (
189+ "44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a" ,
190+ ) ?)
191+ . data ( "e30=" )
192+ . build ( ) ?;
193+
194+ if !self
195+ . dir
196+ . exists ( OciDir :: parse_descriptor_to_path ( & empty_descriptor) ?)
197+ {
198+ let mut blob = self . create_blob ( ) ?;
199+ serde_json:: to_writer ( & mut blob, & EmptyDescriptor { } ) ?;
200+ blob. complete_verified_as ( & empty_descriptor) ?;
201+ }
202+
203+ Ok ( empty_descriptor)
204+ }
205+
206+ /// Generate a valid empty manifest. See above.
207+ pub fn new_empty_manifest ( & self ) -> Result < oci_image:: ImageManifestBuilder > {
208+ Ok ( oci_image:: ImageManifestBuilder :: default ( )
209+ . schema_version ( oci_image:: SCHEMA_VERSION )
210+ . config ( self . empty_config_descriptor ( ) ?)
211+ . layers ( Vec :: new ( ) ) )
212+ }
213+
198214 /// Open the OCI directory at the target path; if it does not already
199215 /// have the standard OCI metadata, it is created.
200216 pub fn ensure ( dir : Dir ) -> Result < Self > {
@@ -548,7 +564,19 @@ impl OciDir {
548564 validated : & mut HashSet < Box < str > > ,
549565 ) -> Result < ( ) > {
550566 let config_digest = sha256_of_descriptor ( manifest. config ( ) ) ?;
551- let _: ImageConfiguration = self . read_json_blob ( manifest. config ( ) ) ?;
567+ match manifest. config ( ) . media_type ( ) {
568+ MediaType :: ImageConfig => {
569+ let _: ImageConfiguration = self . read_json_blob ( manifest. config ( ) ) ?;
570+ }
571+ MediaType :: EmptyJSON => {
572+ let _: EmptyDescriptor = self . read_json_blob ( manifest. config ( ) ) ?;
573+ }
574+ media_type => {
575+ return Err ( Error :: UnexpectedMediaType {
576+ media_type : media_type. clone ( ) ,
577+ } )
578+ }
579+ }
552580 validated. insert ( config_digest. into ( ) ) ;
553581 for layer in manifest. layers ( ) {
554582 let expected = sha256_of_descriptor ( layer) ?;
@@ -833,7 +861,7 @@ mod tests {
833861 ) )
834862 . unwrap( ) ) ;
835863
836- let mut manifest = new_empty_manifest ( ) . build ( ) . unwrap ( ) ;
864+ let mut manifest = w . new_empty_manifest ( ) ? . build ( ) ? ;
837865 let mut config = oci_image:: ImageConfigurationBuilder :: default ( )
838866 . build ( )
839867 . unwrap ( ) ;
@@ -877,7 +905,7 @@ mod tests {
877905 let mut layerw = w. create_gzip_layer ( None ) ?;
878906 layerw. write_all ( b"pretend this is an updated tarball" ) ?;
879907 let root_layer = layerw. complete ( ) ?;
880- let mut manifest = new_empty_manifest ( ) . build ( ) . unwrap ( ) ;
908+ let mut manifest = w . new_empty_manifest ( ) ? . build ( ) ? ;
881909 let mut config = oci_image:: ImageConfigurationBuilder :: default ( )
882910 . build ( )
883911 . unwrap ( ) ;
@@ -942,4 +970,19 @@ mod tests {
942970
943971 Ok ( ( ) )
944972 }
973+
974+ #[ test]
975+ fn test_new_empty_manifest ( ) -> Result < ( ) > {
976+ let td = cap_tempfile:: tempdir ( cap_std:: ambient_authority ( ) ) ?;
977+ let w = OciDir :: ensure ( td. try_clone ( ) ?) ?;
978+
979+ let manifest = w. new_empty_manifest ( ) ?. build ( ) ?;
980+ let desc: Descriptor =
981+ w. insert_manifest ( manifest, Some ( "latest" ) , oci_image:: Platform :: default ( ) ) ?;
982+ assert ! ( w. has_manifest( & desc) . unwrap( ) ) ;
983+
984+ // We expect two validated blobs: the manifest and the image configuration
985+ assert_eq ! ( w. fsck( ) ?, 2 ) ;
986+ Ok ( ( ) )
987+ }
945988}
0 commit comments