Skip to content

Commit 151d604

Browse files
authored
Merge pull request #456 from itowlson/wasm-bindle-parcel-source
Allow parcel ref in component source
2 parents cb3c071 + 6afcc55 commit 151d604

File tree

10 files changed

+252
-63
lines changed

10 files changed

+252
-63
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ wasmtime = "0.34"
4444

4545
[dev-dependencies]
4646
hyper = { version = "0.14", features = [ "full" ] }
47+
sha2 = "0.10.1"
4748

4849
[build-dependencies]
4950
cargo-target-dep = { git = "https://github.com/fermyon/cargo-target-dep", rev = "b7b1989fe0984c0f7c4966398304c6538e52fe49" }
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
use std::sync::Arc;
2+
3+
use bindle::client::{
4+
tokens::{HttpBasic, NoToken, TokenManager},
5+
Client, ClientBuilder,
6+
};
7+
8+
/// BindleConnectionInfo holds the details of a connection to a
9+
/// Bindle server, including url, insecure configuration and an
10+
/// auth token manager
11+
#[derive(Clone)]
12+
pub struct BindleConnectionInfo {
13+
base_url: String,
14+
allow_insecure: bool,
15+
token_manager: AnyAuth,
16+
}
17+
18+
impl BindleConnectionInfo {
19+
/// Generates a new BindleConnectionInfo instance using the provided
20+
/// base_url, allow_insecure setting and optional username and password
21+
/// for basic http auth
22+
pub fn new<I: Into<String>>(
23+
base_url: I,
24+
allow_insecure: bool,
25+
username: Option<String>,
26+
password: Option<String>,
27+
) -> Self {
28+
let token_manager: Box<dyn TokenManager + Send + Sync> = match (username, password) {
29+
(Some(u), Some(p)) => Box::new(HttpBasic::new(&u, &p)),
30+
_ => Box::new(NoToken::default()),
31+
};
32+
33+
Self {
34+
base_url: base_url.into(),
35+
allow_insecure,
36+
token_manager: AnyAuth {
37+
token_manager: Arc::new(token_manager),
38+
},
39+
}
40+
}
41+
42+
/// Returns a client based on this instance's configuration
43+
pub fn client(&self) -> bindle::client::Result<Client<AnyAuth>> {
44+
let builder = ClientBuilder::default()
45+
.http2_prior_knowledge(false)
46+
.danger_accept_invalid_certs(self.allow_insecure);
47+
builder.build(&self.base_url, self.token_manager.clone())
48+
}
49+
}
50+
51+
/// AnyAuth wraps an authentication token manager which applies
52+
/// the appropriate auth header per its configuration
53+
#[derive(Clone)]
54+
pub struct AnyAuth {
55+
token_manager: Arc<Box<dyn TokenManager + Send + Sync>>,
56+
}
57+
58+
#[async_trait::async_trait]
59+
impl TokenManager for AnyAuth {
60+
async fn apply_auth_header(
61+
&self,
62+
builder: reqwest::RequestBuilder,
63+
) -> bindle::client::Result<reqwest::RequestBuilder> {
64+
self.token_manager.apply_auth_header(builder).await
65+
}
66+
}

crates/loader/src/bindle/mod.rs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,26 +6,26 @@
66
mod assets;
77
/// Configuration representation for a Spin application in Bindle.
88
pub mod config;
9+
mod connection;
910
/// Bindle helper functions.
1011
mod utils;
1112

1213
use crate::bindle::{
1314
config::{RawAppManifest, RawComponentManifest},
14-
utils::{find_manifest, parcels_in_group, BindleReader},
15+
utils::{find_manifest, parcels_in_group},
1516
};
1617
use anyhow::{anyhow, Context, Result};
17-
use bindle::{
18-
client::{tokens::NoToken, Client},
19-
Invoice,
20-
};
18+
use bindle::Invoice;
19+
pub use connection::BindleConnectionInfo;
2120
use futures::future;
2221
use spin_manifest::{
2322
Application, ApplicationInformation, ApplicationOrigin, CoreComponent, ModuleSource,
2423
SpinVersion, WasmConfig,
2524
};
2625
use std::{path::Path, sync::Arc};
2726
use tracing::log;
28-
pub use utils::{BindleTokenManager, SPIN_MANIFEST_MEDIA_TYPE};
27+
pub(crate) use utils::BindleReader;
28+
pub use utils::SPIN_MANIFEST_MEDIA_TYPE;
2929

