@@ -6,13 +6,9 @@ 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:: {
12- client:: { Config , ImageLayer } ,
13- manifest:: OciImageManifest ,
14- secrets:: RegistryAuth ,
15- Reference ,
10+ client:: ImageLayer , config:: ConfigFile , manifest:: OciImageManifest , secrets:: RegistryAuth ,
11+ token_cache:: RegistryTokenType , Reference , RegistryOperation ,
1612} ;
1713use reqwest:: Url ;
1814use spin_common:: sha256;
@@ -25,15 +21,15 @@ use walkdir::WalkDir;
2521
2622use crate :: auth:: AuthConfig ;
2723
28- // TODO: the media types for application, wasm module, data and archive layer are not final.
24+ // TODO: the media types for application, data and archive layer are not final
2925/// Media type for a layer representing a locked Spin application configuration
3026pub 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" ;
3327/// Media type for a layer representing a generic data file used by a Spin application
3428pub const DATA_MEDIATYPE : & str = "application/vnd.wasm.content.layer.v1+data" ;
3529/// Media type for a layer representing a compressed archive of one or more files used by a Spin application
3630pub const ARCHIVE_MEDIATYPE : & str = "application/vnd.wasm.content.bundle.v1.tar+gzip" ;
31+ // Note: this will be updated with a canonical value once defined upstream
32+ const WASM_LAYER_MEDIA_TYPE : & str = "application/vnd.wasm.content.layer.v1+wasm" ;
3733
3834const CONFIG_FILE : & str = "config.json" ;
3935const LATEST_TAG : & str = "latest" ;
@@ -164,12 +160,27 @@ impl Client {
164160 locked. components = components;
165161 locked. metadata . remove ( "origin" ) ;
166162
167- let oci_config = Config {
168- data : serde_json:: to_vec ( & locked) ?,
169- media_type : SPIN_APPLICATION_MEDIA_TYPE . to_string ( ) ,
170- annotations : None ,
163+ // Push layer for locked spin application config
164+ let locked_config_layer = ImageLayer :: new (
165+ serde_json:: to_vec ( & locked) . context ( "could not serialize locked config" ) ?,
166+ SPIN_APPLICATION_MEDIA_TYPE . to_string ( ) ,
167+ None ,
168+ ) ;
169+ layers. push ( locked_config_layer) ;
170+
171+ // Construct empty/default OCI config file. Data may be parsed according to
172+ // the expected config structure per the image spec, so we want to ensure it conforms.
173+ // (See https://github.com/opencontainers/image-spec/blob/main/config.md)
174+ // TODO: Explore adding data applicable to the Spin app being published.
175+ let oci_config_file = ConfigFile {
176+ architecture : oci_distribution:: config:: Architecture :: Wasm ,
177+ os : oci_distribution:: config:: Os :: Wasip1 ,
178+ ..Default :: default ( )
171179 } ;
180+ let oci_config =
181+ oci_distribution:: client:: Config :: oci_v1_from_config_file ( oci_config_file, None ) ?;
172182 let manifest = OciImageManifest :: build ( & layers, & oci_config, None ) ;
183+
173184 let response = self
174185 . oci
175186 . push ( & reference, & layers, oci_config, & auth, Some ( manifest) )
@@ -275,16 +286,16 @@ impl Client {
275286 let m = self . manifest_path ( & reference. to_string ( ) ) . await ?;
276287 fs:: write ( & m, & manifest_json) . await ?;
277288
289+ // Older published Spin apps feature the locked app config *as* the OCI manifest config layer,
290+ // while newer versions publish the locked app config as a generic layer alongside others.
291+ // Assume that these bytes may represent the locked app config and write it as such.
278292 let mut cfg_bytes = Vec :: new ( ) ;
279293 self . oci
280294 . pull_blob ( & reference, & manifest. config . digest , & mut cfg_bytes)
281295 . 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 ?;
296+ self . write_locked_app_config ( & reference. to_string ( ) , & cfg_bytes)
297+ . await
298+ . context ( "unable to write locked app config to cache" ) ?;
288299
289300 // If a layer is a Wasm module, write it in the Wasm directory.
290301 // Otherwise, write it in the data directory (after unpacking if archive layer)
@@ -307,6 +318,11 @@ impl Client {
307318 . pull_blob ( & reference, & layer. digest , & mut bytes)
308319 . await ?;
309320 match layer. media_type . as_str ( ) {
321+ SPIN_APPLICATION_MEDIA_TYPE => {
322+ this. write_locked_app_config ( & reference. to_string ( ) , & bytes)
323+ . await
324+ . with_context ( || "unable to write locked app config to cache" ) ?;
325+ }
310326 WASM_LAYER_MEDIA_TYPE => {
311327 this. cache . write_wasm ( & bytes, & layer. digest ) . await ?;
312328 }
@@ -373,6 +389,19 @@ impl Client {
373389 Ok ( p. join ( CONFIG_FILE ) )
374390 }
375391
392+ /// Write the config object in `<cache_root>/registry/oci/manifests/repository:<tag_or_latest>/config.json`
393+ async fn write_locked_app_config (
394+ & self ,
395+ reference : impl AsRef < str > ,
396+ bytes : impl AsRef < [ u8 ] > ,
397+ ) -> Result < ( ) > {
398+ let cfg = std:: str:: from_utf8 ( bytes. as_ref ( ) ) ?;
399+ tracing:: debug!( "Pulled config: {}" , cfg) ;
400+
401+ let c = self . lockfile_path ( reference) . await ?;
402+ fs:: write ( & c, & cfg) . await . map_err ( anyhow:: Error :: from)
403+ }
404+
376405 /// Create a new wasm layer based on a file.
377406 async fn wasm_layer ( file : & Path ) -> Result < ImageLayer > {
378407 tracing:: log:: trace!( "Reading wasm module from {:?}" , file) ;
0 commit comments