Merge pull request #133 from radu-matei/cleanup

This commit is contained in:
Radu Matei 2022-03-07 19:07:17 +02:00 committed by GitHub
commit 04db5184c9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 115 additions and 90 deletions

38
Cargo.lock generated
View File

@ -2990,6 +2990,21 @@ dependencies = [
"walkdir",
]
[[package]]
name = "spin-macro"
version = "0.1.0"
dependencies = [
"anyhow",
"bytes 1.1.0",
"http 0.2.6",
"proc-macro2",
"quote",
"syn",
"wit-bindgen-gen-core",
"wit-bindgen-gen-rust-wasm",
"wit-bindgen-rust",
]
[[package]]
name = "spin-publish"
version = "0.1.0"
@ -3010,6 +3025,17 @@ dependencies = [
"toml",
]
[[package]]
name = "spin-sdk"
version = "0.1.0"
dependencies = [
"anyhow",
"bytes 1.1.0",
"http 0.2.6",
"spin-macro",
"wasi-experimental-http",
]
[[package]]
name = "spin-templates"
version = "0.1.0"
@ -3846,6 +3872,18 @@ dependencies = [
"winapi",
]
[[package]]
name = "wasi-experimental-http"
version = "0.9.0"
source = "git+https://github.com/radu-matei/wasi-experimental-http?branch=from-client#410e9c598131aeec4d21ffdb65dfd703304da37d"
dependencies = [
"anyhow",
"bytes 1.1.0",
"http 0.2.6",
"thiserror",
"tracing",
]
[[package]]
name = "wasi-experimental-http-wasmtime"
version = "0.9.0"

View File

@ -35,7 +35,7 @@ tracing-subscriber = { version = "0.3.7", features = [ "env-filter" ] }
hyper = { version = "0.14", features = [ "full" ] }
[workspace]
members = [ "crates/config", "crates/engine", "crates/http", "crates/loader", "crates/templates" ]
members = [ "crates/config", "crates/engine", "crates/http", "crates/loader", "crates/templates", "sdk/rust", "sdk/rust/macro" ]
[[bin]]
name = "spin"

View File

@ -38,7 +38,7 @@ pub struct ApplicationInformation {
/// but for now, a component with a different trigger must be part of
/// a separate application.
pub trigger: ApplicationTrigger,
/// Namespace for groupping applications.
/// Namespace for grouping applications.
pub namespace: Option<String>,
/// The location from which the application is loaded.
pub origin: ApplicationOrigin,
@ -137,7 +137,6 @@ impl Debug for ModuleSource {
}
}
}
//f.debug_struct("Buffer").field("buffer", bytes.len().finish()
/// Configuration for the HTTP trigger.
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct HttpConfig {
@ -156,13 +155,17 @@ impl Default for HttpConfig {
}
}
/// The type of interface the component implements.
/// The executor for the HTTP component.
/// The component can either implement the Spin HTTP interface,
/// or the Wagi CGI interface.
///
/// If an executor is not specified, the inferred default is `HttpExecutor::Spin`.
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
#[serde(deny_unknown_fields, rename_all = "camelCase", tag = "type")]
pub enum HttpExecutor {
/// The component implements the Spin HTTP interface.
Spin,
/// The component implements the Wagi interface.
/// The component implements the Wagi CGI interface.
Wagi(WagiConfig),
}

View File

