Merge pull request #137 from radu-matei/docs

This commit is contained in:
Radu Matei 2022-03-07 19:18:56 +02:00 committed by GitHub
commit 70b3893127
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 664 additions and 462 deletions

View File

@ -2,5 +2,6 @@
"recommendations": [
"matklad.rust-analyzer",
"serayuzgur.crates",
"fermyon.autobindle",
]
}

20
Cargo.lock generated
View File

@ -2888,10 +2888,7 @@ name = "spin-config"
version = "0.1.0"
dependencies = [
"anyhow",
"async-trait",
"itertools",
"serde",
"tokio",
]
[[package]]
@ -2899,31 +2896,15 @@ name = "spin-engine"
version = "0.1.0"
dependencies = [
"anyhow",
"async-trait",
"bindle",
"bytes 1.1.0",
"dirs 4.0.0",
"fs_extra",
"futures",
"git2",
"glob",
"lazy_static",
"regex",
"serde",
"sha2 0.10.1",
"spin-config",
"structopt",
"tempfile",
"tokio",
"toml",
"tracing",
"tracing-futures",
"tracing-subscriber 0.3.8",
"wasi-common",
"wasi-experimental-http-wasmtime 0.9.0 (git+https://github.com/deislabs/wasi-experimental-http?rev=4ed321d6943f75546e38bba80e14a59797aa29de)",
"wasmtime",
"wasmtime-wasi",
"wit-bindgen-rust",
"wit-bindgen-wasmtime",
]
@ -2957,7 +2938,6 @@ dependencies = [
"wasi-common",
"wasmtime",
"wasmtime-wasi",
"wit-bindgen-rust",
"wit-bindgen-wasmtime",
]

View File

@ -6,7 +6,4 @@ authors = [ "Fermyon Engineering <engineering@fermyon.com>" ]
[dependencies]
anyhow = "1.0"
async-trait = "0.1.52"
itertools = "0.10.3"
serde = { version = "1.0", features = [ "derive" ] }
tokio = "1.16.1"

View File

