Skip to content

Commit 021f630

Browse files
committed
Allow parcel ref in component source
Signed-off-by: itowlson <[email protected]>
1 parent 03fc666 commit 021f630

File tree

6 files changed

+118
-55
lines changed

6 files changed

+118
-55
lines changed
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: 28 additions & 7 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,6 +133,7 @@ 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> {
129138
let src = src
130139
.as_ref()
@@ -139,8 +148,20 @@ async fn core(
139148

140149
ModuleSource::FileReference(p)
141150
}
142-
config::RawModuleSource::Bindle(_) => {
143-
todo!("Bindle module sources are not yet supported in file-based app config")
151+
config::RawModuleSource::Bindle(b) => {
152+
let bindle_id = bindle::Id::from_str(&b.reference)?;
153+
let parcel_sha = &b.parcel;
154+
let client = match bindle_connection {
155+
None => anyhow::bail!(
156+
"Component {} requires a Bindle connection but none was specified",
157+
raw.id
158+
),
159+
Some(c) => c.client()?,
160+
};
161+
let bindle_reader = crate::bindle::BindleReader::remote(&client, &bindle_id);
162+
let bytes = bindle_reader.get_parcel(parcel_sha).await?;
163+
let name = format!("{}@{}", bindle_id, parcel_sha);
164+
ModuleSource::Buffer(bytes, name)
144165
}
145166
};
146167

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 {

0 commit comments

Comments
 (0)