Skip to content

Commit 2c7badb

Browse files
authored
Merge pull request #1882 from vdice/feat/oci-config-updates
feat(oci): manifest/config updates to support containerd
2 parents fa975e8 + 464214b commit 2c7badb

File tree

3 files changed

+51
-23
lines changed

3 files changed

+51
-23
lines changed

Cargo.lock

Lines changed: 2 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/oci/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ dkregistry = { git = "https://github.com/camallo/dkregistry-rs", rev = "37acecb4
1414
docker_credential = "1.0"
1515
dirs = "4.0"
1616
futures-util = "0.3"
17-
oci-distribution = { git = "https://github.com/fermyon/oci-distribution", rev = "05022618d78feef9b99f20b5da8fd6def6bb80d2" }
17+
oci-distribution = { git = "https://github.com/fermyon/oci-distribution", rev = "63cbb0925775e0c9c870195cad1d50ac8707a264" }
1818
reqwest = "0.11"
1919
serde = { version = "1.0", features = ["derive"] }
2020
serde_json = "1.0"

crates/oci/src/client.rs

Lines changed: 48 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,9 @@ use anyhow::{bail, Context, Result};
66
use docker_credential::DockerCredential;
77
use futures_util::future;
88
use futures_util::stream::{self, StreamExt, TryStreamExt};
9-
use oci_distribution::token_cache::RegistryTokenType;
10-
use oci_distribution::RegistryOperation;
119
use 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
};
1713
use reqwest::Url;
1814
use spin_common::sha256;
@@ -25,15 +21,15 @@ use walkdir::WalkDir;
2521

2622
use 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
3026
pub 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
3428
pub 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
3630
pub 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

3834
const CONFIG_FILE: &str = "config.json";
3935
const 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

Comments
 (0)