@ -185,7 +185,7 @@ pub struct WagiConfig {
///
/// This should be a space-separate list of strings. The value
/// ${SCRIPT_NAME} will be replaced with the Wagi SCRIPT_NAME,
/// and the value ${ARGS} will be replaced with the query paramater
/// and the value ${ARGS} will be replaced with the query parameter
/// name/value pairs presented as args. For example,
/// `param1=val1&param2=val2` will become `param1=val1 param2=val2`,
/// which will then be presented to the program as two arguments

View File

@ -6,31 +6,15 @@ authors = [ "Radu Matei <radu.matei@fermyon.com>" ]
[dependencies]
anyhow = "1.0.44"
async-trait = "0.1.51"
bindle = "0.8.0"
bytes = "1.1.0"
dirs = "4.0"
fs_extra = "1.2.0"
futures = "0.3.17"
git2 = "0.13"
glob = "0.3.0"
lazy_static = "1.4.0"
regex = "1.5.4"
serde = { version = "1.0.130", features = [ "derive" ] }
sha2 = "0.10.1"
spin-config = { path = "../config" }
structopt = "0.3.23"
tempfile = "3.3.0"
tokio = { version = "1.10.0", features = [ "fs" ] }
toml = "0.5.8"
tracing = { version = "0.1", features = [ "log" ] }
tracing-futures = "0.2"
tracing-subscriber = { version = "0.3.7", features = [ "env-filter" ] }
wasi-common = "0.34"
wasi-experimental-http-wasmtime = { git = "https://github.com/deislabs/wasi-experimental-http", rev = "4ed321d6943f75546e38bba80e14a59797aa29de" }
wasmtime = "0.34"
wasmtime-wasi = "0.34"
wit-bindgen-rust = { git = "https://github.com/bytecodealliance/wit-bindgen", rev = "e9c7c0a3405845cecd3fe06f3c20ab413302fc73" }
[dev-dependencies]
wit-bindgen-wasmtime = { git = "https://github.com/bytecodealliance/wit-bindgen", rev = "e9c7c0a3405845cecd3fe06f3c20ab413302fc73" }

View File

@ -2,7 +2,7 @@
name = "spin-http-engine"
version = "0.1.0"
edition = "2021"
authors = [ "Radu Matei <radu.matei@fermyon.com>" ]
authors = [ "Fermyon Engineering <engineering@fermyon.com>" ]
[lib]
doctest = false
@ -33,8 +33,7 @@ wagi = { git = "https://github.com/deislabs/wagi", rev = "984c3922626b770ba43443
wasi-common = "0.34"
wasmtime = "0.34"
wasmtime-wasi = "0.34"
wit-bindgen-rust = { git = "https://github.com/bytecodealliance/wit-bindgen", rev = "e9c7c0a3405845cecd3fe06f3c20ab413302fc73" }
wit-bindgen-wasmtime = { git = "https://github.com/bytecodealliance/wit-bindgen", rev = "e9c7c0a3405845cecd3fe06f3c20ab413302fc73" }
[dev-dependencies]
miniserde = "0.1"
miniserde = "0.1"

View File

@ -2,6 +2,7 @@
name = "spin-loader"
version = "0.1.0"
edition = "2021"
authors = [ "Fermyon Engineering <engineering@fermyon.com>" ]
[dependencies]
anyhow = "1"

View File

@ -74,7 +74,7 @@ pub struct RawWasmConfig {
/// Files to be mapped inside the Wasm module at runtime.
///
/// In the local configuration file, this is a vector, each element of which
/// is either a file paths or glob relative to the spin.toml file, or a
/// is either a file path or glob relative to the spin.toml file, or a
/// mapping of a source path to an absolute mount path in the guest.
pub files: Option<Vec<RawFileMount>>,
/// Optional list of HTTP hosts the component is allowed to connect.

View File

@ -2,8 +2,7 @@
name = "spin-publish"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
authors = [ "Fermyon Engineering <engineering@fermyon.com>" ]
[dependencies]
anyhow = "1.0"

View File

@ -2,8 +2,7 @@
name = "spin-templates"
version = "0.1.0"
edition = "2021"
authors = [ "Radu Matei <radu.matei@fermyon.com>" ]
authors = [ "Fermyon Engineering <engineering@fermyon.com>" ]
[dependencies]
anyhow = "1.0"

62
docs/architecture.md Normal file
View File

@ -0,0 +1,62 @@
# Spin architecture and internals
This document aims to offer an overview to the implementation of Spin, as well
as explain how the code is structured and how all parts fit together. This
document is continuously evolving, and if you want even more detailed
information, make sure to review the code for a given part of Spin.
## How Spin runs an application
A Spin application is defined as a `spin.toml` file. It can either be run
directly by `spin up`, passing the manifest file (`--file spin.toml`), or it can
be pushed to the registry then referenced using its remote ID
(`spin bindle push` followed by `spin up --bindle <id>`).
Regardless of the application origin (local file or remote reference from the
registry), a Spin application is defined by
`spin_config::Configuration<CoreComponent>` (contained in the
[`spin-config`](../crates/config) crate), which is the canonical representation
of a Spin application.
The crate responsible for transforming a custom configuration into a canonical
Spin application is [`spin-loader`](../crates/loader), which implements loading
applications from local `spin.toml` files and from remote Bindle references (and
ensures files referenced in the application configuration are copied and mounted
at the location expected in the WebAssmebly module). Once the canonical
representation is loaded from an application source, it is passed to a trigger —
currently, the only trigger implemented is the HTTP trigger, and we will use it
as an example throughout this document.
The HTTP trigger (defined in the [`spin-http`](../crates/http) crate) takes an
application configuration ([#40](https://github.com/fermyon/spin/issues/40)
explores a trigger handling multiple applications), starts an HTTP listener, and
for each new request, it routes it to the component configured in the
application configuration. Then, it instantiates the WebAssembly module (using a
`spin_engine::ExecutionContext`) and uses the appropriate executor (either the
[`SpinHttpExecutor`](../crates/http/src/spin.rs) or the
[`WagiHttpExecutor`](../crates/http/src/wagi.rs), based on the component
configuration) to handle the request and return the response.
## The Spin execution context
The Spin execution context (or "Spin engine") is the part of Spin that executes
WebAssembly components using the
[Wasmtime](https://github.com/bytecodealliance/wasmtime) WebAssembly runtime. It
is implemented in the [`spin-engine`](../crates/engine/) crate, and serves as
the part of Spin that takes a fully formed application configuration and creates
Wasm instances based on the component configurations.
There are two important concepts in this crate:
- `spin_engine::Builder` — the builder for creating an execution context. It is
created using an `ExecutionContextConfiguration` object (which contains a Spin
application and Wasmtime configuration), and implements the logic for
configuring WASI and the other host implementations provided by Spin. The
builder exposes the Wasmtime
[`Linker`](https://docs.rs/wasmtime/latest/wasmtime/struct.Linker.html),
[`Engine`](https://docs.rs/wasmtime/latest/wasmtime/struct.Engine.html), and
[`Store<RuntimeContext<T>>`](https://docs.rs/wasmtime/latest/wasmtime/struct.Store.html)
(where `RuntimeContext<T>` is the internal Spin context, which is detailed
later in the document), and it uses them to [pre-instantiate]()
- `spin_engine::ExecutionContext` — the main execution engine in Spin.

View File

@ -1,144 +1,119 @@
# Configuration for Spin applications
A _Spin application_ is a collection of at least one _component_, each
instantiated as a result of an _event_ generated by a _trigger_.
In the example below we can see a simple application with a single component,
executed when the `/hello` endpoint is accessed:
Spin applications are comprised of general information, and a collection of at
least one _component_. In the example below we can see a simple HTTP application
with a single component executed when the `/hello` endpoint is accessed:
```toml
name = "spin-hello-world"
version = "1.0.0"
description = "A simple application that returns hello and goodbye."
authors = [ "Radu Matei <radu@fermyon.com>" ]
trigger = {type = "http", base = "/" }
apiVersion = "0.1.0"
name = "spin-hello-world"
description = "A simple application that returns hello world."
trigger = { type = "http", base = "/" }
version = "1.0.0"
[[component]]
source = "target/wasm32-wasi/release/spinhelloworld.wasm"
id = "hello"
id = "hello"
source = "target/wasm32-wasi/release/spinhelloworld.wasm"
[component.trigger]
route = "/hello"
route = "/hello"
```
All components of an application must be executed by the same trigger type.
## Application configuration
## The `spin.toml` configuration file
Currently, the only configuration file for a Spin application is the `spin.toml`
file. This might change in the future (it could potentially be generated from
multiple configuration sources), as this file currently mixes build- and
run-time concerns.
These are the fields currently supported for the configuration:
The following are the fields supported by the `spin.toml` configuration file:
- `apiVersion` (REQUIRED): Spin API version. Currently, this value MUST be
`"0.1.0"`.
- `name` (REQUIRED): Name of the application.
- `version` (REQUIRED): Version of the application.
- `description` (OPTIONAL): Description of the application.
- `authors` (OPTIONAL): Authors of the application.
- `namespace` (OPTIONAL): Logical grouping of the application at runtime.
- `component` (REQUIRED): List with the components of the application.
- `trigger` (REQUIRED): Application trigger and configuration. (Currently, the
only implemented trigger is `http`, and the application base path can be
configured (`base`), and will be prepended to the routes individual components
are handling. For example, if `base = "/foo"`, and a component
`route = "/bar/..."`, that component will handle incoming requests for
`/foo/bar/...`.)
- `authors` (OPTIONAL): List with the authors of the application.
- `trigger` (REQUIRED): Trigger for the application. Currently, all components
of the application must be invoked as a result of the same trigger type.
Currently, the only implemented application trigger is `http`, with the
following configuration fields:
- `type` (REQUIRED): The application trigger type with the value `"http"`.
- `base` (REQUIRED): The base path for the HTTP application which will be
prepended to the routes of all components. (For example, if `base = "/foo"`
and a component has `route = "/bar"`, the component will be invoked for
requests on `/foo/bar`.)
- a list of `component` objects (REQUIRED) defining the application components.
## Component configuration
Each `component` object has the following fields:
- `id` (REQUIRED): ID of the component, used at runtime to select between
multiple components of the same application.
- `source` (REQUIRED): Source for the component. Can either be a path to a local
file, or a pair of `reference` (REQUIRED) and `parcel` (REQUIRED) fields
pointing to a remote bindle.
- `environment` (OPTIONAL): Environment variables to be mapped inside the Wasm
module at runtime.
- `files` (OPTIONAL): Paths (relative to the configuration file) of files to be
mapped inside the Wasm module at runtime.
- `id` (REQUIRED): unique (per application) ID of the component, used at runtime
to select between multiple components of the same application.
- `source` (REQUIRED): Source for the WebAssembly module of the component. This
field can be _one_ the following:
- a string with the path to a local file containing the WebAssembly module for
the component OR
- a pair of `reference` (REQUIRED) and `parcel` (REQUIRED) fields pointing to
a remote bindle package (Note that this is currently not implemented, see
[#135](https://github.com/fermyon/spin/issues/135)).
- `environment` (OPTIONAL): Environment variables to be made available inside
the WebAssembly module at runtime.
- `files` (OPTIONAL): Files to be made available inside the WebAssembly module
at runtime. This is a list, each element of which is either:
- a file path or glob relative to the `spin.toml` file (for example
`file.txt`, or `content/static/**/*`) OR
- a mapping of a `source` (REQUIRED), a directory relative to `spin.toml` and
`destination` (REQUIRED), the absolute mount path to be mapped inside the
WebAssembly module. For example
`{ source = "/content/", destination = "/"}`.
- `allowed_http_hosts` (OPTIONAL): List of HTTP hosts the component is allowed
to connect to.
- `trigger` (REQUIRED): Trigger configuration for the component.
- `dependencies` (OPTIONAL): List of dependencies to be resolved and satisfied
at runtime by the host.
- `build` (OPTIONAL): Currently unused build information or configuration that
could be used by a plugin to build the component.
### Component sources
When writing a `spin.toml` file, components can either come from a local file,
or be they can reference a remote bindle.
As such, the `source` field in the component configuration can either directly
point to the path:
```toml
source = "path/to/wasm/file.wasm"
```
Or it can be an object containing the bindle reference and parcel:
```toml
[component.source]
reference = "bindle reference"
parcel = "parcel"
```
### Triggers
Triggers in Spin are components that generate events that cause the execution of
components. Currently, the only trigger implemented for Spin is the HTTP
trigger, which contains the following fields:
- `route` (REQUIRED): The HTTP route the component will be invoked for.
- `executor` (REQUIRED): The object that sets an executor. There are currently two executor `type`s:
- `spin` uses the Spin HTTP executor
- `wagi` uses the Wagi CGI executor
to make HTTP requests to (using the
[WASI experimental HTTP library](https://github.com/deislabs/wasi-experimental-http))
- `trigger` (REQUIRED): Trigger configuration for the component. Triggers are
the components that generate events that cause the execution of components.
Since the only implemented Spin trigger is HTTP, the component trigger
configuration currently contains HTTP component trigger configuration, which
has the following fields:
- `route` (REQUIRED): The HTTP route the component will be invoked for. It can
either be an exact route (for example `/foo/test`), or it can contain a
wildcard (`/foo/test/...`) as the last path segment, which means the
component will be invoked for every request starting with the `/foo/test`
prefix (for example `/foo/test/abc/def`).
- `executor` (REQUIRED): The executor for the HTTP component. There are
currently two executor `type`s:
- `spin` (DEFAULT): the Spin HTTP executor, which uses
[the WebAssembly component model](https://github.com/WebAssembly/component-model)
OR
- `wagi`: the Wagi CGI executor, which can be used to write components in
any language that compiles to WASI. The Wagi executor has the following
optional fields:
- `argv` (OPTIONAL): The string representation of the `argv` list that
should be passed into the handler. `${SCRIPT_NAME}` will be replaced
with the script name, and `${ARGS}` will be replaced with the query
parameters of the request, formatted as arguments. The default is to
follow the CGI specification, and pass `${SCRIPT_NAME} ${ARGS}`
- `entrypoint` (OPTIONAL): The name of the function that should be called
as the entrypoint to this handler. By default, it is `_start` (which in
most languages translates to calling `main` in the guest module).
## Examples
- Spin HTTP component that contains the files in `static/` mapped to `/`:
```toml
[[component]]
source = "modules/spin_static_fs.wasm"
id = "fileserver"
files = [ { source = "static/", destination = "/" } ]
[component.trigger]
route = "/hello"
executor = { type = "spin" }
# executor = { type="wagi" }
route = "/static/..."
```
### Wagi Executor
Some executors have additional configuration. Wagi supports the following extra configurations:
- `argv` (OPTIONAL): The string representation of the `argv` list that should be passed into the handler. `${SCRIPT_NAME}` will be replaced with the script name, and `${ARGS}` will be replaced with the query parameters of the request, formatted as arguments. The default is to follow the CGI specification, and pass `${SCRIPT_NAME} ${ARGS}`
- `entrypoint` (OPTIONAL, EXPERT): The name of the function that should be called as the entrypoint to this handler. By default, it is `_start` (which in most languages translates to calling `main` in the guest module).
- a Wagi component that contains file mounts and sets the module `argv` and
invokes a custom export function as the entrypoint:
```toml
[[component]]
source = "modules/env_wagi.wasm"
id = "env"
files = [ "content/**/*" , "templates/*", "scripts/*", "config/*"]
[component.trigger]
route = "/..."
executor = {type="wagi", argv="test ${SCRIPT_NAME} ${ARGS} done", entrypoint="_start"}
executor = { type="wagi", argv="test ${SCRIPT_NAME} ${ARGS} done", entrypoint = "some-other-export-function" }
```
### Dependencies
Each entry in the `dependencies` table should correspond to exactly one import
module from the Wasm module. Currently, this map should either contain an
interface that should be satisfied by the host runtime (through a host
implementation), or an exact reference (_not_ a version range or constraint) to
a component from the registry. The fields in a dependency section are:
- `type` (REQUIRED): The type of the dependency. Possible values: `host` or
`component`.
- `reference` (OPTIONAL): Reference to a component from the registry. Required
if `type` is `component`.
- `parcel` (OPTIONAL): Parcel to use from the bindle reference. Required if
`type` is `component`.
## Glossary
- WebAssembly module (or "module") — compilation artifact targeting
`wasm32-wasi` that adheres to
[the Wasm specification](https://webassembly.org/specs/).
- WebAssembly component (or "component") — compilation artifact targeting
`wasm32-wasi` that adheres to
[the WebAssembly component model](https://github.com/WebAssembly/component-model/blob/main/design/high-level/Goals.md),
and whose imports and exports use
[interface types](https://github.com/WebAssembly/interface-types)

91
docs/contributing.md Normal file
View File

@ -0,0 +1,91 @@
# Contributing to Spin
We are delighted that you are interested in making Spin better! Thank you! This
document will guide you in making your first contribution to the project.
First, any contribution and interaction on any Fermyon project MUST follow our
[code of conduct](https://www.fermyon.com/code-of-conduct). Thank you for being
part of an inclusive and open community!
We welcome and appreciate contributions of all types — opening issues, fixing
typos, adding examples, one-liner code fixes, tests, or complete features.
If you plan on contributing anything complex, please go through the issue and PR
queues first to make sure someone else has not started working on it. If it
doesn't exist already, please open an issue so you have a change to get feedback
from the community and the maintainers before you start working on your feature.
## Making code contributions to Spin
The following guide is intended to make sure your contribution can get merged as
soon as possible. First, make sure you have the following prerequisites
configured:
- [Rust](https://www.rust-lang.org/) at
[1.56+](https://www.rust-lang.org/tools/install) with the `wasm32-wasi` and
`wasm32-unknown-unknown` targets configured
(`rustup target add wasm32-wasi && rustup target add wasm32-unknown-unknown`)
- [`rustfmt`](https://github.com/rust-lang/rustfmt) and
[`clippy`](https://github.com/rust-lang/rust-clippy) configured for your Rust
installation
- `make`
- [Bindle server v0.8.0](https://github.com/deislabs/bindle/releases/tag/v0.8.0)
in your system path.
- if you are a VS Code user, we recommend the
[`rust-analyzer`](https://rust-analyzer.github.io/) and
[`autobindle`](https://github.com/fermyon/autobindle) extensions.
- please ensure you
[configure adding a GPG signature to your commits](https://docs.github.com/en/authentication/managing-commit-signature-verification/about-commit-signature-verification)
as well as appending a sign-off message (`git commit -S -s`)
Once you have set up the prerequisites and identified the contribution you want
to make to Spin, make sure you can correctly build the project:
```shell
# clone the repository
$ git clone https://github.com/fermyon/spin && cd spin
# add a new remote pointing to your fork of the project
$ git remote add fork https://github.com/<your-username>/spin
# create a new branch for your work
$ git checkout -b <your-branch>
# if you are making a documentation contribution,
# you can skip compiling and running the tests.
# build a release version of the Spin CLI
$ cargo build --release
# make sure compilation is successful
$ ./target/release/spin --help
# run the tests and make sure they pass
$ make test
```
Now you should be ready to start making your contribution. To familiarize
yourself with the Spin project, please read the
[architecture document](./architecture.md). Since most of Spin is implemented in
Rust, we try to follow the common Rust coding conventions (keep an eye on the
recommendations from Clippy!) If applicable, add units or integration tests to
ensure your contribution is correct.
Build the project and run the tests (`make build test`), and if everything is
successful, you should be ready to commit your changes. We try to follow the
[Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/)
guidelines for writing commit messages:
```shell
$ git commit -S -s -m "<your commit message that follows https://www.conventionalcommits.org/en/v1.0.0/>"
```
We try to only keep useful changes as separate commits — if you prefer to commit
often, please
[cleanup the commit history](https://git-scm.com/book/en/v2/Git-Tools-Rewriting-History)
before opening a pull request. Once you are happy with your changes you can push
the branch to your fork:
```shell
# "fork" is the name of the git remote pointing to your fork
$ git push fork
```
Now you are ready to create a pull request. Thank you for your contribution!

8
docs/index.md Normal file
View File

@ -0,0 +1,8 @@
# Index
1. [Introduction](./intro.md)
1. [Quickstart](./quickstart.md)
1. [Configuration for Spin applications](./configuration.md)
1. [Writing HTTP applications with Spin](./writing-http-apps.md)
1. [Architecture](./architecture.md)
1. [Contributing to Spin](./contributing.md)

24
docs/intro.md Normal file
View File

@ -0,0 +1,24 @@
# Introducing Spin
Spin is an open source framework for building and running fast, secure, and
composable cloud microservices with WebAssembly. It aims to be the easiest way
to get started with WebAssembly microservices, and takes advantage of the latest
developments in the
[WebAssembly component model](https://github.com/WebAssembly/component-model)
and [Wasmtime](https://wasmtime.dev/) runtime.
Spin offers a simple CLI that helps you create, distribute, and execute
applications, and in the next sections we will learn more about Spin
applications and how to get started.
## Spin applications
Spin applications are comprised of one or more _components_, and follow the
event-driven model — they are executed as the result of events being generated
by _triggers_ (for example an HTTP server receiving requests, or a queue
subscription receiving messages). On each new event, _the entrypoint_ of a
component is executed by Spin. The entrypoints to components are _functions_.
This, together with the fact that they are invoked in response to events, brings
the Spin application model closer to the Function-as-a-Service model.
In the next section, we will [take Spin for a spin](./quickstart.md).

163
docs/quickstart.md Normal file
View File

@ -0,0 +1,163 @@
# Taking Spin for a spin
## Getting the `spin` binary
You can download the [latest release](https://github.com/fermyon/spin/releases).
For example, for an M1 macOS machine:
```
$ wget https://github.com/fermyon/spin/releases/download/canary/spin-canary-macos-aarch64.tar.gz
$ tar xfv spin-canary-macos-aarch64.tar.gz
$ ./spin --help
```
> On an M1 macOS machine you might need to install / configure OpenSSL@1.1 by
> running
> `brew install openssl@1.1 && sudo ln -s /opt/homebrew/Cellar/openssl@1.1/1.1.1m /usr/local/openssl-aarch64`
Alternatively, if you want to build Spin from source,
[follow the contribution guide](./contributing.md) for a detailed guide on
getting started:
```shell
$ git clone https://github.com/fermyon/spin
$ cd spin && cargo build --release
$ ./target/release/spin --help
```
At this point, move the `spin` binary somewhere in your path, so it can be
accessed from any directory.
## Creating a new Spin HTTP application in Rust
First, we need to add the official Spin templates from the repository:
```
$ spin templates add --git https://github.com/fermyon/spin --name fermyon
$ spin templates list
+-----------------------------------------------------------------------------------+
| Name Repository URL Branch |
+===================================================================================+
| spin-http fermyon https://github.com/fermyon/bartholomew refs/heads/main |
+-----------------------------------------------------------------------------------+
```
Now we can create a new application from the template:
```
$ spin new --repo fermyon --template spin-http --path spin-hello-world
$ cd spin-hello-world
```
This generated all we need to build and run our very first Spin application.
Let's have a look at `spin.toml`:
```toml
apiVersion = "0.1.0"
name = "spin-hello-world"
description = "A simple application that returns hello world."
trigger = { type = "http", base = "/" }
version = "1.0.0"
[[component]]
id = "hello"
source = "target/wasm32-wasi/release/spinhelloworld.wasm"
[component.trigger]
route = "/hello"
```
Since this is an HTTP application, the application trigger is of `type = http`,
and there is one component that responds to requests on route `/hello` using the
`spinhelloworld.wasm` WebAssembly module. (See the
[configuration document](./configuration.md) for a detailed guide on the Spin
application configuration.)
Now let's have a look at the `hello` component — below is the complete source
code for a Spin HTTP component written in Rust. It is a regular Rust function
that takes an HTTP request and returns an HTTP response, annotated with the
`http_component` macro:
```rust
use anyhow::Result;
use spin_sdk::{
http::{Request, Response},
http_component,
};
/// A simple Spin HTTP component.
#[http_component]
fn hello_world(req: Request) -> Result<Response> {
println!("{:?}", req.headers());
Ok(http::Response::builder()
.status(200)
.header("foo", "bar")
.body(Some("Hello, Fermyon!".into()))?)
}
```
> See
> [the section on building HTTP applications with Spin for a detailed guide](./writing-http-apps.md).
We can build this component using the regular Rust toolchain, targeting
`wasm32-wasi`, which will produce the WebAssembly module referenced in
`spin.toml`:
```
$ cargo build --target wasm32-wasi --release
```
## Running the application with `spin up`
Now that we configured the application and built our component, we can _spin up_
the application (pun intended):
```shell
# optionally, use RUST_LOG=spin=trace to see detailed logs
$ spin up --file spin.toml
INFO spin_http_engine: Serving HTTP on address 127.0.0.1:3000
```
Spin will instantiate all components from the application configuration, and
will crate the router configuration for the HTTP trigger accordingly. The
component can now be invoked by making requests to `http://localhost:3000/hello`
(see route field in the configuration):
```
$ curl -i localhost:3000/hello
HTTP/1.1 200 OK
foo: bar
content-length: 15
Hello, Fermyon!
```
You can add as many components as needed in `spin.toml`, mount files and
directories, allow granular outbound HTTP connections, or environment variables.
(see the [configuration document](./configuration.md) for a detailed guide on
the Spin application configuration) and iterate locally with
`spin up --file spin.toml` until you are ready to distribute the application.
## Distributing the application
First, we need to start the registry. You can
[install the latest Bindle release](https://github.com/deislabs/bindle/tree/main/docs#from-the-binary-releases),
or use the
[`autobindle`](https://marketplace.visualstudio.com/items?itemName=fermyon.autobindle)
VS Code extension, which automatically downloads and starts Bindle on
`http://localhost:8080/v1`. Now we can package the entire application, the
components, and all the referenced files and publishes them to the registry:
```
$ export BINDLE_URL=http://localhost:8080/v1
$ spin bindle push --file spin.toml
pushed: spin-hello-world/1.0.0
```
Now we can run the application using `spin up` directly from the registry:
```
$ spin up --bindle spin-hello-world/1.0.0
```
Congratulations! You just completed writing, building, publishing, and running
your first Spin application.

View File

@ -1,17 +1,26 @@
# Cutting a Spin Release
# Creating a new Spin release
To cut a release of Spin, you will need to do the following:
1. Create a pull request that changes the version number for your new version (e.g. 0.1.0 becomes 0.1.1)
- `Cargo.toml` is the most important place to make this change
- Check the docs for hard-coded version strings
1. Merge the PR created in #1 (Such PRs are still required to get approvals, so make sure you get signoff on the PR)
1. Before proceeding, verify that the merge commit on `main` intended to be tagged is green, i.e. CI is successful
1. Create a pull request that changes the version number for your new version
(e.g. 0.1.0 becomes 0.1.1)
- `Cargo.toml` is the most important place to make this change
- Check the docs for hard-coded version strings
1. Merge the PR created in #1 (Such PRs are still required to get approvals, so
make sure you get signoff on the PR)
1. Before proceeding, verify that the merge commit on `main` intended to be
tagged is green, i.e. CI is successful
1. Create a new tag with a `v` and then the version number (`v0.1.1`)
1. Push the tag up to `main` on GitHub
- This will trigger a release build
1. Wait for the `release` [action](https://github.com/fermyon/spin/actions/workflows/release.yaml) to complete, and download the binary artifacts that are generated by that action.
1. Generate SHAs of the Windows, Mac (`amd64` and `aarch64`), and Linux (`amd64` and `aarch64`) binaries with `shasum -a 256` or a similar command
1. Go to the GitHub [tags page](https://github.com/fermyon/spin/releases) and create a release, adding release notes, and uploading the binaries you downloaded above. The SHAs should go in the release notes.
- This will trigger a release build
1. Wait for the `release`
[action](https://github.com/fermyon/spin/actions/workflows/release.yaml) to
complete, and download the binary artifacts that are generated by that
action.
1. Generate SHAs of the Windows, Mac (`amd64` and `aarch64`), and Linux (`amd64`
and `aarch64`) binaries with `shasum -a 256` or a similar command
1. Go to the GitHub [tags page](https://github.com/fermyon/spin/releases) and
create a release, adding release notes, and uploading the binaries you
downloaded above. The SHAs should go in the release notes.
At this point, you can just verify that all things are good.
At this point, you can verify in the GitHub UI that the release was successful.

View File

@ -1,154 +1,184 @@
# Writing HTTP applications using Spin
# Building HTTP applications using Spin
// TODO
Currently, the only applications that can be built with Spin are web based, or
applications that are invoked as the result of an HTTP request, and which return
an HTTP response. This is because HTTP workloads appear to be the most important
for event-driven Functions-as-a-Service workloads, and we think initially serve
the most popular use cases.
Let's take the following Spin application. It sets a base path, `/test`, and
there are two components, each serving requests for `/test/hello/...` and
`/test/wagi/...` respectively:
> The extensible nature of Spin allows anyone to extend it by building more
> triggers (see the [architecture](./architecture.md) and
> [contributing](./contributing.md) documents), and we are experimenting with a
> new trigger that invokes components for new payloads on a Redis message queue
> (see [#59](https://github.com/fermyon/spin/issues/59)).
```toml
name = "spin-hello-world"
trigger = { type = "http", base = "/test" }
Spin is built on top of the
[WebAssembly component model](https://github.com/WebAssembly/component-model).
We _strongly_ believe it represents the future of WebAssembly, and that it will
enable scenarios that are simply not possible today (for example dynamic linking
and transitive dependencies). As a result, the Spin HTTP trigger (and executor)
is defined using [WebAssembly interfaces](../wit/ephemeral), and the
[SDK for building Rust components](../sdk/rust) is built on top of the Rust
implementation and bindings generator for WebAssembly components.
[[component]]
source = "spin-module-that-prints-requests.wasm"
id = "hello"
[component.trigger]
route = "/hello/..."
But the WebAssembly component model is currently in its early stages. This means
only a few languages fully implement it. While language communities implement
the component model, we want to allow developers to use
[any language that compiles to WASI](https://www.fermyon.com/wasm-languages/webassembly-language-support)
to build Spin HTTP applications. This is why we currently implement a Wagi
executor which supports [Wagi](https://github.com/deislabs/wagi)-based
components that expect the HTTP request using the module's standard input, and
return the HTTP response using the module's standard output, following
[the CGI specification](https://tools.ietf.org/html/rfc3875). As a programming
language adds support for the component model, we plan to enable better support
for it in Spin, and eventually only support Spin applications that implement the
WebAssembly component model.
[[component]]
source = "env_wagi.wasm"
id = "wagi"
[component.trigger]
route = "/wagi/..."
executor = "wagi"
```
## Building HTTP components in Rust
Let's see how the application configuration above gets turned into the headers
by starting the application on `localhost:3000`.
We believe the Rust SDK offers the best experience for building Spin HTTP
components, and this is the recommended way of writing Spin components in Rust.
First, let's send a request to the `hello` component.
Building such a component in Rust requires writing a function that takes an HTTP
`Request` and returns an HTTP `Response`, annotated with a special Spin
procedural macro. Below is a complete component implementation:
```js
➜ curl 'localhost:3000/test/hello/abc/def?foo=bar' -d "abc"
Request {
method: Method::Post,
uri: "/test/hello/abc/def",
headers: [
(
"host",
"localhost:3000",
),
(
"user-agent",
"curl/7.77.0",
),
(
"accept",
"*/*",
),
(
"content-length",
"3",
),
(
"content-type",
"application/x-www-form-urlencoded",
),
(
"PATH_INFO",
"/abc/def",
),
(
"X_FULL_URL",
"http://localhost:3000/test/hello/abc/def?foo=bar",
),
(
"X_MATCHED_ROUTE",
"/test/hello/...",
),
(
"X_BASE_PATH",
"/test",
),
(
"X_RAW_COMPONENT_ROUTE",
"/hello/...",
),
(
"X_COMPONENT_ROUTE",
"/hello",
),
],
params: [
(
"foo",
"bar",
),
],
body: Some(
[
97,
98,
99,
],
),
```rust
use anyhow::Result;
use spin_sdk::{
http::{Request, Response},
http_component,
};
/// A simple Spin HTTP component.
#[http_component]
fn hello_world(req: Request) -> Result<Response> {
println!("{:?}", req.headers());
Ok(http::Response::builder()
.status(200)
.header("foo", "bar")
.body(Some("Hello, Fermyon!".into()))?)
}
```
Available in the request object are the following fields:
The important things to note in the function above:
- `method` — the HTTP method of the request — in this case, GET
- `uri` — the absolute path of the URI, _without_ the query parameters
- `params` — list of `(key, value)` pairs with the query parameters
- `headers` — list of `(key, value)` pairs with the headers (see the default
headers for a list of default headers and their meaning)
- body — optional byte array containing the request body
- the `spin_sdk::http_component` macro — this marks the function as the
entrypoint for the Spin component
- the function signature — `fn hello_world(req: Request) -> Result<Response>`
the Spin HTTP component uses the HTTP objects from the popular Rust crate
[`http`](https://crates.io/crates/http), and the request and response bodies
are optionally using [`bytes::Bytes`](https://crates.io/crates/bytes)
Now let's send a request to the Wagi component and inspect the environment
variables:
### Making outbound HTTP requests
```
➜ curl 'localhost:3000/test/wagi/abc/def?foo=bar' -d "abc"
### Arguments ###
This SDK includes the ability to send outbound HTTP requests using the
[DeisLabs WASI experimental HTTP library](https://github.com/deislabs/wasi-experimental-http).
Let's see an example where the component makes an outbound HTTP request to a
server, modifies the result, then returns it:
### Env Vars ###
QUERY_STRING = foo=bar
REMOTE_HOST = 127.0.0.1
AUTH_TYPE =
X_FULL_URL = http://localhost:3000/test/wagi/abc/def?foo=bar
PATH_TRANSLATED = /abc/def
SERVER_PORT = 3000
X_MATCHED_ROUTE = /test/wagi/...
SERVER_PROTOCOL = HTTP/1.1
CONTENT_TYPE =
SERVER_SOFTWARE = WAGI/1
HTTP_HOST = localhost:3000
HTTP_ACCEPT = */*
REMOTE_ADDR = 127.0.0.1
X_RAW_COMPONENT_ROUTE = /wagi/...
CONTENT_LENGTH = 3
SERVER_NAME = localhost
GATEWAY_INTERFACE = CGI/1.1
HTTP_CONTENT_LENGTH = 3
HTTP_CONTENT_TYPE = application/x-www-form-urlencoded
X_BASE_PATH = /test
HTTP_USER_AGENT = curl/7.77.0
X_COMPONENT_ROUTE = /wagi
REMOTE_USER =
PATH_INFO = /abc/def
REQUEST_METHOD = POST
X_RAW_PATH_INFO = /abc/def
SCRIPT_NAME = /test/wagi
```rust
#[http_component]
fn hello_world(_req: Request) -> Result<Response> {
let mut res = spin_sdk::http::send(
http::Request::builder()
.method("GET")
.uri("https://fermyon.com")
.body(None)?,
)?;
### STDIN ###
abc%
res.headers_mut()
.insert(http::header::SERVER, "spin/0.1.0".try_into()?);
Ok(res)
}
```
### The default headers
In order for the component above to be allowed to make the outbound HTTP
request, the destination host must be declared in the Spin application
configuration:
```toml
[[component]]
id = "hello"
source = "target/wasm32-wasi/release/spinhelloworld.wasm"
allowedHttpHosts = [ "https://fermyon.com" ]
[component.trigger]
route = "/hello"
```
Making a request to this component, we can see the appended header, and that the
response contains the expected body:
```shell
$ curl -I localhost:3000/hello
HTTP/1.1 200 OK
content-length: 29350
content-type: text/html; charset=utf-8
server: spin/0.1.0 # the header added by our component
```
Any Rust crate that compiles to `wasm32-wasi` can be used as dependency in Rust
components.
As the Spin framework evolves, the Spin SDK will continue adding functionality
that improves the experience for building Spin components (such as implementing
interfaces for popular functionality such as
[object storage](https://github.com/fermyon/spin/issues/48),
[key/value stores](https://github.com/fermyon/spin/issues/47), or
[neural networks](https://github.com/fermyon/spin/issues/50)).
As more languages support the WebAssembly component model, our goal is to
develop language SDKs for such popular languages.
## Building HTTP components using the Wagi executor
You can use any language that compiles to WASI to build an HTTP component using
the [Wagi](https://github.com/deislabs/wagi) executor.
Wagi is a project that lets you write HTTP handlers using nothing but a
language's standard library, following
[the CGI specification](https://tools.ietf.org/html/rfc3875).
For example, here is a complete Wagi component written in Swift:
```swift
print("content-type: text/html; charset=UTF-8\n\n");
print("hello world\n");
```
Here is another example, this time written in [Grain](https://grain-lang.org/),
a new programming language that natively targets WebAssembly:
```js
import Process from "sys/process";
import Array from "array";
print("content-type: text/plain\n");
// This will print all the Wagi env variable
print("==== Environment: ====");
Array.forEach(print, Process.env());
// This will print the route path followed by each query
// param. So /foo?bar=baz will be ["/foo", "bar=baz"].
print("==== Args: ====");
Array.forEach(print, Process.argv());
```
> You can find examples on how to build Wagi applications in
> [the DeisLabs GitHub organization](https://github.com/deislabs?q=wagi&type=public&language=&sort=).
In short, read HTTP headers from environment variables and the HTTP body from
standard input, and return the response to standard output. You can follow the
[Wagi guide](https://github.com/deislabs/wagi/blob/main/docs/writing_modules.md)
on writing modules (note that a module declaring its subroutes will not be
implemented in Spin).
## The default headers set in Spin HTTP components
Spin sets a few default headers on the request based on the base path, component
route, and request URI, which should always be available when writing a module:
route, and request URI, which will always be available when writing a module:
- `X_FULL_URL` - the full URL of the request —
`http://localhost:3000/test/wagi/abc/def?foo=bar`
@ -167,5 +197,3 @@ route, and request URI, which should always be available when writing a module:
Besides the headers above, components that use the Wagi executor also have
available
[all headers set by Wagi, following the CGI spec](https://github.com/deislabs/wagi/blob/main/docs/environment_variables.md).
### The HTTP headers

180
readme.md
View File

@ -1,165 +1,45 @@
<div align="center">
<h1>Project Spin</h1>
<h1>Spin</h1>
<img src="./docs/images/spin.png" width="300"/>
<p>Spin is a tool that allows developers to build, publish, and deploy WebAssembly workloads. It is the next version of the Fermyon runtime.</p>
<p>Spin is a framework for building, deploying, and running fast, secure, and composable cloud microservices with WebAssembly.</p>
</div>
## Take Spin for a spin
## What is Spin?
* [Take Spin for a spin](#take-spin-for-a-spin)
* [Build Spin CLI](#build-spin-cli)
* [Build and Run an HTTP Application with Spin](#build-and-run-an-http-application-with-spin)
* [Generate an HTTP Application Using a Spin Template](#generate-an-http-application-using-a-spin-template)
* [Build the Application](#build-the-application)
* [Run the Application Locally](#run-the-application-locally)
* [Publishing Interfaces](#publishing-interfaces)
* [Publish the Spin HTTP Interface](#publish-the-spin-http-interface)
* [Use Interface in HTTP Application](#use-interface-in-http-application)
Spin is an open source framework for building and running fast, secure, and
composable cloud microservices with WebAssembly. It aims to be the easiest way
to get started with WebAssembly microservices, and takes advantage of the latest
developments in the
[WebAssembly component model](https://github.com/WebAssembly/component-model)
and [Wasmtime](https://wasmtime.dev/) runtime.
## Build Spin CLI
Spin offers a simple CLI that helps you create, distribute, and execute
applications, and in the next sections we will learn more about Spin
applications and how to get started.
Clone this repository and build the Spin CLI:
## Getting started
```shell
$ git clone https://github.com/fermyon/spin
$ cd spin && cargo build --release
See the [quickstart document](./docs/quickstart.md) for a detailed guide on
configuring Spin and writing your first Spin application, but in short:
```
$ wget https://github.com/fermyon/spin/releases/download/canary/spin-canary-<os-arch>.tar.gz
$ tar xfv spin-canary-<os-arch>.tar.gz
$ ./spin --help
```
## Build and Run an HTTP Application with Spin
After you follow the [quickstart document](./docs/quickstart.md), you can follow
the [guide on writing HTTP applications with Spin](./docs/writing-http-apps.md)
and the [guide on configuring Spin applications](./docs/configuration.md).
### Generate an HTTP Application Using a Spin Template
After you built your application, run it using Spin, pointing to the Spin
application configuration file:
Add a new Spin template based on the `templates/spin-http` directory from this
repo:
```shell
$ spin templates add --local templates/spin-http --name spin-http
$ spin templates list
+---------------------------------------+
| Name Repository URL Branch |
+=======================================+
| spin-http local |
+---------------------------------------+
```
$ spin up --file spin.toml
```
Create the application:
## Contributing
```shell
$ mkdir helloworld
# TODO: the name and path where the app is generated is wrong.
$ spin new --repo local --template spin-http --path .
```
### Build the Application
In the application directory:
```shell
$ cargo build --release
```
### Run the Application Locally
The configuration file `spin.toml` contains the information required for Spin to
run the application locally:
```shell
$ export RUST_LOG=spin_engine=info,spin_http,wact=info
$ spin up --app spin.toml
2022-02-06T02:44:08.810806Z INFO spin_http_engine: Processing request for application spin-hello-world on path /hello
2022-02-06T02:44:08.810897Z INFO execute{component="hello"}: spin_http_engine: Executing request for component hello
2022-02-06T02:44:08.810918Z INFO execute{component="hello"}: prepare_component{component="hello"}: spin_engine: Preparing component hello
2022-02-06T02:44:08.810936Z INFO execute{component="hello"}: prepare_component{component="hello"}: store: spin_engine: Creating store.
2022-02-06T02:44:08.811318Z INFO execute{component="hello"}: spin_http_engine: Request URI: "/hello"
2022-02-06T02:44:08.811553Z INFO execute{component="hello"}: spin_http_engine: Response status code: 200
2022-02-06T02:44:08.811715Z INFO execute{component="hello"}: spin_http_engine: Request finished, sending response.
```
The application is now ready, after starting, send a request using
`curl -i localhost:3000/hello`:
```console
$ curl -i localhost:3000/hello
HTTP/1.1 200 OK
content-length: 12
date: Sun, 06 Feb 2022 02:44:08 GMT
I'm a teapot
```
## Publishing Interfaces
In the example above, the interface (`.wit` file) was copied over to the local
HTTP application directory. You can also publish interfaces to a bindle registry
for others to consume as well as pull interfaces from a bindle registry to use.
The example below creates and publishes the spin http interface and then walks
through how to consume it in the HTTP application from the previous example.
### Publish the Spin HTTP Interface
Push the Spin HTTP interface to the registry (from the root of this repository).
This step, together with starting the registry, will not be required once we set
up a canonical registry instance:
```shell
$ wact interface publish --name fermyon/http --version 0.1.0 wit/ephemeral/spin-http.wit
```
### Use Interface in HTTP Application
1. Update `Cargo.toml` to include the following dependency, component and
interface information:
```toml
[...]
[dependencies]
# The Wact dependency generates bindings that simplify working with interfaces.
wact = { git = "https://github.com/fermyon/wact", rev = "93a9eaeba9205918dc214a6310c0bb6e33c0e3c8" }
[workspace]
# Metadata about this component.
[package.metadata.component]
name = "spinhelloworld"
# This component implements the fermyon/http interface.
[package.metadata.component.exports]
fermyon-http = { name = "fermyon/http", version = "0.1.0" }
```
2. Update the application to use wact to generate and use rust bindings. In
`src/lib.rs`:
```rust
// Import the HTTP objects from the generated bindings.
use fermyon_http::{Request, Response};
// Generate Rust bindings for all interfaces in Cargo.toml.
wact::component!();
struct FermyonHttp {}
impl fermyon_http::FermyonHttp for FermyonHttp {
// Implement the `handler` entrypoint for Spin HTTP components.
fn handler(req: Request) -> Response {
println!("Request: {:?}", req);
Response {
status: 418,
headers: None,
body: Some("I'm a teapot".as_bytes().to_vec()),
}
}
}
```
3. Remove `*.wit` files from local HTTP application directory
4. In the application directory, build the component:
```shell
$ cargo build --target wasm32-wasi --release
# OR
$ cargo component build --release
```
[Run the application locally](#run-the-application-locally) to test.
We are delighted that you are interested in making Spin better! Thank you!
Please follow the [contributing guide](./docs/contributing.md).

View File

@ -22,7 +22,7 @@ fn hello_world(req: Request) -> Result<Response> {
Ok(http::Response::builder()
.status(200)
.header("foo", "bar")
.body(Some("Hello, Fermyon".into()))?)
.body(Some("Hello, Fermyon!".into()))?)
}
```
@ -46,7 +46,7 @@ server, modifies the result, then returns it:
```rust
#[http_component]
fn hello_world(_req: Request) -> Result<Response> {
let mut res = spin_sdk::outbound_http::send_request(
let mut res = spin_sdk::http::send(
http::Request::builder()
.method("GET")
.uri("https://fermyon.com")
@ -54,10 +54,11 @@ fn hello_world(_req: Request) -> Result<Response> {
)?;
res.headers_mut()
.insert(header::SERVER, "spin/0.1.0".try_into()?);
.insert(http::header::SERVER, "spin/0.1.0".try_into()?);
Ok(res)
}
```
In order for the component above to be allowed to make the outbound HTTP
@ -81,6 +82,5 @@ $ curl -I localhost:3000/hello
HTTP/1.1 200 OK
content-length: 29350
content-type: text/html; charset=utf-8
date: Fri, 04 Mar 2022 23:06:43 GMT
server: spin/0.1.0
```

View File

@ -20,6 +20,9 @@ pub mod http {
/// The Spin HTTP response.
pub type Response = http::Response<Option<bytes::Bytes>>;
/// Directly expose the ability to send an HTTP request.
pub use crate::outbound_http::send_request as send;
/// Helper function to return a 404 Not Found response.
pub fn not_found() -> Result<Response> {
Ok(http::Response::builder()

View File

@ -2,12 +2,11 @@ apiVersion = "0.1.0"
authors = ["Fermyon Engineering <engineering@fermyon.com>"]
description = "A simple application that returns hello."
name = "spin-hello-world"
trigger = { type = "http", base = "/" }
trigger = { type = "http" }
version = "1.0.0"
[[component]]
id = "hello"
source = "target/wasm32-wasi/release/spinhelloworld.wasm"
# allowedHttpHosts = [ "https://fermyon.com" ]
[component.trigger]
route = "/hello"