Skip to content

Commit

Permalink
Merge pull request #2479 from itowlson/manifestless
Browse files Browse the repository at this point in the history
Run a Wasm file as an application without a manifest
  • Loading branch information
itowlson authored May 26, 2024
2 parents 9457de5 + 3d562c4 commit c14a222
Show file tree
Hide file tree
Showing 8 changed files with 80 additions and 9 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions crates/loader/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ dirs = "4.0"
dunce = "1.0"
futures = "0.3.17"
glob = "0.3.0"
indexmap = { version = "1" }
itertools = "0.10.3"
lazy_static = "1.4.0"
mime_guess = { version = "2.0" }
Expand Down
41 changes: 41 additions & 0 deletions crates/loader/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,14 @@ pub async fn from_file(
loader.load_file(path).await
}

/// Load a Spin locked app from a standalone Wasm file.
pub async fn from_wasm_file(wasm_path: impl AsRef<Path>) -> Result<LockedApp> {
let app_root = std::env::current_dir()?;
let manifest = single_file_manifest(wasm_path)?;
let loader = LocalLoader::new(&app_root, FilesMountStrategy::Direct, None).await?;
loader.load_manifest(manifest).await
}

/// The strategy to use for mounting WASI files into a guest.
#[derive(Debug)]
pub enum FilesMountStrategy {
Expand All @@ -50,3 +58,36 @@ pub enum FilesMountStrategy {
/// patterns, and `exclude_files` are not supported.
Direct,
}

fn single_file_manifest(
wasm_path: impl AsRef<Path>,
) -> anyhow::Result<spin_manifest::schema::v2::AppManifest> {
use serde::Deserialize;

let wasm_path_str = wasm_path
.as_ref()
.to_str()
.context("Failed to stringise Wasm file path")?
.to_owned();
let app_name = wasm_path
.as_ref()
.file_stem()
.and_then(|s| s.to_str())
.unwrap_or("wasm-file")
.to_owned();

let manifest = toml::toml!(
spin_manifest_version = 2

[application]
name = app_name

[[trigger.http]]
route = "/..."
component = { source = wasm_path_str }
);

let manifest = spin_manifest::schema::v2::AppManifest::deserialize(manifest)?;

Ok(manifest)
}
2 changes: 1 addition & 1 deletion crates/loader/src/local.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ impl LocalLoader {
}

// Load the given manifest into a LockedApp, ready for execution.
async fn load_manifest(&self, mut manifest: AppManifest) -> Result<LockedApp> {
pub(crate) async fn load_manifest(&self, mut manifest: AppManifest) -> Result<LockedApp> {
spin_manifest::normalize::normalize_manifest(&mut manifest);

let AppManifest {
Expand Down
4 changes: 2 additions & 2 deletions crates/manifest/src/schema/v2.rs
Original file line number Diff line number Diff line change
Expand Up @@ -118,10 +118,10 @@ pub struct Component {
pub exclude_files: Vec<String>,
/// `allowed_http_hosts = ["example.com"]`
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub(crate) allowed_http_hosts: Vec<String>,
pub allowed_http_hosts: Vec<String>,
/// `allowed_outbound_hosts = ["redis://myredishost.com:6379"]`
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub(crate) allowed_outbound_hosts: Vec<String>,
pub allowed_outbound_hosts: Vec<String>,
/// `key_value_stores = ["default", "my-store"]`
#[serde(
default,
Expand Down
1 change: 1 addition & 0 deletions examples/spin-timer/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions src/commands/up.rs
Original file line number Diff line number Diff line change
Expand Up @@ -435,6 +435,9 @@ impl UpCommand {
.await?;
ResolvedAppSource::OciRegistry { locked_app }
}
AppSource::BareWasm(path) => ResolvedAppSource::BareWasm {
wasm_path: path.clone(),
},
AppSource::Unresolvable(err) => bail!("{err}"),
AppSource::None => bail!("Internal error - should have shown help"),
})
Expand Down Expand Up @@ -463,6 +466,11 @@ impl UpCommand {
})
}
ResolvedAppSource::OciRegistry { locked_app } => Ok(locked_app),
ResolvedAppSource::BareWasm { wasm_path } => spin_loader::from_wasm_file(&wasm_path)
.await
.with_context(|| {
format!("Failed to load component from {}", quoted_path(&wasm_path))
}),
}
}

Expand Down
31 changes: 25 additions & 6 deletions src/commands/up/app_source.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ use spin_manifest::schema::v2::AppManifest;
pub enum AppSource {
File(PathBuf),
OciRegistry(String),
BareWasm(PathBuf),
Unresolvable(String),
None,
}
Expand All @@ -31,7 +32,13 @@ impl AppSource {

pub fn infer_file_source(path: impl Into<PathBuf>) -> Self {
match spin_common::paths::resolve_manifest_file_path(path.into()) {
Ok(file) => Self::File(file),
Ok(file) => {
if is_wasm_file(&file) {
Self::BareWasm(file)
} else {
Self::File(file)
}
}
Err(e) => Self::Unresolvable(e.to_string()),
}
}
Expand All @@ -58,11 +65,17 @@ impl AppSource {
}
}

fn is_wasm_file(path: &Path) -> bool {
let extn = path.extension().and_then(std::ffi::OsStr::to_str);
extn.is_some_and(|e| e == "wasm" || e == "wat")
}

impl std::fmt::Display for AppSource {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::File(path) => write!(f, "local app {}", quoted_path(path)),
Self::OciRegistry(reference) => write!(f, "remote app {reference:?}"),
Self::BareWasm(path) => write!(f, "Wasm file {}", quoted_path(path)),
Self::Unresolvable(s) => write!(f, "unknown app source: {s:?}"),
Self::None => write!(f, "<no source>"),
}
Expand All @@ -77,6 +90,9 @@ pub enum ResolvedAppSource {
manifest_path: PathBuf,
manifest: AppManifest,
},
BareWasm {
wasm_path: PathBuf,
},
OciRegistry {
locked_app: LockedApp,
},
Expand All @@ -85,17 +101,20 @@ pub enum ResolvedAppSource {
impl ResolvedAppSource {
pub fn trigger_types(&self) -> anyhow::Result<Vec<&str>> {
let types = match self {
ResolvedAppSource::File { manifest, .. } => {
manifest.triggers.keys().collect::<HashSet<_>>()
}
ResolvedAppSource::File { manifest, .. } => manifest
.triggers
.keys()
.map(|s| s.as_str())
.collect::<HashSet<_>>(),
ResolvedAppSource::OciRegistry { locked_app } => locked_app
.triggers
.iter()
.map(|t| &t.trigger_type)
.map(|t| t.trigger_type.as_str())
.collect::<HashSet<_>>(),
ResolvedAppSource::BareWasm { .. } => ["http"].into_iter().collect(),
};

ensure!(!types.is_empty(), "no triggers in app");
Ok(types.into_iter().map(|t| t.as_str()).collect())
Ok(types.into_iter().collect())
}
}

0 comments on commit c14a222

Please sign in to comment.