Skip to content

Commit 940fe48

Browse files
committed
feat(push): allow pushing composed components to registry
Signed-off-by: Brian H <[email protected]>
1 parent 29ba553 commit 940fe48

File tree

4 files changed

+100
-31
lines changed

4 files changed

+100
-31
lines changed

Cargo.lock

Lines changed: 3 additions & 0 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: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ edition = { workspace = true }
88
anyhow = { workspace = true }
99
async-compression = { version = "0.4", features = ["gzip", "tokio"] }
1010
async-tar = "0.5"
11+
async-trait = { workspace = true }
1112
base64 = "0.22"
1213
chrono = "0.4"
1314
# Fork with updated auth to support ACR login
@@ -22,6 +23,8 @@ reqwest = "0.12"
2223
serde = { workspace = true }
2324
serde_json = { workspace = true }
2425
spin-common = { path = "../common" }
26+
spin-componentize = { path = "../componentize" }
27+
spin-compose = { path = "../compose" }
2528
spin-loader = { path = "../loader" }
2629
spin-locked-app = { path = "../locked-app" }
2730
tempfile = { workspace = true }

crates/oci/src/client.rs

Lines changed: 89 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ use std::collections::{BTreeMap, HashMap};
44
use std::path::{Path, PathBuf};
55

66
use anyhow::{bail, Context, Result};
7+
use async_trait::async_trait;
78
use docker_credential::DockerCredential;
89
use futures_util::future;
910
use futures_util::stream::{self, StreamExt, TryStreamExt};
@@ -18,7 +19,7 @@ use spin_common::ui::quoted_path;
1819
use spin_common::url::parse_file_url;
1920
use spin_loader::cache::Cache;
2021
use spin_loader::FilesMountStrategy;
21-
use spin_locked_app::locked::{ContentPath, ContentRef, LockedApp};
22+
use spin_locked_app::locked::{ContentPath, ContentRef, LockedApp, LockedComponentSource};
2223
use tokio::fs;
2324
use walkdir::WalkDir;
2425

@@ -119,6 +120,7 @@ impl Client {
119120
reference: impl AsRef<str>,
120121
annotations: Option<BTreeMap<String, String>>,
121122
infer_annotations: InferPredefinedAnnotations,
123+
skip_compose: bool,
122124
) -> Result<Option<String>> {
123125
let reference: Reference = reference
124126
.as_ref()
@@ -137,8 +139,15 @@ impl Client {
137139
)
138140
.await?;
139141

140-
self.push_locked_core(locked, auth, reference, annotations, infer_annotations)
141-
.await
142+
self.push_locked_core(
143+
locked,
144+
auth,
145+
reference,
146+
annotations,
147+
infer_annotations,
148+
skip_compose,
149+
)
150+
.await
142151
}
143152

144153
/// Push a Spin application to an OCI registry and return the digest (or None
@@ -149,15 +158,23 @@ impl Client {
149158
reference: impl AsRef<str>,
150159
annotations: Option<BTreeMap<String, String>>,
151160
infer_annotations: InferPredefinedAnnotations,
161+
skip_compose: bool,
152162
) -> Result<Option<String>> {
153163
let reference: Reference = reference
154164
.as_ref()
155165
.parse()
156166
.with_context(|| format!("cannot parse reference {}", reference.as_ref()))?;
157167
let auth = Self::auth(&reference).await?;
158168

159-
self.push_locked_core(locked, auth, reference, annotations, infer_annotations)
160-
.await
169+
self.push_locked_core(
170+
locked,
171+
auth,
172+
reference,
173+
annotations,
174+
infer_annotations,
175+
skip_compose,
176+
)
177+
.await
161178
}
162179

