@@ -6,13 +6,13 @@ use anyhow::{bail, Context, Result};
66use docker_credential:: DockerCredential ;
77use futures_util:: future;
88use futures_util:: stream:: { self , StreamExt , TryStreamExt } ;
9- use oci_distribution:: token_cache:: RegistryTokenType ;
10- use oci_distribution:: RegistryOperation ;
119use oci_distribution:: {
1210 client:: { Config , ImageLayer } ,
13- manifest:: OciImageManifest ,
11+ errors:: OciDistributionError ,
12+ manifest:: { OciImageManifest , OCI_IMAGE_MEDIA_TYPE } ,
1413 secrets:: RegistryAuth ,
15- Reference ,
14+ token_cache:: RegistryTokenType ,
15+ Reference , RegistryOperation ,
1616} ;
1717use reqwest:: Url ;
1818use spin_common:: sha256;
@@ -25,15 +25,19 @@ use walkdir::WalkDir;
2525
2626use crate :: auth:: AuthConfig ;
2727
28- // TODO: the media types for application, wasm module, data and archive layer are not final.
28+ // TODO: the media types for application, data and archive layer are not final
2929/// Media type for a layer representing a locked Spin application configuration
3030pub const SPIN_APPLICATION_MEDIA_TYPE : & str = "application/vnd.fermyon.spin.application.v1+config" ;
31- // Note: we hope to use a canonical value defined upstream for this media type
32- const WASM_LAYER_MEDIA_TYPE : & str = "application/vnd.wasm.content.layer.v1+wasm" ;
3331/// Media type for a layer representing a generic data file used by a Spin application
3432pub const DATA_MEDIATYPE : & str = "application/vnd.wasm.content.layer.v1+data" ;
3533/// Media type for a layer representing a compressed archive of one or more files used by a Spin application
3634pub const ARCHIVE_MEDIATYPE : & str = "application/vnd.wasm.content.bundle.v1.tar+gzip" ;
35+ // Legacy wasm layer media type used by pre-2.0 versions of Spin
36+ const WASM_LAYER_MEDIA_TYPE_LEGACY : & str = "application/vnd.wasm.content.layer.v1+wasm" ;
37+
38+ // TODO: use canonical types defined upstream; see https://github.com/bytecodealliance/registry/pull/146
39+ const WASM_LAYER_MEDIA_TYPE : & str = "application/vnd.bytecodealliance.wasm.component.layer.v0+wasm" ;
40+ const COMPONENT_ARTIFACT_TYPE : & str = "application/vnd.bytecodealliance.component.v1+wasm" ;
3741
3842const CONFIG_FILE : & str = "config.json" ;
3943const LATEST_TAG : & str = "latest" ;
@@ -164,12 +168,24 @@ impl Client {
164168 locked. components = components;
165169 locked. metadata . remove ( "origin" ) ;
166170
171+ // Push layer for locked spin application config
172+ let locked_config_layer = ImageLayer :: new (
173+ serde_json:: to_vec ( & locked) . context ( "could not serialize locked config" ) ?,
174+ SPIN_APPLICATION_MEDIA_TYPE . to_string ( ) ,
175+ None ,
176+ ) ;
177+ layers. push ( locked_config_layer) ;
178+
167179 let oci_config = Config {
180+ // TODO: now that the locked config bytes are pushed as a layer, what should data here be?
181+ // Keeping as locked config bytes would make it feasible for older Spin clients to pull/run
182+ // apps published by newer Spin clients
168183 data : serde_json:: to_vec ( & locked) ?,
169- media_type : SPIN_APPLICATION_MEDIA_TYPE . to_string ( ) ,
184+ media_type : OCI_IMAGE_MEDIA_TYPE . to_string ( ) ,
170185 annotations : None ,
171186 } ;
172- let manifest = OciImageManifest :: build ( & layers, & oci_config, None ) ;
187+ let mut manifest = OciImageManifest :: build ( & layers, & oci_config, None ) ;
188+ manifest. artifact_type = Some ( COMPONENT_ARTIFACT_TYPE . to_string ( ) ) ;
173189 let response = self
174190 . oci
175191 . push ( & reference, & layers, oci_config, & auth, Some ( manifest) )
@@ -275,16 +291,17 @@ impl Client {
275291 let m = self . manifest_path ( & reference. to_string ( ) ) . await ?;
276292 fs:: write ( & m, & manifest_json) . await ?;
277293
294+ // Older published Spin apps feature the locked app config *as* the OCI manifest config layer,
295+ // while newer versions publish the locked app config as a generic layer alongside others.
296+ // Assume that these bytes may represent the locked app config and write it as such.
297+ // TODO: update this assumption if we change the data we write to the OCI manifest config layer.
278298 let mut cfg_bytes = Vec :: new ( ) ;
279299 self . oci
280300 . pull_blob ( & reference, & manifest. config . digest , & mut cfg_bytes)
281301 . await ?;
282- let cfg = std:: str:: from_utf8 ( & cfg_bytes) ?;
283- tracing:: debug!( "Pulled config: {}" , cfg) ;
284-
285- // Write the config object in `<cache_root>/registry/oci/manifests/repository:<tag_or_latest>/config.json`
286- let c = self . lockfile_path ( & reference. to_string ( ) ) . await ?;
287- fs:: write ( & c, & cfg) . await ?;
302+ self . write_locked_app_config ( & reference. to_string ( ) , & cfg_bytes)
303+ . await
304+ . context ( "unable to write locked app config to cache" ) ?;
288305
289306 // If a layer is a Wasm module, write it in the Wasm directory.
290307 // Otherwise, write it in the data directory (after unpacking if archive layer)
@@ -298,6 +315,7 @@ impl Client {
298315 || this. cache . data_file ( & layer. digest ) . is_ok ( )
299316 {
300317 tracing:: debug!( "Layer {} already exists in cache" , & layer. digest) ;
318+ <<<<<<< HEAD
301319 return anyhow:: Ok ( ( ) ) ;
302320 }
303321
@@ -315,6 +333,44 @@ impl Client {
315333 }
316334 _ => {
317335 this. cache . write_data ( & bytes, & layer. digest ) . await ?;
336+ =======
337+ } else {
338+ tracing : : debug ! ( "Pulling layer {}" , & layer. digest) ;
339+ let mut bytes = Vec :: new ( ) ;
340+ match this
341+ . oci
342+ . pull_blob ( & reference, & layer. digest , & mut bytes)
343+ . await
344+ {
345+ Err ( e) => return Err ( e) ,
346+ _ => match layer. media_type . as_str ( ) {
347+ // If the locked app config is present as a separate layer, this should take precedence
348+ SPIN_APPLICATION_MEDIA_TYPE => {
349+ if let Err ( e) = this. write_locked_app_config ( & reference. to_string ( ) , & bytes)
350+ . await
351+ {
352+ return Err ( OciDistributionError :: GenericError (
353+ Some ( format ! ( "unable to write locked app config to cache: {}" , e) )
354+ ) ) ;
355+ }
356+ }
357+ WASM_LAYER_MEDIA_TYPE | WASM_LAYER_MEDIA_TYPE_LEGACY => {
358+ let _ = this. cache . write_wasm ( & bytes, & layer. digest ) . await ;
359+ }
360+ ARCHIVE_MEDIATYPE => {
361+ if let Err ( e) =
362+ this. unpack_archive_layer ( & bytes, & layer. digest ) . await
363+ {
364+ return Err ( OciDistributionError :: GenericError ( Some (
365+ format ! ( "unable to unpack archive layer with digest {}: {}" , & layer. digest, e) ,
366+ ) ) ) ;
367+ }
368+ }
369+ _ => {
370+ let _ = this. cache . write_data ( & bytes, & layer. digest ) . await ;
371+ }
372+ } ,
373+ >>>>>>> 942 a1782 ( feat( oci) : manifest/config updates to support containerd)
318374 }
319375 }
320376 Ok ( ( ) )
@@ -373,6 +429,19 @@ impl Client {
373429 Ok ( p. join( CONFIG_FILE ) )
374430 }
375431
432+ /// Write the config object in `<cache_root>/registry/oci/manifests/repository:<tag_or_latest>/config.json`
433+ async fn write_locked_app_config(
434+ & self ,
435+ reference: impl AsRef < str > ,
436+ bytes: impl AsRef < [ u8 ] > ,
437+ ) -> Result < ( ) > {
438+ let cfg = std:: str:: from_utf8( bytes. as_ref( ) ) ?;
439+ tracing:: debug!( "Pulled config: {}" , cfg) ;
440+
441+ let c = self . lockfile_path( reference) . await ?;
442+ fs:: write( & c, & cfg) . await . map_err( anyhow:: Error :: from)
443+ }
444+
376445 /// Create a new wasm layer based on a file.
377446 async fn wasm_layer( file: & Path ) -> Result < ImageLayer > {
378447 tracing:: log:: trace!( "Reading wasm module from {:?}" , file) ;
0 commit comments