3030
/// Given a Bindle server URL and reference, pull it, expand its assets locally, and get a
3131
/// prepared application configuration consumable by a Spin execution context.
@@ -34,8 +34,8 @@ pub use utils::{BindleTokenManager, SPIN_MANIFEST_MEDIA_TYPE};
3434
pub async fn from_bindle(id: &str, url: &str, base_dst: impl AsRef<Path>) -> Result<Application> {
3535
// TODO
3636
// Handle Bindle authentication.
37-
let manager = BindleTokenManager::NoToken(NoToken);
38-
let client = Client::new(url, manager)?;
37+
let connection_info = BindleConnectionInfo::new(url, false, None, None);
38+
let client = connection_info.client()?;
3939
let reader = BindleReader::remote(&client, &id.parse()?);
4040

4141
prepare(id, url, &reader, base_dst).await

crates/loader/src/bindle/utils.rs

Lines changed: 5 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,15 @@
11
#![deny(missing_docs)]
22

33
use anyhow::{anyhow, bail, Context, Error, Result};
4-
use async_trait::async_trait;
5-
use bindle::{
6-
client::{
7-
self,
8-
tokens::{NoToken, TokenManager},
9-
Client,
10-
},
11-
standalone::StandaloneRead,
12-
Id, Invoice, Label, Parcel,
13-
};
4+
use bindle::{client::Client, standalone::StandaloneRead, Id, Invoice, Label, Parcel};
145
use futures::{Stream, StreamExt, TryStreamExt};
156
use itertools::Itertools;
16-
use reqwest::RequestBuilder;
177
use std::{fmt::Debug, path::Path, sync::Arc};
188
use tokio::fs;
199
use tokio_util::codec::{BytesCodec, FramedRead};
2010

11+
use super::connection::AnyAuth;
12+
2113
static EMPTY: &Vec<bindle::Parcel> = &vec![];
2214

2315
// Alternative to storing `spin.toml` as a parcel, this could be
@@ -101,30 +93,6 @@ trigger = "http"
10193
10294
*/
10395

104-
/// Any kind of Bindle authentication.
105-
#[derive(Clone)]
106-
pub enum BindleTokenManager {
107-
/// Anonymous authentication
108-
NoToken(NoToken),
109-
}
110-
111-
#[async_trait]
112-
impl TokenManager for BindleTokenManager {
113-
async fn apply_auth_header(&self, builder: RequestBuilder) -> client::Result<RequestBuilder> {
114-
match self {
115-
Self::NoToken(t) => t.apply_auth_header(builder).await,
116-
}
117-
}
118-
}
119-
120-
impl Debug for BindleTokenManager {
121-
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
122-
match self {
123-
Self::NoToken(_) => f.debug_tuple("NoToken").finish(),
124-
}
125-
}
126-
}
127-
12896
/// Encapsulate a Bindle source.
12997
#[derive(Clone, Debug)]
13098
pub(crate) struct BindleReader {
@@ -207,7 +175,7 @@ impl BindleReader {
207175
}
208176
}
209177