163180
/// Push a Spin application to an OCI registry and return the digest (or None
@@ -169,10 +186,11 @@ impl Client {
169186
reference: Reference,
170187
annotations: Option<BTreeMap<String, String>>,
171188
infer_annotations: InferPredefinedAnnotations,
189+
skip_compose: bool,
172190
) -> Result<Option<String>> {
173191
let mut locked_app = locked.clone();
174192
let mut layers = self
175-
.assemble_layers(&mut locked_app, AssemblyMode::Simple)
193+
.assemble_layers(&mut locked_app, AssemblyMode::Simple, skip_compose)
176194
.await
177195
.context("could not assemble layers for locked application")?;
178196

@@ -183,7 +201,7 @@ impl Client {
183201
{
184202
locked_app = locked.clone();
185203
layers = self
186-
.assemble_layers(&mut locked_app, AssemblyMode::Archive)
204+
.assemble_layers(&mut locked_app, AssemblyMode::Archive, skip_compose)
187205
.await
188206
.context("could not assemble archive layers for locked application")?;
189207
}
@@ -246,43 +264,57 @@ impl Client {
246264
&mut self,
247265
locked: &mut LockedApp,
248266
assembly_mode: AssemblyMode,
267+
skip_compose: bool,
249268
) -> Result<Vec<ImageLayer>> {
250269
let mut layers = Vec::new();
251270
let mut components = Vec::new();
252271
for mut c in locked.clone().components {
253-
// Add the wasm module for the component as layers.
254-
let source = c
255-
.clone()
256-
.source
257-
.content
258-
.source
259-
.context("component loaded from disk should contain a file source")?;
260-
261-
let source = parse_file_url(source.as_str())?;
262-
let layer = Self::wasm_layer(&source).await?;
263-
264-
// Update the module source with the content ref of the layer.
265-
c.source.content = self.content_ref_for_layer(&layer);
266-
267-
layers.push(layer);
268-
269-
let mut deps = BTreeMap::default();
270-
for (dep_name, mut dep) in c.dependencies {
271-
let source = dep
272+
if !skip_compose {
273+
let composed = spin_compose::compose(&ComponentSourceLoader, &c)
274+
.await
275+
.with_context(|| {
276+
format!("failed to resolve dependencies for component {:?}", c.id)
277+
})?;
278+
279+
let layer = ImageLayer::new(composed, WASM_LAYER_MEDIA_TYPE.to_string(), None);
280+
c.source.content = self.content_ref_for_layer(&layer);
281+
c.dependencies.clear();
282+
layers.push(layer);
283+
} else {
284+
// Add the wasm module for the component as layers.
285+
let source = c
286+
.clone()
272287
.source
273288
.content
274289
.source
275-
.context("dependency loaded from disk should contain a file source")?;
276-
let source = parse_file_url(source.as_str())?;
290+
.context("component loaded from disk should contain a file source")?;
277291

292+
let source = parse_file_url(source.as_str())?;
278293
let layer = Self::wasm_layer(&source).await?;
279294

280-
dep.source.content = self.content_ref_for_layer(&layer);
281-
deps.insert(dep_name, dep);
295+
// Update the module source with the content ref of the layer.
296+
c.source.content = self.content_ref_for_layer(&layer);
282297

283298
layers.push(layer);
299+
300+
let mut deps = BTreeMap::default();
301+
for (dep_name, mut dep) in c.dependencies {
302+
let source = dep
303+
.source
304+
.content
305+
.source
306+
.context("dependency loaded from disk should contain a file source")?;
307+
let source = parse_file_url(source.as_str())?;
308+
309+
let layer = Self::wasm_layer(&source).await?;
310+
311+
dep.source.content = self.content_ref_for_layer(&layer);
312+
deps.insert(dep_name, dep);
313+
314+
layers.push(layer);
315+
}
316+
c.dependencies = deps;
284317
}
285-
c.dependencies = deps;
286318

287319
let mut files = Vec::new();
288320
for f in c.files {
@@ -669,6 +701,32 @@ impl Client {
669701
}
670702
}
671703

704+
struct ComponentSourceLoader;
705+
706+
#[async_trait]
707+
impl spin_compose::ComponentSourceLoader for ComponentSourceLoader {
708+
async fn load_component_source(
709+
&self,
710+
source: &LockedComponentSource,
711+
) -> anyhow::Result<Vec<u8>> {
712+
let source = source
713+
.content
714+
.source
715+
.as_ref()
716+
.context("component loaded from disk should contain a file source")?;
717+
718+
let source = parse_file_url(source.as_str())?;
719+
720+
let bytes = fs::read(&source)
721+
.await
722+
.with_context(|| format!("cannot read wasm module {}", quoted_path(source)))?;
723+
724+
let component = spin_componentize::componentize_if_necessary(&bytes)?;
725+
726+
Ok(component.into())
727+
}
728+
}
729+
672730
/// Unpack contents of the provided archive layer, represented by bytes and its
673731
/// corresponding digest, into the provided cache.
674732
/// A temporary staging directory is created via tempfile::tempdir() to store

src/commands/registry.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,10 @@ pub struct Push {
4949
)]
5050
pub insecure: bool,
5151

52+
/// Skip composing the application's components before pushing it.
53+
#[clap(long = "skip-compose")]
54+
pub skip_compose: bool,
55+
5256
/// Specifies to perform `spin build` before pushing the application.
5357
#[clap(long, takes_value = false, env = ALWAYS_BUILD_ENV)]
5458
pub build: bool,
@@ -94,6 +98,7 @@ impl Push {
9498
&self.reference,
9599
annotations,
96100
InferPredefinedAnnotations::All,
101+
self.skip_compose,
97102
)
98103
.await?;
99104
match digest {

0 commit comments

Comments
 (0)