@ -14,10 +14,6 @@ use wasi_common::WasiCtx;
use wasmtime::{Engine, Instance, InstancePre, Linker, Module, Store};
use wasmtime_wasi::{ambient_authority, Dir, WasiCtxBuilder};
/// Runtime configuration
#[derive(Clone, Debug, Default)]
pub struct RuntimeConfig;
/// Builder-specific configuration.
#[derive(Clone, Debug)]
pub struct ExecutionContextConfiguration {
@ -28,7 +24,7 @@ pub struct ExecutionContextConfiguration {
}
impl ExecutionContextConfiguration {
/// Create a new execution context configuration.
/// Creates a new execution context configuration.
pub fn new(app: spin_config::Configuration<CoreComponent>) -> Self {
// In order for Wasmtime to run WebAssembly components, multi memory
// and module linking must always be enabled.
@ -67,7 +63,7 @@ pub struct Builder<T: Default> {
}
impl<T: Default> Builder<T> {
/// Create a new instance of the execution builder.
/// Creates a new instance of the execution builder.
pub fn new(config: ExecutionContextConfiguration) -> Result<Builder<T>> {
let data = RuntimeContext::default();
let engine = Engine::new(&config.wasmtime)?;
@ -82,17 +78,13 @@ impl<T: Default> Builder<T> {
})
}
/// Configure the WASI linker imports for the current execution context.
/// Configures the WASI linker imports for the current execution context.
pub fn link_wasi(&mut self) -> Result<&Self> {
wasmtime_wasi::add_to_linker(&mut self.linker, |ctx| ctx.wasi.as_mut().unwrap())?;
Ok(self)
}
// Importing the next version of the outbound HTTP library
// from https://github.com/fermyon/wasi-experimental-toolkit/tree/main/crates/http-wasmtime
// doesn't work as a git import, as it can't find the WIT file.
/// Configure the ability to execute outbound HTTP requests.
/// Configures the ability to execute outbound HTTP requests.
pub fn link_http(&mut self) -> Result<&Self> {
wasi_experimental_http_wasmtime::HttpState::new()?
.add_to_linker(&mut self.linker, |ctx| ctx.http.as_ref().unwrap())?;
@ -100,7 +92,7 @@ impl<T: Default> Builder<T> {
Ok(self)
}
/// Build a new instance of the execution context.
/// Builds a new instance of the execution context.
#[instrument(skip(self))]
pub async fn build(&mut self) -> Result<ExecutionContext<T>> {
let mut components = HashMap::new();
@ -150,7 +142,7 @@ impl<T: Default> Builder<T> {
})
}
/// Build a new default instance of the execution context.
/// Builds a new default instance of the execution context.
pub async fn build_default(
config: ExecutionContextConfiguration,
) -> Result<ExecutionContext<T>> {
@ -183,7 +175,7 @@ pub struct ExecutionContext<T: Default> {
}
impl<T: Default> ExecutionContext<T> {
/// Create a store for a given component given its configuration and runtime data.
/// Creates a store for a given component given its configuration and runtime data.
#[instrument(skip(self, data, io))]
pub fn prepare_component(
&self,
@ -205,7 +197,7 @@ impl<T: Default> ExecutionContext<T> {
Ok((store, instance))
}
/// Create a store for a given component given its configuration and runtime data.
/// Creates a store for a given component given its configuration and runtime data.
fn store(
&self,
component: &Component<T>,
@ -225,7 +217,6 @@ impl<T: Default> ExecutionContext<T> {
Some(r) => {
wasi_ctx = wasi_ctx
.stderr(Box::new(r.stderr.out))
// .inherit_stderr()
.stdout(Box::new(r.stdout.out))
.stdin(Box::new(r.stdin));
}

View File

@ -4,8 +4,13 @@ mod routes;
mod spin;
mod tls;
mod wagi;
pub use tls::TlsConfig;
use crate::wagi::WagiHttpExecutor;
use crate::{
routes::{RoutePattern, Router},
spin::SpinHttpExecutor,
wagi::WagiHttpExecutor,
};
use anyhow::{Context, Error, Result};
use async_trait::async_trait;
use futures_util::stream::StreamExt;
@ -16,13 +21,10 @@ use hyper::{
service::{make_service_fn, service_fn},
Body, Request, Response, Server,
};
use routes::{RoutePattern, Router};
use spin::SpinHttpExecutor;
use spin_config::{ApplicationTrigger, Configuration, CoreComponent, TriggerConfig};
use spin_engine::{Builder, ExecutionContextConfiguration};
use spin_http::SpinHttpData;
use std::{future::ready, net::SocketAddr, sync::Arc};
pub use tls::TlsConfig;
use tls_listener::TlsListener;
use tokio::net::{TcpListener, TcpStream};
use tokio_rustls::server::TlsStream;
@ -54,7 +56,7 @@ pub struct HttpTrigger {
}
impl HttpTrigger {
/// Create a new Spin HTTP trigger.
/// Creates a new Spin HTTP trigger.
pub async fn new(
address: String,
app: Configuration<CoreComponent>,
@ -79,7 +81,7 @@ impl HttpTrigger {
})
}
/// Handle incoming requests using an HTTP executor.
/// Handles incoming requests using an HTTP executor.
pub(crate) async fn handle(
&self,
req: Request<Body>,
@ -146,7 +148,7 @@ impl HttpTrigger {
}
}
/// Create an HTTP 500 response.
/// Creates an HTTP 500 response.
fn internal_error(body: Option<&str>) -> Result<Response<Body>> {
let body = match body {
Some(body) => Body::from(body.as_bytes().to_vec()),
@ -158,14 +160,14 @@ impl HttpTrigger {
.body(body)?)
}
/// Create an HTTP 404 response.
/// Creates an HTTP 404 response.
fn not_found() -> Result<Response<Body>> {
let mut not_found = Response::default();
*not_found.status_mut() = StatusCode::NOT_FOUND;
Ok(not_found)
}
/// Run the HTTP trigger indefinitely.
/// Runs the HTTP trigger indefinitely.
pub async fn run(&self) -> Result<()> {
match self.tls.as_ref() {
Some(tls) => self.serve_tls(tls).await?,

View File

@ -13,7 +13,7 @@ use tracing::log;
// The current implementation of the router clones the components, which could
// become costly if we have a lot of components.
// The router should borrow the components, which needs to introduce a lifetime
// paramter which surfaces in the HTTP trigger (and which needs a &'static because
// parameter which surfaces in the HTTP trigger (and which needs a &'static because
// of the Hyper server.)
//
// For now we continue to use the router using owned data, but in the future it might
@ -27,7 +27,7 @@ pub(crate) struct Router {
}
impl Router {
/// Build a router based on application configuration.
/// Builds a router based on application configuration.
pub(crate) fn build(app: &Configuration<CoreComponent>) -> Result<Self> {
let ApplicationTrigger::Http(app_trigger) = app.info.trigger.clone();
let routes = app
@ -57,10 +57,10 @@ impl Router {
// but might not hold if the application configuration is deserialized in
// other ways.
/// Return the component that should handle the given path, or an error
/// Returns the component that should handle the given path, or an error
/// if no component matches.
/// If there are multiple possible components registered for the same route or
/// wildcard, return the last one in the components vector.
/// wildcard, this returns the last one in the components vector.
pub(crate) fn route<S: Into<String> + Debug>(&self, p: S) -> Result<CoreComponent> {
let p = p.into();
@ -86,7 +86,7 @@ pub(crate) enum RoutePattern {
}
impl RoutePattern {
/// Return a RoutePattern given a path fragment.
/// Returns a RoutePattern given a path fragment.
pub(crate) fn from<S: Into<String>>(base: S, path: S) -> Self {
let path = format!(
"{}{}",
@ -111,7 +111,7 @@ impl RoutePattern {
}
}
/// Resolve a relative path from the end of the matched path to the end of the string.
/// Resolves a relative path from the end of the matched path to the end of the string.
pub(crate) fn relative(&self, uri: &str) -> Result<String> {
let base = match self {
Self::Exact(path) => path,
@ -125,7 +125,7 @@ impl RoutePattern {
.to_owned())
}
/// Sanitize the base and path and return a formed path.
/// Sanitizes the base and path and return a formed path.
pub(crate) fn sanitize_with_base<S: Into<String>>(base: S, path: S) -> String {
format!(
"{}{}",
@ -134,7 +134,7 @@ impl RoutePattern {
)
}
/// Strip the trailing slash from a string.
/// Strips the trailing slash from a string.
fn sanitize<S: Into<String>>(s: S) -> String {
let s = s.into();
// TODO

View File

@ -1,6 +1,7 @@
use crate::spin_http::{Method, SpinHttp};
use crate::HttpExecutor;
use crate::{ExecutionContext, RuntimeContext};
use crate::{
spin_http::{Method, SpinHttp},
ExecutionContext, HttpExecutor, RuntimeContext,
};
use anyhow::Result;
use async_trait::async_trait;
use http::Uri;
@ -119,8 +120,6 @@ impl SpinHttpExecutor {
res.push((name, value));
}
// TODO
// Is there any scenario where the server doesn't populate the host header?
let default_host = http::HeaderValue::from_str("localhost")?;
let host = std::str::from_utf8(
req.headers()

View File

@ -6,7 +6,7 @@ use std::{
};
use tokio_rustls::{rustls, TlsAcceptor};
/// Tls configuration for the server.
/// TLS configuration for the server.
#[derive(Clone)]
pub struct TlsConfig {
/// Path to TLS certificate.
@ -16,7 +16,7 @@ pub struct TlsConfig {
}
impl TlsConfig {
// Create a TLS acceptor from server config.
// Creates a TLS acceptor from server config.
pub(super) fn server_config(&self) -> anyhow::Result<TlsAcceptor> {
let certs = load_certs(&self.cert_path)?;
let mut keys = load_keys(&self.key_path)?;
@ -31,14 +31,14 @@ impl TlsConfig {
}
}
// Load public certificate from file.
// Loads public certificate from file.
fn load_certs(path: impl AsRef<Path>) -> io::Result<Vec<rustls::Certificate>> {
certs(&mut io::BufReader::new(fs::File::open(path)?))
.map_err(|_| io::Error::new(io::ErrorKind::InvalidInput, "invalid cert"))
.map(|mut certs| certs.drain(..).map(rustls::Certificate).collect())
}
// Load private key from file.
// Loads private key from file.
fn load_keys(path: impl AsRef<Path>) -> io::Result<Vec<rustls::PrivateKey>> {
pkcs8_private_keys(&mut io::BufReader::new(fs::File::open(path)?))
.map_err(|_| io::Error::new(io::ErrorKind::InvalidInput, "invalid key"))

View File

@ -1,13 +1,11 @@
use crate::routes::RoutePattern;
use crate::ExecutionContext;
use crate::HttpExecutor;
use crate::{routes::RoutePattern, ExecutionContext, HttpExecutor};
use anyhow::Result;
use async_trait::async_trait;
use hyper::{body, Body, Request, Response};
use spin_config::WagiConfig;
use spin_engine::io::{IoStreamRedirects, OutRedirect};
use std::collections::HashMap;
use std::{
collections::HashMap,
net::SocketAddr,
sync::{Arc, RwLock},
};
@ -68,9 +66,6 @@ impl HttpExecutor for WagiHttpExecutor {
&HashMap::new(),
);
// TODO
// Is there any scenario where the server doesn't populate the host header?
// MPB: Yes, a misbehaving client can fail to set the HOST.
let default_host = http::HeaderValue::from_str("localhost")?;
let host = std::str::from_utf8(
parts
@ -114,12 +109,12 @@ impl HttpExecutor for WagiHttpExecutor {
impl WagiHttpExecutor {
fn streams_from_body(body: Vec<u8>) -> IoStreamRedirects {
let stdin = ReadPipe::from(body);
let stdout_buf: Vec<u8> = vec![];
let stdout_buf = vec![];
let lock = Arc::new(RwLock::new(stdout_buf));
let stdout = WritePipe::from_shared(lock.clone());
let stdout = OutRedirect { out: stdout, lock };
let stderr_buf: Vec<u8> = vec![];
let stderr_buf = vec![];
let lock = Arc::new(RwLock::new(stderr_buf));
let stderr = WritePipe::from_shared(lock.clone());
let stderr = OutRedirect { out: stderr, lock };

View File

@ -1,7 +1,9 @@
#![deny(missing_docs)]
use super::utils::BindleReader;
use crate::assets::{create_dir, ensure_under};
use crate::{
assets::{create_dir, ensure_under},
bindle::utils::BindleReader,
};
use anyhow::{anyhow, bail, Context, Result};
use bindle::{Id, Label};
use futures::future;
@ -62,7 +64,6 @@ impl Copier {
}
}
/// Copy
async fn copy(&self, p: &Label, dir: impl AsRef<Path>) -> Result<()> {
let to = dir.as_ref().join(&p.name);

View File

@ -4,14 +4,13 @@
/// Module to prepare the assets for the components of an application.
mod assets;
/// Configuration representation for a Spin apoplication in Bindle.
/// Configuration representation for a Spin application in Bindle.
pub mod config;
/// Bindle helper functions.
mod utils;
use self::config::RawComponentManifest;
use crate::bindle::{
config::RawAppManifest,
config::{RawAppManifest, RawComponentManifest},
utils::{find_manifest, BindleReader},
};
use anyhow::{anyhow, Context, Result};
@ -26,7 +25,6 @@ use spin_config::{
};
use std::path::{Path, PathBuf};
use tracing::log;
pub use utils::{BindleTokenManager, SPIN_MANIFEST_MEDIA_TYPE};
/// Given a Bindle server URL and reference, pull it, expand its assets locally, and get a
@ -47,6 +45,7 @@ pub async fn from_bindle(
prepare(id, url, &reader, base_dst).await
}
/// Converts a Bindle invoice into Spin configuration.
async fn prepare(
id: &str,
url: &str,
@ -58,13 +57,13 @@ async fn prepare(
None => tempfile::tempdir()?.into_path(),
};
// first, get the invoice.
// First, get the invoice from the Bindle server.
let invoice = reader
.get_invoice()
.await
.with_context(|| anyhow!("Failed to load invoice '{}' from '{}'", id, url))?;
// then, reconstruct application manifest from the parcel.
// Then, reconstruct the application manifest from the parcels.
let raw: RawAppManifest =
toml::from_slice(&reader.get_parcel(&find_manifest(&invoice)?).await?)?;
log::trace!("Recreated manifest from bindle: {:?}", raw);
@ -85,6 +84,7 @@ async fn prepare(
Ok(Configuration { info, components })
}
/// Given a raw component manifest, prepare its assets and return a fully formed core component.
async fn core(
raw: RawComponentManifest,
invoice: &Invoice,
@ -128,7 +128,7 @@ async fn core(
})
}
/// Convert the raw application manifest from the bindle invoice into the
/// Converts the raw application manifest from the bindle invoice into the
/// standard application configuration.
fn info(raw: &RawAppManifest, invoice: &Invoice, url: &str) -> ApplicationInformation {
ApplicationInformation {

View File

@ -4,7 +4,7 @@
/// Module to prepare the assets for the components of an application.
pub mod assets;
/// Configuration representation for a Spin apoplication as a local spin.toml file.
/// Configuration representation for a Spin application as a local spin.toml file.
pub mod config;
#[cfg(test)]
@ -51,6 +51,8 @@ pub async fn raw_manifest_from_file(app: &impl AsRef<Path>) -> Result<RawAppMani
Ok(manifest)
}
/// Converts a raw application manifest into Spin configuration while handling
/// the Spin manifest and API version.
async fn prepare_any_version(
raw: RawAppManifestAnyVersion,
src: impl AsRef<Path>,
@ -61,6 +63,7 @@ async fn prepare_any_version(
}
}
/// Converts a raw application manifest into Spin configuration.
async fn prepare(
raw: RawAppManifest,
src: impl AsRef<Path>,
@ -87,7 +90,7 @@ async fn prepare(
Ok(Configuration { info, components })
}
/// Given a component manifest, prepare its assets and return a fully formed core component.
/// Given a raw component manifest, prepare its assets and return a fully formed core component.
async fn core(
raw: RawComponentManifest,
src: impl AsRef<Path>,
@ -133,7 +136,7 @@ async fn core(
})
}
/// Convert the raw application information from the spin.toml manifest to the standard configuration.
/// Converts the raw application information from the spin.toml manifest to the standard configuration.
fn info(raw: RawAppInformation, src: impl AsRef<Path>) -> ApplicationInformation {
ApplicationInformation {
api_version: "0.1.0".to_owned(),

View File

@ -1,16 +1,15 @@
#![deny(missing_docs)]
use std::path::Path;
use anyhow::{Context, Result};
use bindle::standalone::StandaloneRead;
use bindle::{standalone::StandaloneRead, Id};
use std::path::Path;
type BindleClient = bindle::client::Client<spin_loader::bindle::BindleTokenManager>;
/// Pushes a standalone bindle to a Bindle server.
pub async fn push_all(
path: impl AsRef<Path>,
bindle_id: &bindle::Id,
bindle_id: &Id,
client: &BindleClient,
server_url: &str,
) -> Result<()> {

View File

@ -1,13 +1,12 @@
#![deny(missing_docs)]
use anyhow::{Context, Result};
use bindle::{Invoice, Parcel};
use std::{
collections::BTreeMap,
path::{Path, PathBuf},
};
use anyhow::{Context, Result};
use bindle::{Invoice, Parcel};
struct BindleWriter {
source_dir: PathBuf,
dest_dir: PathBuf,

View File

@ -1,17 +1,13 @@
#![deny(missing_docs)]
use crate::bindle_writer::{self, ParcelSources};
use anyhow::{Context, Result};
use bindle::{BindleSpec, Condition, Group, Invoice, Label, Parcel};
use path_absolutize::Absolutize;
use sha2::{Digest, Sha256};
use spin_loader::{bindle::config as bindle_schema, local::config as local_schema};
use std::path::{Path, PathBuf};
use crate::bindle_writer;
use crate::bindle_writer::ParcelSources;
use spin_loader::bindle::config as bindle_schema;
use spin_loader::local::config as local_schema;
/// Expands a file-based application manifest to a Bindle invoice.
pub async fn expand_manifest(
app_file: impl AsRef<Path>,
@ -20,7 +16,7 @@ pub async fn expand_manifest(
let app_file = app_file
.as_ref()
.absolutize()
.context("Failed to resolve absoiute path to manifest file")?;
.context("Failed to resolve absolute path to manifest file")?;
let manifest = spin_loader::local::raw_manifest_from_file(&app_file).await?;
let local_schema::RawAppManifestAnyVersion::V0_1_0(manifest) = manifest;
let app_dir = app_dir(&app_file)?;

View File

@ -12,6 +12,3 @@ bytes = "1"
http = "0.2"
spin-macro = {path = "macro"}
wasi-experimental-http = { git = "https://github.com/radu-matei/wasi-experimental-http", branch = "from-client" }
[workspace]
members = ["macro"]

View File

@ -2,13 +2,15 @@
#![deny(missing_docs)]
/// Export the macros
/// Exports the procedural macros for writing handlers for Spin components.
pub use spin_macro::*;
/// Export the experimental outbound HTTP crate.
/// Exports the experimental outbound HTTP crate.
pub use wasi_experimental_http as outbound_http;
/// HTTP helpers.
/// Helpers for building Spin HTTP components.
/// These are convenience helpers, and the types in this module are
/// based on the [`http`](https://crates.io/crates) crate.
pub mod http {
use anyhow::Result;