210-
pub(crate) fn remote(c: &Client<BindleTokenManager>, id: &Id) -> Self {
178+
pub(crate) fn remote(c: &Client<AnyAuth>, id: &Id) -> Self {
211179
Self {
212180
inner: BindleReaderInner::Remote(c.clone(), id.clone()),
213181
}
@@ -225,7 +193,7 @@ impl BindleReader {
225193
#[derive(Clone)]
226194
enum BindleReaderInner {
227195
Standalone(Arc<StandaloneRead>),
228-
Remote(Client<BindleTokenManager>, Id),
196+
Remote(Client<AnyAuth>, Id),
229197
}
230198

231199
impl Debug for BindleReaderInner {

crates/loader/src/local/mod.rs

Lines changed: 40 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,21 +18,27 @@ use spin_manifest::{
1818
Application, ApplicationInformation, ApplicationOrigin, CoreComponent, ModuleSource,
1919
SpinVersion, WasmConfig,
2020
};
21-
use std::{path::Path, sync::Arc};
21+
use std::{path::Path, str::FromStr, sync::Arc};
2222
use tokio::{fs::File, io::AsyncReadExt};
2323

24+
use crate::bindle::BindleConnectionInfo;
25+
2426
/// Given the path to a spin.toml manifest file, prepare its assets locally and
2527
/// get a prepared application configuration consumable by a Spin execution context.
2628
/// If a directory is provided, use it as the base directory to expand the assets,
2729
/// otherwise create a new temporary directory.
28-
pub async fn from_file(app: impl AsRef<Path>, base_dst: impl AsRef<Path>) -> Result<Application> {
30+
pub async fn from_file(
31+
app: impl AsRef<Path>,
32+
base_dst: impl AsRef<Path>,
33+
bindle_connection: &Option<BindleConnectionInfo>,
34+
) -> Result<Application> {
2935
let app = app
3036
.as_ref()
3137
.absolutize()
3238
.context("Failed to resolve absolute path to manifest file")?;
3339
let manifest = raw_manifest_from_file(&app).await?;
3440

35-
prepare_any_version(manifest, app, base_dst).await
41+
prepare_any_version(manifest, app, base_dst, bindle_connection).await
3642
}
3743

3844
/// Reads the spin.toml file as a raw manifest.
@@ -54,9 +60,10 @@ async fn prepare_any_version(
5460
raw: RawAppManifestAnyVersion,
5561
src: impl AsRef<Path>,
5662
base_dst: impl AsRef<Path>,
63+
bindle_connection: &Option<BindleConnectionInfo>,
5764
) -> Result<Application> {
5865
match raw {
59-
RawAppManifestAnyVersion::V1(raw) => prepare(raw, src, base_dst).await,
66+
RawAppManifestAnyVersion::V1(raw) => prepare(raw, src, base_dst, bindle_connection).await,
6067
}
6168
}
6269

@@ -79,6 +86,7 @@ async fn prepare(
7986
mut raw: RawAppManifest,
8087
src: impl AsRef<Path>,
8188
base_dst: impl AsRef<Path>,
89+
bindle_connection: &Option<BindleConnectionInfo>,
8290
) -> Result<Application> {
8391
let info = info(raw.info, &src);
8492

@@ -104,7 +112,7 @@ async fn prepare(
104112
let components = future::join_all(
105113
raw.components
106114
.into_iter()
107-
.map(|c| async { core(c, &src, &base_dst).await })
115+
.map(|c| async { core(c, &src, &base_dst, bindle_connection).await })
108116
.collect::<Vec<_>>(),
109117
)
110118
.await
@@ -125,7 +133,10 @@ async fn core(
125133
raw: RawComponentManifest,
126134
src: impl AsRef<Path>,
127135
base_dst: impl AsRef<Path>,
136+
bindle_connection: &Option<BindleConnectionInfo>,
128137
) -> Result<CoreComponent> {
138+
let id = raw.id;
139+
129140
let src = src
130141
.as_ref()
131142
.parent()
@@ -139,12 +150,33 @@ async fn core(
139150

140151
ModuleSource::FileReference(p)
141152
}
142-
config::RawModuleSource::Bindle(_) => {
143-
todo!("Bindle module sources are not yet supported in file-based app config")
153+
config::RawModuleSource::Bindle(b) => {
154+
let bindle_id = bindle::Id::from_str(&b.reference).with_context(|| {
155+
format!("Invalid bindle ID {} in component {}", b.reference, id)
156+
})?;
157+
let parcel_sha = &b.parcel;
158+
let client = match bindle_connection {
159+
None => anyhow::bail!(
160+
"Component {} requires a Bindle connection but none was specified",
161+
id
162+
),
163+
Some(c) => c.client()?,
164+
};
165+
let bindle_reader = crate::bindle::BindleReader::remote(&client, &bindle_id);
166+
let bytes = bindle_reader
167+
.get_parcel(parcel_sha)
168+
.await
169+
.with_context(|| {
170+
format!(
171+
"Failed to download parcel {}@{} for component {}",
172+
bindle_id, parcel_sha, id
173+
)
174+
})?;
175+
let name = format!("{}@{}", bindle_id, parcel_sha);
176+
ModuleSource::Buffer(bytes, name)
144177
}
145178
};
146179

147-
let id = raw.id;
148180
let description = raw.description;
149181
let mounts = match raw.wasm.files {
150182
Some(f) => assets::prepare_component(&f, src, &base_dst, &id).await?,

crates/loader/src/local/tests.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ async fn test_from_local_source() -> Result<()> {
1111

1212
let temp_dir = tempfile::tempdir()?;
1313
let dir = temp_dir.path();
14-
let app = from_file(MANIFEST, dir).await?;
14+
let app = from_file(MANIFEST, dir, &None).await?;
1515

1616
assert_eq!(app.info.name, "spin-local-source-test");
1717
assert_eq!(app.info.version, "1.0.0");
@@ -145,7 +145,7 @@ async fn test_duplicate_component_id_is_rejected() -> Result<()> {
145145

146146
let temp_dir = tempfile::tempdir()?;
147147
let dir = temp_dir.path();
148-
let app = from_file(MANIFEST, dir).await;
148+
let app = from_file(MANIFEST, dir, &None).await;
149149

150150
assert!(
151151
app.is_err(),

src/commands/up.rs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ use std::sync::Arc;
33

44
use anyhow::{bail, Context, Result};
55
use clap::{Args, Parser};
6+
use spin_loader::bindle::BindleConnectionInfo;
67
use tempfile::TempDir;
78

89
use spin_engine::io::FollowComponents;
@@ -133,7 +134,8 @@ impl UpCommand {
133134
let manifest_file = app
134135
.as_deref()
135136
.unwrap_or_else(|| DEFAULT_MANIFEST_FILE.as_ref());
136-
spin_loader::from_file(manifest_file, working_dir).await?
137+
let bindle_connection = self.bindle_connection();
138+
spin_loader::from_file(manifest_file, working_dir, &bindle_connection).await?
137139
}
138140
(None, Some(bindle)) => match &self.server {
139141
Some(server) => spin_loader::from_bindle(bindle, server, working_dir).await?,
@@ -222,6 +224,12 @@ impl UpCommand {
222224
FollowComponents::Named(followed)
223225
}
224226
}
227+
228+
fn bindle_connection(&self) -> Option<BindleConnectionInfo> {
229+
self.server
230+
.as_ref()
231+
.map(|url| BindleConnectionInfo::new(url, false, None, None))
232+
}
225233
}
226234

227235
enum WorkingDirectory {
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
spin_version = "1"
2+
authors = ["Fermyon Engineering <[email protected]>"]
3+
description = "A simple application that returns hello and goodbye."
4+
name = "spin-hello-from-parcel"
5+
trigger = {type = "http", base = "/test"}
6+
version = "1.0.0"
7+
8+
[config]
9+
object = { default = "teapot" }
10+
11+
[[component]]
12+
id = "hello"
13+
source = { reference = "spin-hello-world/1.0.0", parcel = "AWAITING_PARCEL_SHA" }
14+
files = [ { source = "assets", destination = "/" } ]
15+
[component.trigger]
16+
route = "/hello/..."
17+
[component.config]
18+
message = "I'm a {{object}}"

0 commit comments

Comments
 (0)