diff --git a/.build/check-broken-links.sh b/.build/check-broken-links.sh index 6df4e2df..e3be0786 100755 --- a/.build/check-broken-links.sh +++ b/.build/check-broken-links.sh @@ -90,6 +90,9 @@ if ! blc -rv --exclude 'https://marketplace.visualstudio.com/items?itemName=BytecodeAlliance.starlingmonkey-debugger' \ --exclude 'http://localhost:16686/' \ --exclude 'http://localhost:5050/explore' \ + `## Spin 4 bits not yet published` \ + --exclude 'https://github.com/spinframework/spin-docs/blob/main/content/v4' \ + --exclude 'https://docs.rs/spin-sdk/latest/spin_sdk' \ http://127.0.0.1:3000/v3/javascript-components \ | tee "${report}" then diff --git a/content/v3/http-outbound.md b/content/v3/http-outbound.md index f4951426..087cb153 100644 --- a/content/v3/http-outbound.md +++ b/content/v3/http-outbound.md @@ -28,18 +28,18 @@ The outbound HTTP interface depends on your language. {{ startTab "Rust"}} -> [**Want to go straight to the reference documentation?** Find it here.](https://docs.rs/spin-sdk/latest/spin_sdk/http/index.html) +> [**Want to go straight to the reference documentation?** Find it here.](https://docs.rs/spin-sdk/5.2.0/spin_sdk/http/index.html) -To send requests, use the [`spin_sdk::http::send`](https://docs.rs/spin-sdk/latest/spin_sdk/http/fn.send.html) function. This takes a request argument and returns a response (or error). It is `async`, so within an async inbound handler you can have multiple outbound `send`s running concurrently. +To send requests, use the [`spin_sdk::http::send`](https://docs.rs/spin-sdk/5.2.0/spin_sdk/http/fn.send.html) function. This takes a request argument and returns a response (or error). It is `async`, so within an async inbound handler you can have multiple outbound `send`s running concurrently. > Support for streaming request and response bodies is **experimental**. We currently recommend that you stick with the simpler non-streaming interfaces if you don't require streaming. `send` is quite flexible in its request and response types. The request may be: * [`http::Request`](https://docs.rs/http/latest/http/request/struct.Request.html) - typically constructed via `http::Request::builder()` -* [`spin_sdk::http::Request`](https://docs.rs/spin-sdk/latest/spin_sdk/http/struct.Request.html) - typically constructed via `spin_sdk::http::Request::get()`, `spin_sdk::http::Request::post()`, or `spin_sdk::http::Request::builder()` - * You can also use the [builder type](https://docs.rs/spin-sdk/latest/spin_sdk/http/struct.RequestBuilder.html) directly - `build` will be called automatically for you -* [`spin_sdk::http::OutgoingRequest`](https://docs.rs/spin-sdk/latest/spin_sdk/http/struct.OutgoingRequest.html) - constructed via `OutgoingRequest::new()` +* [`spin_sdk::http::Request`](https://docs.rs/spin-sdk/5.2.0/spin_sdk/http/struct.Request.html) - typically constructed via `spin_sdk::http::Request::get()`, `spin_sdk::http::Request::post()`, or `spin_sdk::http::Request::builder()` + * You can also use the [builder type](https://docs.rs/spin-sdk/5.2.0/spin_sdk/http/struct.RequestBuilder.html) directly - `build` will be called automatically for you +* [`spin_sdk::http::OutgoingRequest`](https://docs.rs/spin-sdk/5.2.0/spin_sdk/http/struct.OutgoingRequest.html) - constructed via `OutgoingRequest::new()` * Any type for which you have implemented the `TryInto` trait Generally, you should use `OutgoingRequest` when you need to stream the outbound request body; otherwise, the `Request` types are usually simpler. @@ -47,9 +47,9 @@ Generally, you should use `OutgoingRequest` when you need to stream the outbound The response may be: * [`http::Response`](https://docs.rs/http/latest/http/response/struct.Response.html) -* [`spin_sdk::http::Response`](https://docs.rs/spin-sdk/latest/spin_sdk/http/struct.Response.html) -* [`spin_sdk::http::IncomingResponse`](https://docs.rs/spin-sdk/latest/spin_sdk/http/struct.IncomingResponse.html) -* Any type for which you have implemented the [spin_sdk::http::conversions::TryFromIncomingResponse](https://docs.rs/spin-sdk/latest/spin_sdk/http/conversions/trait.TryFromIncomingResponse.html) trait +* [`spin_sdk::http::Response`](https://docs.rs/spin-sdk/5.2.0/spin_sdk/http/struct.Response.html) +* [`spin_sdk::http::IncomingResponse`](https://docs.rs/spin-sdk/5.2.0/spin_sdk/http/struct.IncomingResponse.html) +* Any type for which you have implemented the [spin_sdk::http::conversions::TryFromIncomingResponse](https://docs.rs/spin-sdk/5.2.0/spin_sdk/http/conversions/trait.TryFromIncomingResponse.html) trait Generally, you should use `IncomingResponse` when you need to stream the response body; otherwise, the `Response` types are usually simpler. diff --git a/content/v3/http-trigger.md b/content/v3/http-trigger.md index 40aa0fd4..ff5f0e15 100644 --- a/content/v3/http-trigger.md +++ b/content/v3/http-trigger.md @@ -154,9 +154,9 @@ The exact signature of the HTTP handler, and how a function is identified to be {{ startTab "Rust"}} -> [**Want to go straight to the reference documentation?** Find it here.](https://docs.rs/spin-sdk/latest/spin_sdk/http/index.html) +> [**Want to go straight to the reference documentation?** Find it here.](https://docs.rs/spin-sdk/5.2.0/spin_sdk/http/index.html) -In Rust, the handler is identified by the [`#[spin_sdk::http_component]`](https://docs.rs/spin-sdk/latest/spin_sdk/attr.http_component.html) attribute. The handler function can have one of two forms: _request-response_ or _input-output parameter_. +In Rust, the handler is identified by the [`#[spin_sdk::http_component]`](https://docs.rs/spin-sdk/5.2.0/spin_sdk/attr.http_component.html) attribute. The handler function can have one of two forms: _request-response_ or _input-output parameter_. **Request-Response Handlers** @@ -174,15 +174,15 @@ In this form, nothing is sent to the client until the entire response is ready. You have some flexibility in choosing the types of the request and response. The request may be: * [`http::Request`](https://docs.rs/http/latest/http/request/struct.Request.html) -* [`spin_sdk::http::Request`](https://docs.rs/spin-sdk/latest/spin_sdk/http/struct.Request.html) -* [`spin_sdk::http::IncomingRequest`](https://docs.rs/spin-sdk/latest/spin_sdk/http/struct.IncomingRequest.html) -* Any type for which you have implemented the [`spin_sdk::http::conversions::TryFromIncomingRequest`](https://docs.rs/spin-sdk/latest/spin_sdk/http/conversions/trait.TryFromIncomingRequest.html) trait +* [`spin_sdk::http::Request`](https://docs.rs/spin-sdk/5.2.0/spin_sdk/http/struct.Request.html) +* [`spin_sdk::http::IncomingRequest`](https://docs.rs/spin-sdk/5.2.0/spin_sdk/http/struct.IncomingRequest.html) +* Any type for which you have implemented the [`spin_sdk::http::conversions::TryFromIncomingRequest`](https://docs.rs/spin-sdk/5.2.0/spin_sdk/http/conversions/trait.TryFromIncomingRequest.html) trait The response may be: * [`http::Response`](https://docs.rs/http/latest/http/response/struct.Response.html) - typically constructed via `Response::builder()` -* [`spin_sdk::http::Response`](https://docs.rs/spin-sdk/latest/spin_sdk/http/struct.Response.html) - typically constructed via a [`ResponseBuilder`](https://docs.rs/spin-sdk/latest/spin_sdk/http/struct.ResponseBuilder.html) -* Any type for which you have implemented the [`spin_sdk::http::IntoResponse`](https://docs.rs/spin-sdk/latest/spin_sdk/http/trait.IntoResponse.html) trait +* [`spin_sdk::http::Response`](https://docs.rs/spin-sdk/5.2.0/spin_sdk/http/struct.Response.html) - typically constructed via a [`ResponseBuilder`](https://docs.rs/spin-sdk/5.2.0/spin_sdk/http/struct.ResponseBuilder.html) +* Any type for which you have implemented the [`spin_sdk::http::IntoResponse`](https://docs.rs/spin-sdk/5.2.0/spin_sdk/http/trait.IntoResponse.html) trait * A `Result` where the success type is one of the above and the error type is `anyhow::Error` or another error type for which you have implemented `spin_sdk::http::IntoResponse` (such as `anyhow::Result`) For example: @@ -208,11 +208,11 @@ To extract data from the request, specify a body type as the generic parameter f **Input-Output Parameter Handlers** -In this form, the handler function receives the request as an argument of type [`spin_sdk::http::IncomingRequest`](https://docs.rs/spin-sdk/latest/spin_sdk/http/struct.IncomingRequest.html). It also receives an argument of type [`spin_sdk::http::ResponseOutparam`](https://docs.rs/spin-sdk/latest/spin_sdk/http/struct.ResponseOutparam.html), through which is sends the response. The function does not return a value. This form is recommended for streaming responses. +In this form, the handler function receives the request as an argument of type [`spin_sdk::http::IncomingRequest`](https://docs.rs/spin-sdk/5.2.0/spin_sdk/http/struct.IncomingRequest.html). It also receives an argument of type [`spin_sdk::http::ResponseOutparam`](https://docs.rs/spin-sdk/5.2.0/spin_sdk/http/struct.ResponseOutparam.html), through which is sends the response. The function does not return a value. This form is recommended for streaming responses. To send a response: -1. Create a [`spin_sdk::http::OutgoingResponse`](https://docs.rs/spin-sdk/latest/spin_sdk/http/struct.OutgoingResponse.html). +1. Create a [`spin_sdk::http::OutgoingResponse`](https://docs.rs/spin-sdk/5.2.0/spin_sdk/http/struct.OutgoingResponse.html). 2. Call `take_body()` on the `OutgoingResponse` - this gives you a [`futures::Sink`](https://docs.rs/futures/latest/futures/sink/trait.Sink.html) that you can later use to send data via the response. 3. Call `set` on the `ResponseOutparam`, passing the `OutgoingResponse`. 4. Call `send` on the `Sink` as many times as you like. Each send is carried out as you call it, so you can send the first part of the response without waiting for the whole response to be ready. @@ -251,7 +251,7 @@ async fn handle_hello_rust(_req: IncomingRequest, response_out: ResponseOutparam } ``` -For a full Rust SDK reference, see the [Rust Spin SDK documentation](https://docs.rs/spin-sdk/latest/spin_sdk/index.html). +For a full Rust SDK reference, see the [Rust Spin SDK documentation](https://docs.rs/spin-sdk/5.2.0/spin_sdk/index.html). {{ blockEnd }} diff --git a/content/v3/kv-store-api-guide.md b/content/v3/kv-store-api-guide.md index aa6711fd..90990cf0 100644 --- a/content/v3/kv-store-api-guide.md +++ b/content/v3/kv-store-api-guide.md @@ -40,7 +40,7 @@ The exact detail of calling these operations from your application depends on yo {{ startTab "Rust"}} -> [**Want to go straight to the reference documentation?** Find it here.](https://docs.rs/spin-sdk/latest/spin_sdk/key_value/index.html) +> [**Want to go straight to the reference documentation?** Find it here.](https://docs.rs/spin-sdk/5.2.0/spin_sdk/key_value/index.html) Key value functions are available in the `spin_sdk::key_value` module. The function names match the operations above. For example: diff --git a/content/v3/language-support-overview.md b/content/v3/language-support-overview.md index c633fac4..00068b6b 100644 --- a/content/v3/language-support-overview.md +++ b/content/v3/language-support-overview.md @@ -13,7 +13,7 @@ This page contains information about language support for Spin features: {{ startTab "Rust"}} -**[πŸ“„ Visit the Rust Spin SDK reference documentation](https://docs.rs/spin-sdk/latest/spin_sdk/) to see specific modules, functions, variables and syntax relating to the following Rust features.** +**[πŸ“„ Visit the Rust Spin SDK reference documentation](https://docs.rs/spin-sdk/5.2.0/spin_sdk/) to see specific modules, functions, variables and syntax relating to the following Rust features.** | Feature | SDK Supported? | |-----|-----| diff --git a/content/v3/mqtt-outbound.md b/content/v3/mqtt-outbound.md index 0bba8769..7dc1e06e 100644 --- a/content/v3/mqtt-outbound.md +++ b/content/v3/mqtt-outbound.md @@ -32,7 +32,7 @@ The exact detail of calling these operations from your application depends on yo {{ startTab "Rust"}} -> [**Want to go straight to the reference documentation?** Find it here.](https://docs.rs/spin-sdk/latest/spin_sdk/mqtt/index.html) +> [**Want to go straight to the reference documentation?** Find it here.](https://docs.rs/spin-sdk/5.2.0/spin_sdk/mqtt/index.html) MQTT functions are available in the `spin_sdk::mqtt` module. @@ -49,7 +49,7 @@ let cat_picture: Vec = request.body().to_vec(); connection.publish("pets", &cat_picture, spin_sdk::mqtt::Qos::AtLeastOnce)?; ``` -For full details of the MQTT API, see the [Spin SDK reference documentation](https://docs.rs/spin-sdk/latest/spin_sdk/mqtt/index.html); +For full details of the MQTT API, see the [Spin SDK reference documentation](https://docs.rs/spin-sdk/5.2.0/spin_sdk/mqtt/index.html); You can find a complete Rust code example for using outbound MQTT from an HTTP component in the [Spin Rust SDK repository on GitHub](https://github.com/spinframework/spin-rust-sdk/tree/main/examples/mqtt-outbound). diff --git a/content/v3/rdbms-storage.md b/content/v3/rdbms-storage.md index bff26c18..6b0a6fbe 100644 --- a/content/v3/rdbms-storage.md +++ b/content/v3/rdbms-storage.md @@ -37,7 +37,7 @@ The exact detail of calling these operations from your application depends on yo {{ startTab "Rust"}} -> [**Want to go straight to the reference documentation?** Find it here.](https://docs.rs/spin-sdk/latest/spin_sdk/index.html) +> [**Want to go straight to the reference documentation?** Find it here.](https://docs.rs/spin-sdk/5.2.0/spin_sdk/index.html) MySQL functions are available in the `spin_sdk::mysql` module, and PostgreSQL functions in the `spin_sdk::pg4` module. @@ -73,7 +73,7 @@ match rowset.rows.first() { You can find complete examples for using relational databases in the Spin repository on GitHub ([MySQL](https://github.com/spinframework/spin-rust-sdk/tree/main/examples/mysql), [PostgreSQL](https://github.com/spinframework/spin-rust-sdk/tree/main/examples/postgres)). -For full information about the MySQL and PostgreSQL APIs, see [the Spin SDK reference documentation](https://docs.rs/spin-sdk/latest/spin_sdk/index.html). +For full information about the MySQL and PostgreSQL APIs, see [the Spin SDK reference documentation](https://docs.rs/spin-sdk/5.2.0/spin_sdk/index.html). {{ blockEnd }} diff --git a/content/v3/redis-outbound.md b/content/v3/redis-outbound.md index 944b1297..5f14ab60 100644 --- a/content/v3/redis-outbound.md +++ b/content/v3/redis-outbound.md @@ -41,7 +41,7 @@ The exact detail of calling these operations from your application depends on yo {{ startTab "Rust"}} -> [**Want to go straight to the reference documentation?** Find it here.](https://docs.rs/spin-sdk/latest/spin_sdk/redis/index.html) +> [**Want to go straight to the reference documentation?** Find it here.](https://docs.rs/spin-sdk/5.2.0/spin_sdk/redis/index.html) Redis functions are available in the `spin_sdk::redis` module. @@ -58,7 +58,7 @@ connection.set("my-key", &"my-value".into()); let data = connection.get("my-key")?; ``` -For full details of the Redis API, see the [Spin SDK reference documentation](https://docs.rs/spin-sdk/latest/spin_sdk/redis/index.html); +For full details of the Redis API, see the [Spin SDK reference documentation](https://docs.rs/spin-sdk/5.2.0/spin_sdk/redis/index.html); **General Notes** diff --git a/content/v3/redis-trigger.md b/content/v3/redis-trigger.md index 0f44773d..90b8f8ed 100644 --- a/content/v3/redis-trigger.md +++ b/content/v3/redis-trigger.md @@ -73,7 +73,7 @@ The exact signature of the Redis handler, and how a function is identified to be {{ startTab "Rust"}} -> [**Want to go straight to the reference documentation?** Find it here.](https://docs.rs/spin-sdk/latest/spin_sdk/index.html) +> [**Want to go straight to the reference documentation?** Find it here.](https://docs.rs/spin-sdk/5.2.0/spin_sdk/index.html) In Rust, the handler is identified by the `#[spin_sdk::redis_component]` attribute. It takes a `bytes::Bytes`, representing the raw payload of the Redis message, and returns an `anyhow::Result` indicating success or an error with details. This example just logs the payload as a string: diff --git a/content/v3/rust-components.md b/content/v3/rust-components.md index f8f7ec36..34b278e2 100644 --- a/content/v3/rust-components.md +++ b/content/v3/rust-components.md @@ -35,7 +35,7 @@ official resources for learning Rust](https://www.rust-lang.org/learn). > All examples from this page can be found in [the Spin Rust SDK repository on GitHub](https://github.com/spinframework/spin-rust-sdk/tree/main/examples). -[**Want to go straight to the Spin SDK reference documentation?** Find it here.](https://docs.rs/spin-sdk/latest/spin_sdk/index.html) +[**Want to go straight to the Spin SDK reference documentation?** Find it here.](https://docs.rs/spin-sdk/5.2.0/spin_sdk/index.html) ## Prerequisites @@ -100,7 +100,7 @@ for writing Spin components with the Spin Rust SDK. > Make sure to read [the page describing the HTTP trigger](./http-trigger.md) for more > details about building HTTP applications. -Building a Spin HTTP component using the Rust SDK means writing a single function decorated with the [`#[http_component]`](https://docs.rs/spin-sdk/latest/spin_sdk/attr.http_component.html) attribute. The function can have one of two forms: +Building a Spin HTTP component using the Rust SDK means writing a single function decorated with the [`#[http_component]`](https://docs.rs/spin-sdk/5.2.0/spin_sdk/attr.http_component.html) attribute. The function can have one of two forms: * takes an HTTP request as a parameter, and returns an HTTP response β€” shown below * taken as parameters _both_ the HTTP request and an object through which to write a response - see [the HTTP trigger page](./http-trigger#authoring-http-components) for an example. @@ -122,9 +122,9 @@ async fn handle_hello_rust(_req: Request) -> anyhow::Result { The important things to note in the implementation above: -- the [`spin_sdk::http_component`](https://docs.rs/spin-sdk/latest/spin_sdk/attr.http_component.html) macro marks the function as the entry point for the Spin component +- the [`spin_sdk::http_component`](https://docs.rs/spin-sdk/5.2.0/spin_sdk/attr.http_component.html) macro marks the function as the entry point for the Spin component - the function signature β€” `fn hello_world(req: Request) -> Result` β€” - the Spin HTTP component allows for a flexible set of response types via the [`IntoResponse`](https://docs.rs/spin-sdk/latest/spin_sdk/http/trait.IntoResponse.html) trait, including the SDK's `Response` type and the `Response` type from the Rust [`http` crate](https://crates.io/crates/http). See the section on [using the `http` crate](#using-the-http-crate) for more information. + the Spin HTTP component allows for a flexible set of response types via the [`IntoResponse`](https://docs.rs/spin-sdk/5.2.0/spin_sdk/http/trait.IntoResponse.html) trait, including the SDK's `Response` type and the `Response` type from the Rust [`http` crate](https://crates.io/crates/http). See the section on [using the `http` crate](#using-the-http-crate) for more information. > If you're familiar with Spin 1.x, you will see some changes when upgrading to the Spin 2 SDK. Mostly these provide more flexibility, but you will likely need to change some details such as module paths. If you don't want to modify your code, you can continue using the 1.x SDK - your components will still run. @@ -347,7 +347,7 @@ mod api { Handlers within a `Router` can be sync or async. Use `Router`'s "plain" methods (e.g. `get`, `post`) to assign synchronous handlers, and its "async" methods (e.g. `get_async`, `post_async`) for asynchronous handlers. You can mix sync and async handlers in the same `Router`, and can use `handle` or `handle_async` to invoke `Router` processing, regardless of whether invididual handlers are sync or async. -> For further reference, see the [Spin SDK HTTP router](https://docs.rs/spin-sdk/latest/spin_sdk/http/struct.Router.html). +> For further reference, see the [Spin SDK HTTP router](https://docs.rs/spin-sdk/5.2.0/spin_sdk/http/struct.Router.html). ## Storing Data in Redis From Rust Components @@ -506,7 +506,7 @@ HTTP/1.1 200 OK 0x1234 ``` -For more information on the Rust key-value API see [the Spin SDK documentation](https://docs.rs/spin-sdk/latest/spin_sdk/key_value/index.html). +For more information on the Rust key-value API see [the Spin SDK documentation](https://docs.rs/spin-sdk/5.2.0/spin_sdk/key_value/index.html). ## Storing Data in SQLite @@ -611,4 +611,4 @@ spin-sdk = { git = "https://github.com/spinframework/spin" } ## Read the Rust Spin SDK Documentation -Although you learned a lot by following the concepts and samples shown here, you can dive even deeper and read the [Rust Spin SDK documentation](https://docs.rs/spin-sdk/latest/spin_sdk/index.html). +Although you learned a lot by following the concepts and samples shown here, you can dive even deeper and read the [Rust Spin SDK documentation](https://docs.rs/spin-sdk/5.2.0/spin_sdk/index.html). diff --git a/content/v3/serverless-ai-api-guide.md b/content/v3/serverless-ai-api-guide.md index 47bc168b..f4b053cc 100644 --- a/content/v3/serverless-ai-api-guide.md +++ b/content/v3/serverless-ai-api-guide.md @@ -68,7 +68,7 @@ The exact detail of calling these operations from your application depends on yo {{ startTab "Rust"}} -> [**Want to go straight to the reference documentation?** Find it here.](https://docs.rs/spin-sdk/latest/spin_sdk/llm/index.html) +> [**Want to go straight to the reference documentation?** Find it here.](https://docs.rs/spin-sdk/5.2.0/spin_sdk/llm/index.html) To use Serverless AI functions, the `llm` module from the Spin SDK provides the methods. The following snippet is from the [Rust code generation example](https://github.com/fermyon/ai-examples/tree/main/code-generator-rs): diff --git a/content/v3/sqlite-api-guide.md b/content/v3/sqlite-api-guide.md index 6088f519..e3eeee66 100644 --- a/content/v3/sqlite-api-guide.md +++ b/content/v3/sqlite-api-guide.md @@ -54,7 +54,7 @@ serde = {version = "1.0", features = ["derive"]} serde_json = "1.0" ``` -> [**Want to go straight to the reference documentation?** Find it here.](https://docs.rs/spin-sdk/latest/spin_sdk/sqlite3/index.html) +> [**Want to go straight to the reference documentation?** Find it here.](https://docs.rs/spin-sdk/5.2.0/spin_sdk/sqlite3/index.html) SQLite functions are available in the `spin_sdk::sqlite3` module diff --git a/content/v3/variables.md b/content/v3/variables.md index 181320a9..3ba08f87 100644 --- a/content/v3/variables.md +++ b/content/v3/variables.md @@ -103,7 +103,7 @@ The exact details of calling the config SDK from a Spin application depends on t {{ startTab "Rust"}} -> [**Want to go straight to the reference documentation?** Find it here.](https://docs.rs/spin-sdk/latest/spin_sdk/variables/index.html) +> [**Want to go straight to the reference documentation?** Find it here.](https://docs.rs/spin-sdk/5.2.0/spin_sdk/variables/index.html) The interface is available in the `spin_sdk::variables` module and is named `get`. diff --git a/content/v4/api-guides-overview.md b/content/v4/api-guides-overview.md new file mode 100644 index 00000000..3cbbd048 --- /dev/null +++ b/content/v4/api-guides-overview.md @@ -0,0 +1,55 @@ +title = "API Support Overview" +template = "main" +date = "2023-11-04T00:00:01Z" +[extra] +url = "https://github.com/spinframework/spin-docs/blob/main/content/v4/api-guides-overview.md" + +--- +- [Asynchronous and Blocking APIs](#asynchronous-and-blocking-apis) +- [Targeting a Deployment Environment](#targeting-a-deployment-environment) + +The following table shows the status of the interfaces Spin provides to applications. + +| Host Capabilities/Interfaces | Stability | Async? (see NOTE) | +|----------------------------------------------|--------------|---------------------| +| [HTTP Trigger](./http-trigger) | Stable | Async entry point | +| [Redis Trigger](./redis-trigger) | Stable | Async entry point | +| [Cron Trigger](./triggers) | Experimental | Sync entry point | +| [Outbound HTTP](./http-outbound) | Stable | Async | +| [Outbound Redis](./redis-outbound) | Stable | Async | +| [Configuration Variables](./variables) | Stable | Async | +| [PostgreSQL](./rdbms-storage) | Stable | Async | +| [MySQL](./rdbms-storage) | Experimental | Blocking | +| [Key-value Storage](./kv-store-api-guide) | Stable | Async | +| [Serverless AI](./serverless-ai-api-guide) | Experimental | Blocking | +| [SQLite Storage](./sqlite-api-guide) | Stable | Async | +| [MQTT Messaging](./mqtt-outbound) | Experimental | Async | + +NOTE: Blocking versions of async APIs and entry points are still available for backward compatibility. + +For more information about what is possible in the programming language of your choice, please see our [Language Support Overview](./language-support-overview). + +## Asynchronous and Blocking APIs + +In Spin 3.x, all APIs and entry points were blocking. These blocking APIs and entry points are still supported in Spin 4.x, but Spin 4.x also supports asynchronous versions of most of them. + +Generally, you can mix async and blocking APIs as necessary. There are one important restriction: a sync entry point _must not_ call an async API. If you are using a sync trigger (as shown above), you _must_ call _only_ blocking APIs. Depending on your language and Spin SDK version, blocking APIs may be available in your SDK, or you may need to switch to an older SDK. + +Older builds of trigger plugins may be sync even if marked async above. Check the plugin documentation, and make sure you are running an up-to-date version, if you want to use async APIs. + +## Targeting a Deployment Environment + +Some Spin runtimes may support a different set of APIs from those listed above, or may support only older versions. For example, some runtimes might not support SQLite, or Serverless AI; or Spin 3.x-based runtimes don't support the async API versions introduced in Spin 4.0. This is an important consideration when writing a Spin application that will run in a different environment from your development environment: you do not want to depend on SQLite if you will have to deploy to an environment without it. + +You can tell the Spin CLI about the environment (or environments) that you plan to deploy into using the `application.targets` field in `spin.toml`. If you do this, `spin build` verifies the set of APIs used by your components against each listed environment. If a component uses APIs that wouldn't be supported, `spin build` will warn you. + +For example, here is how to specify that you want your application to be compatible with `spin up` version 3.2: + +```toml +# spin.toml + +[application] +targets = ["spin-up:3.2"] +``` + +For the other runtime environments such as SpinKube or commercial clouds, see the documentation for those projects for their environment IDs. diff --git a/content/v4/build.md b/content/v4/build.md new file mode 100644 index 00000000..3e40b6c6 --- /dev/null +++ b/content/v4/build.md @@ -0,0 +1,245 @@ +title = "Building Spin Application Code" +template = "main" +date = "2023-11-04T00:00:01Z" +enable_shortcodes = true +[extra] +url = "https://github.com/spinframework/spin-docs/blob/main/content/v4/build.md" + +--- + +- [Setting Up for `spin build`](#setting-up-for-spin-build) +- [Running `spin build`](#running-spin-build) +- [Running the Application After Build](#running-the-application-after-build) +- [Overriding the Working Directory](#overriding-the-working-directory) +- [Building With Profiles](#building-with-profiles) +- [Next Steps](#next-steps) + +A Spin application is made up of one or more components. Components are binary Wasm modules; _building_ refers to the process of converting your source code into those modules. + +> Even languages that don't require a compile step when used 'natively' may still require a build step to adapt them to work as Wasm modules. + +Because most compilers don't target Wasm by default, building Wasm modules often requires special command options, which you may not have at your fingertips. +What's more, when developing a multi-component application, you may need to issue such commands for several components on each iteration. +Doing this manually can be tedious and error-prone. + +To make the build process easier, the `spin build` command allows you to build all the components in one command. + +> You don't have to use `spin build` to manage your builds. If you prefer to use a Makefile or other build system, you can! `spin build` is just there to provide an 'out of the box' solution. + + +## Setting Up for `spin build` + +To use `spin build`, each component that you want to build must specify the command used to build it in `spin.toml`, as part of its `component.(name).build` table: + +```toml +[component.hello] +# This is the section you need for `spin build` +[component.hello.build] +command = "npm run build" +``` + +If you generated the component from a Fermyon-supplied template, the `build` section should be set up correctly for you. You don't need to change or add anything. + +> Different components may be built from different languages, and so each component can have its own build command. In addition, some components may be precompiled into Wasm modules, and don't need a build command at all. If a component doesn't have a build command, `spin build` just skips it. + +{{ tabs "sdk-type" }} + +{{ startTab "Rust"}} + +For Rust applications, you must have the `wasm32-wasip2` target installed: + + + +```bash +$ rustup target add wasm32-wasip2 +``` + +The build command typically runs `cargo build` with the `wasm32-wasip2` target and the `--release` option: + + + +```toml +[component.hello.build] +command = "cargo build --target wasm32-wasip2 --release" +``` + +{{ blockEnd }} + +{{ startTab "TypeScript" }} + +For JavaScript and TypeScript applications, you must have [Node.js](https://nodejs.org). + + +It's normally convenient to put the detailed build instructions in `package.json`. The build script looks like: + + + +```json +{ + "scripts": { + "build": "npx webpack && mkdirp dist && j2w -i build/bundle.js -o target/spin-http-js.wasm" + } +} +``` + +{{ details "Parts of the build script" "The build script calls out to [`webpack`](https://webpack.js.org/) and `j2w` which is a script provided by the `@fermyon/spin-sdk` package that utilizes [`ComponentizeJS`](https://github.com/bytecodealliance/ComponentizeJS). [`knitwit`](https://github.com/fermyon/knitwit) is a utility that helps combine `wit` worlds "}} + +The build command can then call the NPM script: + + + +```toml +[component.hello.build] +command = "npm run build" +``` + +{{ blockEnd }} + +{{ startTab "Python" }} + +Ensure that you have Python 3.10 or later installed on your system. You can check your Python version by running: + +```bash +python3 --version +``` + +If you do not have Python 3.10 or later, you can install it by following the instructions [here](https://www.python.org/downloads/). + +For Python applications, you must have [`componentize-py`](https://pypi.org/project/componentize-py/) installed: + + + +```bash +$ pip3 install componentize-py +``` + +The build command then calls `componentize-py` on your application file: + + + +```toml +[component.hello.build] +command = "componentize-py -w spin-http componentize app -o app.wasm" +``` + +{{ blockEnd }} + +{{ startTab "TinyGo" }} + +For Go applications, you must use the TinyGo compiler, as the standard Go compiler does not yet support the WASI standard. See the [TinyGo installation guide](https://tinygo.org/getting-started/install/). + +The build command calls TinyGo with the WASI backend and appropriate options: + + + +```toml +[component.hello.build] +command = "tinygo build -target=wasip1 -gc=leaking -buildmode=c-shared -no-debug -o main.wasm ." +``` + +{{ blockEnd }} + +{{ blockEnd }} + +> The output of the build command _must_ match the component's `source` path. If you change the `build` or `source` attributes, make sure to keep them in sync. + + +## Running `spin build` + +Once the build commands are set up, running `spin build` will execute, sequentially, each build command: + + + +```bash +$ spin build +Building component hello with `cargo build --target wasm32-wasip2 --release` + Updating crates.io index + Updating git repository `https://github.com/spinframework/spin` + + //--snip-- + + Compiling hello v0.1.0 (hello) + Finished release [optimized] target(s) in 39.05s +Finished building all Spin components +``` + +> If your build doesn't work, and your source code looks okay, you can [run `spin doctor`](./troubleshooting-application-dev.md) to check for problems with your Spin configuration and tools. + +## Running the Application After Build + +You can pass the `--up` option to `spin build` to start the application as soon as the build process completes successfully. + +This is equivalent to running `spin up` immediately after `spin build`. It accepts all the same flags and options that `up` does. See [Running Applications](running-apps) for details. + +## Overriding the Working Directory + +By default, the `command` to build a component is executed in the directory containing the `spin.toml` file. If a component's entire build source is under a subdirectory, it is often more convenient to build in that subdirectory rather than try to pass the path to the build command. You can do this by setting the `workdir` option in the `component.(id).build` table. + +For example, consider this Rust component located in subdirectory `deep`: + + + +```bash +. +β”œβ”€β”€ deep +β”‚Β Β  β”œβ”€β”€ Cargo.toml +β”‚Β Β  └── src +β”‚Β Β  └── lib.rs +└── spin.toml +``` + +To have the Rust build `command` run in directory `deep`, we can set the component's `workdir`: + + + +```toml +[component.deep.build] +# `command` is the normal build command for this language +command = "cargo build --target wasm32-wasip2 --release" +# This tells Spin to run it in the directory of the build file (in this case Cargo.toml) +workdir = "deep" +``` + +> `workdir` must be a relative path, and it is relative to the directory containing `spin.toml`. Specifying an absolute path leads to an error. + +## Building With Profiles + +A component can define _build profiles_, which override certain component settings to allow for different usages. For example, a component might define a debug profile, which compiles the binary with debugging information. A profile can also override environment variables and dependencies. + +To define a profile, create a `profile.` entry in the component TOML. For example: + +```toml +[component.example] +source = "./out/release/example.wasm" +[component.example.build] +command = "make release" +[component.example.profile.debug] +source = "./out/debug/example.wasm" +environment = { TRACE_LEVEL = "full" } +[component.example.profile.debug.build] +command = "make debug" +``` + +To use a build profile, pass the `--profile ` flag to the Spin command you're running. For example, `spin build --profile debug` or `spin up --profile debug`. + +> When you have build profiles in play, you run the risk of accidentally running `spin build` with a profile and then running `spin up` or `spin registry push` without a profile, not realising that you are running or pushing the default profile rather than the one you just built! Spin will warn you if you do this. But a safer technique is to provide `--build` as part of the `up` or `registry` push command, e.g. `spin up --profile debug --build`, `spin registry push --profile publish --build`. This guarantees that the right profile has been freshly built. You can set the `SPIN_ALWAYS_BUILD` environment variable to tell Spin to _always_ use the `--build` option. + +If a component doesn't define a profile (or doesn't override a particular field in its profile), Spin will fall back to the 'base' value. You only need to override the specific components and fields where the profile differs from the base. For example: + +```toml +[component.example1] +source = "./out/example1.wasm" # source will be the same with or without `--profile debug` +[component.example1.build] +command = "make release1" +[component.example1.profile.debug.build] +command = "make debug1" + +[component.example2] # everything will be the same with our without `--profile debug` +source = "./out/example2.wasm" +[component.example2.build] +command = "make example2" +``` + +## Next Steps + +- Try [running your application locally](running-apps) diff --git a/content/v4/cache.md b/content/v4/cache.md new file mode 100644 index 00000000..49c66891 --- /dev/null +++ b/content/v4/cache.md @@ -0,0 +1,95 @@ +title = "Spin Internal Data Layout" +template = "main" +date = "2023-11-04T00:00:01Z" +enable_shortcodes = true +[extra] +url = "https://github.com/spinframework/spin-docs/blob/main/content/v4/cache.md" + +--- + +- [Base Directories](#base-directories) +- [Plugins](#plugins) +- [Templates](#templates) +- [Application Cache](#application-cache) + - [Inside the Application Cache](#inside-the-application-cache) + +This page describes how Spin lays out its internal data on disk. + +> This document is provided as a reference for users wanting to diagnose problems or to reset Spin state. Don't modify the contents of these directories. Spin updates these directories as you issue the relevant commands on the command line. + +> No stability guarantees apply to the internal layout. It may change between Spin versions. + +## Base Directories + +Spin uses similar layouts across Linux, MacOS and Windows platforms, but the paths to various areas of the user's home directory differ across the platforms. On this page, the following terms have the following meanings: + +| Name | Linux | MacOS | Windows | +|---------------------|------------------------------------------|--------------------------------------|-------------------| +| `DATA_DIR` | `$XDG_DATA_HOME` or `$HOME/.local/share` | `$HOME/Library/Application Support`, or `$HOMEBREW_PREFIX/etc/fermyon-spin` for Spin =v3.2 if installed using Homebrew | `%LOCALAPPDATA%` or `%USERPROFILE%\AppData\Local` | +| `CACHE_DIR` | `$XDG_CACHE_HOME` or `$HOME/.cache` | `$HOME/Library/Caches` | `%LOCALAPPDATA%` or `%USERPROFILE%\AppData\Local` | + +These directories are based on the [XDG specification](https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html), and specifically on the cross-platform implementation in the [Rust `dirs` crate](https://docs.rs/dirs/latest/dirs/). + +> If Spin cannot resolve a base directory as listed above, it falls back to `$HOME/.spin` (`%USERPROFILE%\.spin` on Windows). + +> Spin's data directory (`DATA_DIR`) can be overridden via the `SPIN_DATA_DIR` environment variable. + +## Plugins + +Installed plugins are stored in `(DATA_DIR)/spin/plugins`. A snapshot of the plugins registry is also stored under that directory at `(DATA_DIR)/spin/plugins/.spin-plugins`; this is structured as a Git repository. + +> Note: If you [install Spin](./install) using Homebrew, the plugins are stored at `$HOMEBREW_PREFIX/spinframework-spin/plugins` (previously `$HOMEBREW_PREFIX/fermyon-spin/plugins`). + +If you delete the plugins directory, you will no longer be able to run your plugins (until you reinstall them), but other Spin operations will be unaffected. + +## Templates + +Installed templates are stored in `(DATA_DIR)/spin/templates`. + +> Note: If you [install Spin](install) using Homebrew, the templates are stored at at `$HOMEBREW_PREFIX/spinframework-spin/templates` (previously `$HOMEBREW_PREFIX/fermyon-spin/templates`). + +If you delete the templates directory, you will lose access to your installed templates (until you reinstall them), but other Spin operations will be unaffected. + +## Application Cache + +Downloaded application data, such as applications downloaded from registries or Wasm modules downloaded from URLs, are stored in `(CACHE_DIR)/spin/registry`. + +If you delete the application cache directory, Spin will automatically re-download the files as needed. Spin operations will be otherwise unaffected. + +### Inside the Application Cache + +> **Reminder:** This information is provided for diagnostic and entertainment purposes only, and may change across Spin versions. The only operation a user can safely undertake is to delete the entire `registry` directory. + +The application cache is divided into three subdirectories, `data`, `manifests`, and `wasm`. + +The `data` directory contains all static assets referenced from applications distributed with remote registries. The `wasm` directory contains all component sources referenced either in applications distributed with remote registries, or component sources from HTTP endpoints, directly referenced in `spin.toml`. + +> The `data` and `wasm` directories are content addressable. This means that if multiple applications reference the same static file or component source, Spin will be able to determine if it has already been pulled (on that users operating system), based on its digest. This also means that if an application has an update, Spin will only pull the changes in the component sources and static assets. + +The `manifests` directory contains the registry manifests for entire apps distributed with remote registries. They are placed in subdirectories that identify the application based on the registry, repository, and digest (or tag). + +The following `tree` command shows a typical (abbreviated) cache directory: + + + +```console +$ tree ~/Library/Caches/spin/registry/ + +β”œβ”€β”€ data +β”‚Β Β  β”œβ”€β”€ sha256:41a4649a8a8c176133792119cb45a7686767d3fa376ffd656e2ff76a6071fb07 +β”‚Β Β  └── sha256:da3fda2db338a73483068072e22f7e7eef27afdbae3db824e130932adce703ba +β”œβ”€β”€ manifests +β”‚Β Β  └── ghcr.io +β”‚Β Β  └── radu-matei +β”‚Β Β  β”œβ”€β”€ hello-registries +β”‚Β Β  β”‚Β Β  └── latest +β”‚Β Β  β”‚Β Β  β”œβ”€β”€ config.json +β”‚Β Β  β”‚Β Β  └── manifest.json +β”‚Β Β  └── spin-openai-demo +β”‚Β Β  └── v1 +β”‚Β Β  β”œβ”€β”€ config.json +β”‚Β Β  └── manifest.json +└── wasm + β”œβ”€β”€ sha256:0b985e7d43e719f34cbb54849759a2f8e7913c0f9b17bf7cb2b3d2458d33859e + └── sha256:d5f9e1f6b61b90f7404e3800285f7860fe2cfc7d0116023efc370adbb403fe87 +``` diff --git a/content/v4/cli-reference.md b/content/v4/cli-reference.md new file mode 100644 index 00000000..12cd19a8 --- /dev/null +++ b/content/v4/cli-reference.md @@ -0,0 +1,611 @@ +title = "Command Line Reference" +template = "main" +date = "2025-01-01T00:00:01Z" +[extra] +url = "https://github.com/spinframework/spin-docs/blob/main/content/v4/cli-reference.md" + +--- +# Command-Line Help for `spin` + +This document contains the help content for the `spin` command-line program. + +**Command Overview:** + +* [`spin`↴](#spin) +* [`spin add`↴](#spin-add) +* [`spin build`↴](#spin-build) +* [`spin deploy`↴](#spin-deploy) +* [`spin doctor`↴](#spin-doctor) +* [`spin login`↴](#spin-login) +* [`spin new`↴](#spin-new) +* [`spin plugins`↴](#spin-plugins) +* [`spin plugins install`↴](#spin-plugins-install) +* [`spin plugins list`↴](#spin-plugins-list) +* [`spin plugins search`↴](#spin-plugins-search) +* [`spin plugins show`↴](#spin-plugins-show) +* [`spin plugins uninstall`↴](#spin-plugins-uninstall) +* [`spin plugins update`↴](#spin-plugins-update) +* [`spin plugins upgrade`↴](#spin-plugins-upgrade) +* [`spin registry`↴](#spin-registry) +* [`spin registry login`↴](#spin-registry-login) +* [`spin registry pull`↴](#spin-registry-pull) +* [`spin registry push`↴](#spin-registry-push) +* [`spin templates`↴](#spin-templates) +* [`spin templates install`↴](#spin-templates-install) +* [`spin templates list`↴](#spin-templates-list) +* [`spin templates uninstall`↴](#spin-templates-uninstall) +* [`spin templates upgrade`↴](#spin-templates-upgrade) +* [`spin up`↴](#spin-up) +* [`spin watch`↴](#spin-watch) + +## `spin` + +The Spin CLI + +**Usage:** `USAGE: + spin ` + +###### **Subcommands:** + +* `add` β€” Scaffold a new component into an existing application +* `build` β€” Build the Spin application +* `deploy` β€” Package and upload an application to a deployment environment. +* `doctor` β€” Detect and fix problems with Spin applications +* `login` β€” Log into a deployment environment. +* `new` β€” Scaffold a new application based on a template +* `plugins` β€” Install/uninstall Spin plugins +* `registry` β€” Commands for working with OCI registries to distribute applications +* `templates` β€” Commands for working with WebAssembly component templates +* `up` β€” Start the Spin application +* `watch` β€” Build and run the Spin application, rebuilding and restarting it when files change + +###### **Options:** + +* `--help ` β€” Print help information +* `--version ` β€” Print version information + + + +## `spin add` + +Scaffold a new component into an existing application + +**Usage:** `spin USAGE: + add [OPTIONS] [NAME]` + +###### **Arguments:** + +* `` β€” The name of the new application or component +* `` β€” The name of the new application or component. If present, `name` is instead treated as the template ID. This provides backward compatibility with Spin 1.x syntax, so that existing content continues to work + +###### **Options:** + +* `-a`, `--accept-defaults ` β€” An optional argument that allows to skip prompts for the manifest file by accepting the defaults if available on the template +* `--allow-overwrite ` β€” If the output directory already contains files, generate the new files into it without confirming, overwriting any existing files with the same names +* `-f`, `--file ` β€” Path to spin.toml +* `--help ` β€” Print help information +* `--init ` β€” Create the new application or component in the current directory +* `--no-vcs ` β€” An optional argument that allows to skip creating .gitignore +* `-o`, `--output ` β€” The directory in which to create the new application or component. The default is the name argument +* `-t`, `--template ` β€” The template from which to create the new application or component. Run `spin templates list` to see available options +* `--tag ` β€” Filter templates to select by tags +* `-v`, `--value ` β€” Parameter values to be passed to the template (in name=value format) +* `--values-file ` β€” A TOML file which contains parameter values in name = "value" format. Parameters passed as CLI option overwrite parameters specified in the file +* `--version ` β€” Print version information + + + +## `spin build` + +Build the Spin application + +**Usage:** `spin USAGE: + build [OPTIONS] [UP_ARGS]...` + +###### **Arguments:** + +* `` + +###### **Options:** + +* `-c`, `--component-id ` β€” Component ID to build. This can be specified multiple times. The default is all components +* `-f`, `--from ` β€” The application to build. This may be a manifest (spin.toml) file, or a directory containing a spin.toml file. If omitted, it defaults to "spin.toml" +* `--help ` β€” Print help information +* `--skip-target-checks ` β€” By default, if the application manifest specifies one or more deployment targets, Spin checks that all components are compatible with those deployment targets. Specify this option to bypass those target checks +* `-u`, `--up ` β€” Run the application after building +* `--version ` β€” Print version information + + + +## `spin deploy` + +Package and upload an application to a deployment environment. + +**Usage:** `spin USAGE: + deploy` + +###### **Arguments:** + +* `` β€” All args to be passed through to the plugin + +###### **Options:** + +* `--help ` β€” Print help information +* `--version ` β€” Print version information + + + +## `spin doctor` + +Detect and fix problems with Spin applications + +**Usage:** `spin USAGE: + doctor [OPTIONS]` + +###### **Options:** + +* `-f`, `--from ` β€” The application to check. This may be a manifest (spin.toml) file, or a directory containing a spin.toml file. If omitted, it defaults to "spin.toml" +* `--help ` β€” Print help information +* `--version ` β€” Print version information + + + +## `spin login` + +Log into a deployment environment. + +**Usage:** `spin USAGE: + login` + +###### **Arguments:** + +* `` β€” All args to be passed through to the plugin + +###### **Options:** + +* `--help ` β€” Print help information +* `--version ` β€” Print version information + + + +## `spin new` + +Scaffold a new application based on a template + +**Usage:** `spin USAGE: + new [OPTIONS] [NAME]` + +###### **Arguments:** + +* `` β€” The name of the new application or component +* `` β€” The name of the new application or component. If present, `name` is instead treated as the template ID. This provides backward compatibility with Spin 1.x syntax, so that existing content continues to work + +###### **Options:** + +* `-a`, `--accept-defaults ` β€” An optional argument that allows to skip prompts for the manifest file by accepting the defaults if available on the template +* `--allow-overwrite ` β€” If the output directory already contains files, generate the new files into it without confirming, overwriting any existing files with the same names +* `--help ` β€” Print help information +* `--init ` β€” Create the new application or component in the current directory +* `--no-vcs ` β€” An optional argument that allows to skip creating .gitignore +* `-o`, `--output ` β€” The directory in which to create the new application or component. The default is the name argument +* `-t`, `--template ` β€” The template from which to create the new application or component. Run `spin templates list` to see available options +* `--tag ` β€” Filter templates to select by tags +* `-v`, `--value ` β€” Parameter values to be passed to the template (in name=value format) +* `--values-file ` β€” A TOML file which contains parameter values in name = "value" format. Parameters passed as CLI option overwrite parameters specified in the file +* `--version ` β€” Print version information + + + +## `spin plugins` + +Install/uninstall Spin plugins + +**Usage:** `spin USAGE: + plugins ` + +###### **Subcommands:** + +* `install` β€” Install plugin from a manifest +* `list` β€” List available or installed plugins +* `search` β€” Search for plugins by name +* `show` β€” Print information about a plugin +* `uninstall` β€” Remove a plugin from your installation +* `update` β€” Fetch the latest Spin plugins from the spin-plugins repository +* `upgrade` β€” Upgrade one or all plugins + +###### **Options:** + +* `--help ` β€” Print help information +* `--version ` β€” Print version information + + + +## `spin plugins install` + +Install plugin from a manifest. + +The binary file and manifest of the plugin is copied to the local Spin plugins directory. + +**Usage:** `spin plugins USAGE: + install [OPTIONS] [PLUGIN_NAME]` + +###### **Arguments:** + +* `` β€” Name of Spin plugin + +###### **Options:** + +* `--auth-header-value ` β€” Provide the value for the authorization header to be able to install a plugin from a private repository. (e.g) --auth-header-value "Bearer " +* `-f`, `--file ` β€” Path to local plugin manifest +* `--help ` β€” Print help information +* `--override-compatibility-check ` β€” Overrides a failed compatibility check of the plugin with the current version of Spin +* `-u`, `--url ` β€” URL of remote plugin manifest to install +* `-v`, `--version ` β€” Specific version of a plugin to be install from the centralized plugins repository +* `--version ` β€” Print version information +* `-y`, `--yes ` β€” Skips prompt to accept the installation of the plugin + + + +## `spin plugins list` + +List available or installed plugins + +**Usage:** `spin plugins USAGE: + list [OPTIONS]` + +###### **Options:** + +* `--all ` β€” List all versions of plugins. This is the default behaviour +* `--filter ` β€” Filter the list to plugins containing this string +* `--format ` β€” The format in which to list the templates + + Default value: `plain` +* `--help ` β€” Print help information +* `--installed ` β€” List only installed plugins +* `--summary ` β€” List latest and installed versions of plugins +* `--version ` β€” Print version information + + + +## `spin plugins search` + +Search for plugins by name + +**Usage:** `spin plugins USAGE: + search [OPTIONS] [FILTER]` + +###### **Arguments:** + +* `` β€” The text to search for. If omitted, all plugins are returned + +###### **Options:** + +* `--format ` β€” The format in which to list the plugins + + Default value: `plain` +* `--help ` β€” Print help information +* `--version ` β€” Print version information + + + +## `spin plugins show` + +Print information about a plugin + +**Usage:** `spin plugins USAGE: + show ` + +###### **Arguments:** + +* `` β€” Name of Spin plugin + +###### **Options:** + +* `--help ` β€” Print help information +* `--version ` β€” Print version information + + + +## `spin plugins uninstall` + +Remove a plugin from your installation + +**Usage:** `spin plugins USAGE: + uninstall ` + +###### **Arguments:** + +* `` β€” Name of Spin plugin + +###### **Options:** + +* `--help ` β€” Print help information +* `--version ` β€” Print version information + + + +## `spin plugins update` + +Fetch the latest Spin plugins from the spin-plugins repository + +**Usage:** `spin plugins USAGE: + update` + +###### **Options:** + +* `--help ` β€” Print help information +* `--version ` β€” Print version information + + + +## `spin plugins upgrade` + +Upgrade one or all plugins + +**Usage:** `spin plugins USAGE: + upgrade [OPTIONS] [PLUGIN_NAME]` + +###### **Arguments:** + +* `` β€” Name of Spin plugin to upgrade + +###### **Options:** + +* `-a`, `--all ` β€” Upgrade all plugins +* `--auth-header-value ` β€” Provide the value for the authorization header to be able to install a plugin from a private repository. (e.g) --auth-header-value "Bearer " +* `-d`, `--downgrade ` β€” Allow downgrading a plugin's version +* `-f`, `--file ` β€” Path to local plugin manifest +* `--help ` β€” Print help information +* `--override-compatibility-check ` β€” Overrides a failed compatibility check of the plugin with the current version of Spin +* `-u`, `--url ` β€” Path to remote plugin manifest +* `-v`, `--version ` β€” Specific version of a plugin to be install from the centralized plugins repository +* `--version ` β€” Print version information +* `-y`, `--yes ` β€” Skips prompt to accept the installation of the plugin[s] + + + +## `spin registry` + +Commands for working with OCI registries to distribute applications + +**Usage:** `spin USAGE: + registry ` + +###### **Subcommands:** + +* `login` β€” Log in to a registry +* `pull` β€” Pull a Spin application from a registry +* `push` β€” Push a Spin application to a registry + +###### **Options:** + +* `--help ` β€” Print help information +* `--version ` β€” Print version information + + + +## `spin registry login` + +Log in to a registry + +**Usage:** `spin registry USAGE: + login [OPTIONS] ` + +###### **Arguments:** + +* `` β€” OCI registry server (e.g. ghcr.io) + +###### **Options:** + +* `--help ` β€” Print help information +* `-p`, `--password ` β€” Password for the registry +* `--password-stdin ` β€” Take the password from stdin +* `-u`, `--username ` β€” Username for the registry +* `--version ` β€” Print version information + + + +## `spin registry pull` + +Pull a Spin application from a registry + +**Usage:** `spin registry USAGE: + pull [OPTIONS] ` + +###### **Arguments:** + +* `` β€” Reference in the registry of the published Spin application. This is a string whose format is defined by the registry standard, and generally consists of //:. E.g. ghcr.io/ogghead/spin-test-app:0.1.0 + +###### **Options:** + +* `--cache-dir ` β€” Cache directory for downloaded registry data +* `--help ` β€” Print help information +* `-k`, `--insecure ` β€” Ignore server certificate errors +* `--version ` β€” Print version information + + + +## `spin registry push` + +Push a Spin application to a registry + +**Usage:** `spin registry USAGE: + push [OPTIONS] ` + +###### **Arguments:** + +* `` β€” Reference in the registry of the Spin application. This is a string whose format is defined by the registry standard, and generally consists of //:. E.g. ghcr.io/ogghead/spin-test-app:0.1.0 + +###### **Options:** + +* `--annotation ` β€” Specifies the OCI image manifest annotations (in key=value format). Any existing value will be overwritten. Can be used multiple times +* `--build ` β€” Specifies to perform `spin build` (with the default options) before pushing the application +* `--cache-dir ` β€” Cache directory for downloaded registry data +* `--compose ` β€” Compose component dependencies before pushing the application. + + The default is to compose before pushing, which maximises compatibility with different Spin runtime hosts. Turning composition off can optimise bandwidth for shared dependencies, but makes the pushed image incompatible with hosts that cannot carry out composition themselves. + + Default value: `true` +* `-f`, `--from ` β€” The application to push. This may be a manifest (spin.toml) file, or a directory containing a spin.toml file. If omitted, it defaults to "spin.toml" +* `--help ` β€” Print help information +* `-k`, `--insecure ` β€” Ignore server certificate errors +* `--version ` β€” Print version information + + + +## `spin templates` + +Commands for working with WebAssembly component templates + +**Usage:** `spin USAGE: + templates ` + +###### **Subcommands:** + +* `install` β€” Install templates from a Git repository or local directory +* `list` β€” List the installed templates +* `uninstall` β€” Remove a template from your installation +* `upgrade` β€” Upgrade templates to match your current version of Spin + +###### **Options:** + +* `--help ` β€” Print help information +* `--version ` β€” Print version information + + + +## `spin templates install` + +Install templates from a Git repository or local directory. + +The files of the templates are copied to the local template store: a directory in your data or home directory. + +**Usage:** `spin templates USAGE: + install [OPTIONS]` + +###### **Options:** + +* `--branch ` β€” The optional branch of the git repository +* `--dir ` β€” Local directory containing the template(s) to install +* `--git ` β€” The URL of the templates git repository. The templates must be in a git repository in a "templates" directory +* `--help ` β€” Print help information +* `--tar ` β€” URL to a tarball in .tar.gz format containing the template(s) to install +* `--upgrade ` β€” If present, updates existing templates instead of skipping +* `--version ` β€” Print version information + + + +## `spin templates list` + +List the installed templates + +**Usage:** `spin templates USAGE: + list [OPTIONS]` + +###### **Options:** + +* `--help ` β€” Print help information +* `--tag ` β€” Filter templates matching all provided tags +* `--verbose ` β€” Whether to show additional template details in the list +* `--version ` β€” Print version information + + + +## `spin templates uninstall` + +Remove a template from your installation + +**Usage:** `spin templates USAGE: + uninstall ` + +###### **Arguments:** + +* `` β€” The template to uninstall + +###### **Options:** + +* `--help ` β€” Print help information +* `--version ` β€” Print version information + + + +## `spin templates upgrade` + +Upgrade templates to match your current version of Spin. + +The files of the templates are copied to the local template store: a directory in your data or home directory. + +**Usage:** `spin templates USAGE: + upgrade [OPTIONS]` + +###### **Options:** + +* `--all ` β€” By default, Spin displays the list of installed repositories and prompts you to choose which to upgrade. Pass this flag to upgrade all repositories without prompting +* `--branch ` β€” The optional branch of the git repository, if a specific repository is given +* `--help ` β€” Print help information +* `--repo ` β€” By default, Spin displays the list of installed repositories and prompts you to choose which to upgrade. Pass this flag to upgrade only the specified repository without prompting +* `--version ` β€” Print version information + + + +## `spin up` + +Start the Spin application + +**Usage:** `spin USAGE: + up [OPTIONS]` + +###### **Arguments:** + +* `` β€” All other args, to be passed through to the trigger + +###### **Options:** + +* `--build ` β€” For local apps, specifies to perform `spin build` (with the default options) before running the application. + + This is ignored on remote applications, as they are already built. +* `-c`, `--component-id ` β€” [Experimental] Component ID to run. This can be specified multiple times. The default is all components +* `--cache-dir ` β€” Cache directory for downloaded components and assets +* `--direct-mounts ` β€” For local apps with directory mounts and no excluded files, mount them directly instead of using a temporary directory. + + This allows you to update the assets on the host filesystem such that the updates are visible to the guest without a restart. This cannot be used with registry apps or apps which use file patterns and/or exclusions. +* `-e`, `--env ` β€” Pass an environment variable (key=value) to all components of the application +* `-f`, `--from ` β€” The application to run. This may be a manifest (spin.toml) file, a directory containing a spin.toml file, a remote registry reference, or a Wasm module (a .wasm file). If omitted, it defaults to "spin.toml" +* `-h`, `--help ` +* `--help ` β€” Print help information +* `-k`, `--insecure ` β€” Ignore server certificate errors from a registry +* `--temp ` β€” Temporary directory for the static assets of the components +* `--version ` β€” Print version information + + + +## `spin watch` + +Build and run the Spin application, rebuilding and restarting it when files change + +**Usage:** `spin USAGE: + watch [OPTIONS] [UP_ARGS]...` + +###### **Arguments:** + +* `` β€” Arguments to be passed through to spin up + +###### **Options:** + +* `-c`, `--clear ` β€” Clear the screen before each run +* `-d`, `--debounce ` β€” Set the timeout between detected change and re-execution, in milliseconds + + Default value: `100` +* `-f`, `--from ` β€” The application to watch. This may be a manifest (spin.toml) file, or a directory containing a spin.toml file. If omitted, it defaults to "spin.toml" +* `--help ` β€” Print help information +* `--skip-build ` β€” Only run the Spin application, restarting it when build artifacts change +* `--version ` β€” Print version information + + + +
+ + + This document was generated automatically by + clap-markdown. + diff --git a/content/v4/contributing-docs.md b/content/v4/contributing-docs.md new file mode 100644 index 00000000..1fb21ca6 --- /dev/null +++ b/content/v4/contributing-docs.md @@ -0,0 +1,526 @@ +title = "Contributing to Docs" +template = "main" +date = "2023-11-04T00:00:01Z" +[extra] +url = "https://github.com/spinframework/spin-docs/blob/main/content/v4/contributing-docs.md" +keywords = "contribute contributing" + +--- + +- [Technical Documentation Types](#technical-documentation-types) + - [1. How-To Guides](#1-how-to-guides) + - [2. Reference](#2-reference) + - [3. Explanation](#3-explanation) +- [Technical Documentation Procedure](#technical-documentation-procedure) + - [1. Fork the Repository](#1-fork-the-repository) + - [2. Clone the Fork](#2-clone-the-fork) + - [3. Create New Branch](#3-create-new-branch) + - [4. Add Upstream](#4-add-upstream) + - [5. Code Blocks, Annotations and Table of Contents (ToC)](#5-code-blocks-annotations-and-table-of-contents-toc) + - [6.1 Checking Your Content - Using NPM](#61-checking-your-content---using-npm) + - [6.2 Indexing Your Content](#62-indexing-your-content) + - [6.3 Increasing Search Visibility For Your Content](#63-increasing-search-visibility-for-your-content) + - [6.4 The Edit On GitHub Button](#64-the-edit-on-github-button) + - [6.5 How To Properly Edit CSS Styles](#65-how-to-properly-edit-css-styles) + - [6.6 Checking Your Content - Using Bartholomew's CLI](#66-checking-your-content---using-bartholomews-cli) + - [6.7 Checking Your Content - Preview a Documentation Page on Localhost](#67-checking-your-content---preview-a-documentation-page-on-localhost) + - [6.8 Scheduling Menu Items for Timed Release](#68-scheduling-menu-items-for-timed-release) + - [7. Checking Web Pages](#7-checking-web-pages) + - [8. Add Changes](#8-add-changes) + - [9. Commit Changes](#9-commit-changes) + - [10. Push Changes](#10-push-changes) + - [11. Create a Pull Request](#11-create-a-pull-request) + +We are delighted that you are interested in making our documentation better. Thank you! We welcome and appreciate contributions of all types β€” opening issues, fixing typos, adding examples, one-liner code fixes, tests, or complete features. + +Any contribution and interaction on any spinframework project MUST follow our [code of conduct](https://github.com/spinframework/governance). Thank you for being part of an inclusive and open community! + +Below are a few pointers designed to help you contribute. + +## Technical Documentation Types + +The following points will help guide your contribution from a resource-type perspective; essentially we would really appreciate you creating and contributing any of the following 3 resource types. + +### 1. How-To Guides + +How-to guides are oriented towards showing a user how to solve a problem, which leads them to be able to achieve their own goal. The how-to guide will follow a series of logical steps. Think of it as providing a recipe for the user's creativity. For example, you can show a user how to [develop a Spin application](/writing-apps.md) without telling them what the application must do; that is up to the user's imagination. + +### 2. Reference + +Reference resources are merely a dry description; describing the feature in its simplest form. An example of a reference resource is the [Spin application manifest reference](./manifest-reference). You will notice that the Manifest Reference page simply lists all of the manifest entries and available options. + +### 3. Explanation + +An explanation resource is written using a deep-dive approach i.e. providing a deep explanation with the view to impart a deep understanding of a particular concept, feature or product. You may find your contribution is so in-depth that it becomes a long form article like a blog post. If that is the case, consider an addition to the Spin Blog. + +## Technical Documentation Procedure + +### 1. Fork the Repository + +The first step is to fork the [spin-docs repository](https://github.com/spinframework/spin-docs), from spinframework's GitHub, to your own GitHub account. + +Ensure that you are forking the spin-docs repository **to your own** GitHub account; where you have full editing privileges. + +### 2. Clone the Fork + +Copy the URL from the UI in readiness for running the `git clone` command. + +Go ahead and clone the new fork that you just created (the one which resides in your own GitHub account): + + + +```bash +$ cd ~ +$ git clone git@github.com:yourusername/spin-docs.git +$ cd developer +``` + +### 3. Create New Branch + +Create a new branch that will house all of your changes for this specific contribution: + + + +```bash +$ git checkout -b my_new_branch +``` + +### 4. Add Upstream + +Create a new remote for the upstream (a pointer to the original repository to which you are contributing): + + + +```bash +$ git remote add upstream https://github.com/spinframework/spin-docs +``` + +### 5. Code Blocks, Annotations and Table of Contents (ToC) + +It is highly recommended that you use either the `` or the `` annotation before each of your code blocks, and that each code block defines the appropriate [syntax highlighting](https://rdmd.readme.io/docs/code-blocks#language-support). The annotation can be skipped for code blocks with example code snippets i.e. non-terminal or generic output examples. + +**Selective copy** + +The selective copy annotation (``) is intended for use when communicating code and/or CLI commands for the reader to copy and paste. The selective copy annotation allows the reader to see the entire code block (both commands and results) but only copies the lines that start with `$` into the reader's clipboard (minus the `$`) when the user clicks the copy button. For example, copying the following code block will only copy `echo "hello"` into your clipboard, for pasting. + + + +```bash +$ echo "hello" +hello +``` + +> Note: If the command, that starts with `$`, is deliberately spread over two lines (by escaping the newline character), then the copy mechanism will still copy the second line which is technically still part of that single command. + +**No copy** + +The no copy annotation (``) precedes a code block where no copy and pasting of code is intended. If using the no copy attribute please still be sure to add the appropriate syntax highlighting to your code block (for display purposes). For example: + +![No Copy Source Code Example](/static/image/no-copy-source-code-example.png) + +Please find copyable snippet below, for your convenience: + +```` + + +```text +Some generic code not intended for copying/pasting +``` +```` + +The above markdown will render the following code block on the web page: + + + +```text +Some generic code not intended for copying/pasting +``` + +**Non-selective copy** - just a straight copy without any additional features. + +If you want the code in a code block to be copyable with no "smarts" to remove the `$` then you can just simply leave out the annotation altogether. A code block in markdown will be copyable without smarts just as is. + +**Multi-tab code blocks** + +Examples of multi-tab blocks can be seen in the [Spin installer documentation](./install#installing-spin). The above examples demonstrate how tabs can either represent platforms i.e. `Windows`, `Linux` and `macOS` or represent specific programming languages i.e. `Rust`, `JavaScript` and `Golang` etc. Here is a brief example of how to implement multi-tab code blocks when writing technical documentation for this site, using markdown. + +The first step to implementing multi-tab code blocks is placing the `enable_shortcodes = true` configuration at the start of the `.md` file. Specifically, in the `.md` file's frontmatter. + +The markup to create tabs in markdown is as follows + +``` +{{ tabs "os" }} + +{{ startTab "Windows"}} + +To list files on windows use `dir` + + + +\`\`\`bash +$ dir hello_spin +\`\`\` +and script in windows have the extension `.bat` + + + +\`\`\`bash +hello.bat +test.bat +\`\`\` + +{{ blockEnd }} + +{{ startTab "Linux"}} + +To list files on linux use `ls` + + + +\`\`\`bash +$ ls +\`\`\` + +and script in linux have the extension `.sh` + + + +\`\`\`bash +hello.sh +test.sh +\`\`\` + +{{ blockEnd }} +{{ blockEnd }} +``` + +**Note**: Existing documentation will already be using class names for code block `tabs` and `startTab` i.e. `{{ tabs "os" }}` and `{{ startTab "Windows"}}` respectively. Please consult the following `tabs` and `startTab` class names that are already in use (before creating your own). If you need to create a new class name (because one does not already exist) please add it to the list below as part of the pull request that contains your code block contribution. + +**tabs**: +- `gh-interact` +- `os` +- `platforms` +- `sdk-type` +- `spin-version` +- `cloud-plugin-version` + +**startTab** +- `Azure AKS` +- `C#` +- `Docker Desktop` +- `Generic Kubernetes` +- `GitHub CLI` +- `GitHub UI` +- `K3d` +- `Linux` +- `macOS` +- `Python` +- `Rust` +- `TinyGo` +- `TypeScript` +- `v0.9.0` +- `v0.10.0` +- `v1.0.0` +- `v1.1.0` +- `v1.2.0` +- `v0.1.0` +- `v0.1.1` +- `Windows` + +The next section covers the highly recommended use of ToCs. + +**Implementing a Table of Contents (ToC)** + +If you create content with many headings it is highly recommended to place a ToC in your markdown file. There are excellent extensions (such as this Visual Studio Code Extension called [markdown-all-in-one](https://marketplace.visualstudio.com/items?itemName=yzhang.markdown-all-in-one) which will automatically generate your ToC). + +### 6.1 Checking Your Content - Using NPM + +Once you are satisfied with your contribution, you can programmatically check your content. + +If you have not done so already, please go ahead and perform the `npm ci` (npm clean install) command; to enable Node dependencies such as `markdownlint-cli2`. Simply run the following command, from the root of the spin-docs repository: + + + +```bash +$ npm ci +``` + +On top of the Node dependencies the `timeout` executable must be installed on your system and added to the `PATH` environment variable. The `timeout` executable is included in the [gnu coreutils package](https://www.gnu.org/software/coreutils/) which should be present in most Linux distributions. + +On macOS you can install the `timeout` binary using the Homebrew package manager as shown below: + + + +```bash +$ brew install coreutils +``` + +Having all dependencies installed, you can now check for broken links (which takes several minutes) and also lint your markdown files. Simply run the following command, from the root of the spin-docs repository: + + + +```bash +$ npm run test +``` + +**Hint:** Optionally you can run only the linter with the following command: + + + +```bash +# Example of how to lint all Markdown files in a local folder (in this case the spin folder) +npx markdownlint-cli2 content/*.md \"#node_modules\" +# Example of how to lint a local Markdown file +npx markdownlint-cli2 content/install.md \"#node_modules\" +``` + +**Note:** Whilst the `npm run test` command (which lints and also programmatically checks all URLs) does take extra time to complete it **must** be utilized before you [push changes](#10-push-changes); preventing the potential pushing of broken URLs to the documentation site. + +### 6.2 Indexing Your Content + +The Spin documentation site implements in-house search. A new index is automatically generated for you when your contribution is merged into the documentation repository. This is done via a GitHub action. The following section explains how to alter content to increase search visibility for your content. + +### 6.3 Increasing Search Visibility For Your Content + +The built-in search functionality is based on the indexing of individual words in each markdown file, which works well most of the time. However, there are a couple of scenarios where you _may_ want to deliberately increase search visibility for your content. + +**Word Mismatch** + +Words in a documentation markdown file may not be words that are searched for by a user. For example, you may write about "different HTTP listening options" whereas a user may only ever try to find that specific content using a search phrase like "alternate port". If you are aware of typical user search phrases it is always recommended to craft your content to include any predictable user search phrases. However, in the rare case of a mismatch between words in your content and the words a user searches for, you can utilize use the `keywords` string in the `[extra]` section of your document's frontmatter to increase visibility. For example, the following code block shows frontmatter that helps a user find your documentation page (when the user searches for `port`): + +```markdown +[extra] +keywords = "port ports" +``` + +Adding a word to the `keywords` string of a page overrides the built-in search functionality by at least one order of magnitude. Adding a word to the `keywords` string may displace other content, so please use it only if necessary. + +**Homing in on specific content** + +The `keywords` string takes users to the start of a page. In some cases, this is not ideal. You may want to home in on specific content to resolve a search query. + +If a search term relates to a specific part of a page, you may use the following syntax anywhere in the body of your markdown file, and the user's search action will direct them straight to the previous heading (nearest heading above the `@searchTerm` syntax). + +```markdown + +``` + + + +When using the above `@searchTerm` feature, please note the following: +- the words must be separated by a space i.e. +- these keywords will be boosted in the search results by at least one order of magnitude; so please use them with caution, so as not to displace other valid pages containing similar content. + +Example: If you search for the word "homing", the results will point you to the previous heading in this specific section of the documentation. + +![homing example](/static/image/homing.png) + +### 6.4 The Edit On GitHub Button + +Each markdown file in the documentation requires a link to its GitHub page for the site to correctly render the "Edit on GitHub" button for that page. + +![edit on github](/static/image/edit-on-github.png) + +If you create a new markdown file and/or you notice a file without the explicit GitHub URL, please add a URL entry to the [extra] section. For example, the following `url` is required for this page that you are reading (you can check the raw markdown contents of this page to see this example): + +``` +[extra] +url = "https://github.com/spinframework/spin-docs/blob/main/content/v4/contributing-docs.md" +``` + +### 6.5 How To Properly Edit CSS Styles + +> The following section (the running of the `npm run styles` command) is not necessary unless you are editing styles i.e. updating `.scss` files, in order to generate new `.css` files, as part of your contribution. + +Directly editing `.css` files is not recommended, because `.css` files are overwritten. Instead, if you would like to make and test a new/different style please go ahead and update the appropriate `.scss` file. The following command will automatically update the `.css` file that is relevant to the `.scss` file that you are editing: + + + +```bash +$ npm run styles +``` + +The above command is designed to be run in the background; enabling you to view your design changes (that are reflected in the `.css`) while you are editing the `.scss` in real-time. If you are not running this command in the background (i.e. just deliberately regenerating the `.css` files once), then the above command can be stopped by pressing `Ctrl` + `C`. + +### 6.6 Checking Your Content - Using Bartholomew's CLI + +The Bartholomew Command Line Interface (CLI) Tool is called `bart`. The `bart` CLI is a tool that simplifies working with Bartholomew projects, like this documentation site. The `bart` CLI is handy to ensure quality assurance of new and existing content. Installing the CLI is a cinch, so please go ahead and use it when contributing. + +To build the Bartholomew CLI from source perform the following commands: + + + +```bash +$ cd ~ +$ git clone https://github.com/fermyon/bartholomew.git +$ cd ~/bartholomew +$ make bart +``` + +Once built, you will find the `bart` CLI executable in the `~/bartholomew/target/release` directory. However, for convenience it would be a great idea to go ahead and add the `bart` executable to your system path, for example: + + + +```bash +$ sudo mv ~/bartholomew/target/release/bart /usr/local/bin/ +``` + +Once installed, you can use the CLI's `--help` flag to learn more. For example: + + + +```bash +$ bart --help +bart 0.6.0 +The Bartholomew CLI + +USAGE: + bart + +FLAGS: + -h, --help Prints help information + -V, --version Prints version information + +SUBCOMMANDS: + calendar Print the content calendar for a Bartholomew website + check Check content and identify errors or warnings + help Prints this message or the help of the given subcommand(s) + new Create a new page or website from a template +``` + +Let's take a quick look at how you can use the `bart` CLI to check any content that you are wanting to contribute. + +### 6.7 Checking Your Content - Preview a Documentation Page on Localhost + +You can host your changes to the documentation on your own machine (localhost) by using the following `spin` commands: + + + +```bash +$ npm ci +$ cd spin-up-hub +$ npm ci +$ cd .. +$ spin build +$ spin up -e "PREVIEW_MODE=1" +``` + +> Please note: using the `PREVIEW_MODE=1` as part of a `spin` command is safe on localhost and allows you to view the content (even if the `date` setting in the content's `.md` is set to a future date). It is often the case that you will be checking content before the publishing date via your system. The documentation's manifest file `spin.toml` has the `PREVIEW_MODE` set to `0` i.e. `environment = { PREVIEW_MODE = "0" }`. This `spin.toml` file is correct for a production environment and should always be `0` (so that the CMS adheres to the publishing `date` setting for content on the public site). Simply put, you can use `PREVIEW_MODE=1` safely in your command line on your localhost but you should never update the `spin.toml` file (in this regard). + +### 6.8 Scheduling Menu Items for Timed Release + +As mentioned above, all pages (`.md` files) in the documentation have a UTC date i.e. `date = "2023-07-25T17:26:00Z"`. The `date` is a page scheduling mechanism whereby each page is only displayed if the `date` has elapsed. Menu items (found in the `/templates/*.hbs` files) that relate to a scheduled page can also be scheduled (so the specific menu item and its associated page appear at the same time). Simply envelope the menu item with the following `if` syntax to synchronize the appearance of the menu item with the related page: + + + +``` +{{#if (timed_publish "2023-07-25T17:26:00Z" env.PREVIEW_MODE)}} + // Scheduled menu item for timed release +{{/if}} +``` + +> In order to keep the code tidy and readable it is advised to remove the `if` logic (from the `.hbs` file) that wraps the content, once the `timed_publish` has elapsed. + +### 7. Checking Web Pages + +The `bart check` command can be used to check the content. Simply pass in the content as a parameter. The documentation [uses shortcodes](https://developer.fermyon.com/bartholomew/shortcodes), so always pass `--shortcodes ./shortcodes` as shown below: + + + +```bash +$ bart check --shortcodes ./shortcodes content/variables.md +shortcodes: registering alert +shortcodes: registering details +shortcodes: registering tabs +shortcodes: registering startTab +shortcodes: registering blockEnd +βœ… content/variables.md +``` + +> Note: `using a wildcard `*` will check a whole directory via a single command. For example, running `bart check --shortcodes ./shortcodes content/*` will check all markdown files in the Spin project's documentation section. + +### 8. Add Changes + +Once your changes have been checked, go ahead and add your changes by moving to a top-level directory, under which your changes exist i.e. `cd ~/spin-docs`. + +Add your changes by running the following command, from the root of the spin-docs repository: + + + +```bash +$ git add +``` + +### 9. Commit Changes + +All commits must be signed off *and* GPG-signed with a GitHub verification key. The rest of this section is primarily for contributors not familiar with signing, and describes how to configure signing, and how to sign commits. + +First, ensure that your Git installation is configured sufficiently so that you can `--signoff` as part of the `git commit` command. Typically, you need the `user.name` and `user.email` to be configured in your Git session. You can check if these are set by typing `git config --list`. + +If you need to set these values please use the following commands: + + + +```bash +$ git config user.name "yourusername" +``` + + +```bash +$ git config user.email "youremail@somemail.com" +``` + +You must also set up a GPG verification key on your GitHub account, and add this to your Git settings. For more information about setting up a GPG verification key, see GitHub's documentation about [adding a GPG key to your GitHub account](https://docs.github.com/en/authentication/managing-commit-signature-verification/adding-a-gpg-key-to-your-github-account) and [telling your Git client about your GPG key](https://docs.github.com/en/authentication/managing-commit-signature-verification/telling-git-about-your-signing-key). + +With all this set up, type the following commit command, which both _signs off_ (--signoff) and _cryptographically signs_ the data (-S), and leaves a short commit message (-m): + + + +```bash +$ git commit -S --signoff -m "Updating documentation" +``` + +> Note: the `--signoff` option only adds a Signed-off-by trailer by the committer at the end of the commit log message. You must also use the `-S` option which will GPG-sign your commits. For more information about GPG-signing commits see [the GitHub documentation page for signing commits](https://docs.github.com/en/authentication/managing-commit-signature-verification/signing-commits). + +### 10. Push Changes + +At this stage, it is a good idea to just quickly check what GitHub thinks the origin is. For example, if we type `git remote -v` we can see that the origin is our repo; which we a) forked the original repo into and b) which we then cloned to our local disk so that we could edit: + + + +```bash +$ git remote -v +``` + +The above command will return output similar to the following: + +```bash +origin git@github.com:yourusername/spin-docs.git (fetch) +origin git@github.com:yourusername/spin-docs.git (push) +upstream https://github.com/spinframework/spin-docs (fetch) +upstream https://github.com/spinframework/spin-docs (push) +``` + +Once you are satisfied go ahead and push your changes: + + + +```bash +$ git push -u origin my_new_branch +``` + +### 11. Create a Pull Request + +If you return to your GitHub repository in your browser, you will notice that a PR has automatically been generated for you. + +Clicking on the green β€œCompare and pull request” button will allow you to add a title and description as part of the PR. + +![Compare and pull request](/static/image/compare_and_pull_request.png) + +You can also add any information in the textbox provided below the title. For example, screen captures and/or code/console/terminal snippets of your contribution working correctly and/or tests passing etc. + +Once you have finished creating your PR, please keep an eye on the PR; answering any questions as part of the collaboration process. + +**Thank You** + +Thanks for contributing. diff --git a/content/v4/contributing-spin.md b/content/v4/contributing-spin.md new file mode 100644 index 00000000..6e956527 --- /dev/null +++ b/content/v4/contributing-spin.md @@ -0,0 +1,139 @@ +title = "Contributing to Spin" +template = "main" +date = "2023-11-04T00:00:01Z" +[extra] +url = "https://github.com/spinframework/spin-docs/blob/main/content/v4/contributing-spin.md" + +--- +- [Spin Slack Channel](#spin-slack-channel) +- [Developer Community Calls](#developer-community-calls) +- [Code of Conduct](#code-of-conduct) +- [Making Code Contributions to Spin](#making-code-contributions-to-spin) +- [Before You Commit](#before-you-commit) +- [Committing and Pushing Your Changes](#committing-and-pushing-your-changes) + +We are delighted that you are interested in making Spin better! Thank you! + +This document will guide you through making your first contribution to the project. +We welcome and appreciate contributions of all types β€” opening issues, fixing +typos, adding examples, one-liner code fixes, tests, or complete features. + +## Spin Slack Channel + +Join us in the [Spin CNCF Slack Channel](https://cloud-native.slack.com/archives/C089NJ9G1V0)! This is a great place to ask questions, meet people in the community, and share what you are working on. If you're not already signed up for the CNCF Slack, request an invite [here](https://communityinviter.com/apps/cloud-native/cncf). + +## Developer Community Calls + +
+ + Active Contributors of fermyon/spin - Last 28 days +
+ +>> _Recent contributors to Spin, past 30 days. Widget courtesy of OSSinsight.io._ + +Each Monday at 2:30pm UTC and 9:00pm UTC (alternating), we meet to discuss Spin issues, roadmap, and ideas in our Spin Project Meetings. The Spin Project follows an [open planning process](https://www.fermyon.com/blog/moving-to-a-fully-open-planning-process-for-the-spin-project) - anyone who is interested is welcome to join the discussion. [Subscribe to this Google Calendar](https://calendar.google.com/calendar/u/1?cid=c3Bpbi5tYWludGFpbmVyc0BnbWFpbC5jb20) for meeting dates. + +The [Spin Project Meeting Agenda is a public document](https://docs.google.com/document/d/1EG392gb8Eg-1ZEPDy18pgFZvMMrdAEybpCSufFXoe00/edit?usp=sharing). The document contains a rolling agenda with the date and time of each meeting, the Zoom link, and topics of discussion for the day. You will also find the meeting minutes for each meeting and the link to the recording. If you have something you would like to demo or discuss at the project meeting, we encourage you to add it to the agenda. + +## Code of Conduct + +First, any contribution and interaction on the Spin project MUST follow our +[code of conduct](https://github.com/spinframework/spin/blob/main/CODE_OF_CONDUCT.md). Thank you for being +part of an inclusive and open community! + +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 chance 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 Rust installed. + +![Rust Version](https://img.shields.io/badge/dynamic/toml?url=https%3A%2F%2Fraw.githubusercontent.com%2Ffermyon%2Fspin%2Fmain%2FCargo.toml&query=$[%27workspace%27][%27package%27][%27rust-version%27]&label=Rust%20Version&logo=Rust&color=orange) + +After [installing Rust](https://www.rust-lang.org/tools/install) please ensure the `wasm32-wasip2` and + `wasm32-unknown-unknown` targets are configured. For example: + +```bash +rustup target add wasm32-wasip2 && rustup target add wasm32-unknown-unknown +``` + +In addition, make sure you have the following prerequisites configured: + +- [`rustfmt`](https://github.com/rust-lang/rustfmt) +- [`clippy`](https://github.com/rust-lang/rust-clippy) +- `make` +- [`rust-analyzer`](https://rust-analyzer.github.io/) extension (for Visual Studio Code users) +- [GPG signature verification for your GitHub commits](https://docs.github.com/en/authentication/managing-commit-signature-verification/about-commit-signature-verification) and remember to use a sign-off message (`git commit -S -s`) on each of your commits + +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: + + + +```bash +# clone the repository +$ git clone https://github.com/spinframework/spin && cd spin +# add a new remote pointing to your fork of the project +$ git remote add fork https://github.com//spin +# create a new branch for your work +$ git checkout -b + +# build the Spin CLI +$ cargo build + +# make sure compilation is successful +$ ./target/debug/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 +[document about extending Spin](./extending-and-embedding.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 unit or integration tests to +ensure your contribution is correct. + +## Before You Commit + +* Format the code (`cargo fmt`) +* Run Clippy (`cargo clippy`) +* Run the `lint` task (`make lint`) +* Build the project and run the tests (`make test`) + +Spin enforces lints and tests as part of continuous integration - running them locally will save you a round-trip to your pull request! + +If everything works locally, you're ready to commit your changes. + +## Committing and Pushing Your Changes + +We require commits to be signed both with an email address and with a GPG signature. + +> Because of the way GitHub runs enforcement, the GPG signature isn't checked until _after_ all tests have run. Be sure to GPG sign up front, as it can be a bit frustrating to wait for all the tests and _then_ get blocked on the signature! + + + +```bash +$ git commit -S -s -m "" +``` + +> Some contributors like to follow the [Conventional Commits](https://github.com/conventional-commits) convention for commit messages. + +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: + + + +```bash +# "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! diff --git a/content/v4/deploying.md b/content/v4/deploying.md new file mode 100644 index 00000000..656a64b8 --- /dev/null +++ b/content/v4/deploying.md @@ -0,0 +1,37 @@ +title = "Deployment Options" +template = "main" +date = "2023-11-04T00:00:01Z" +[extra] +url = "https://github.com/spinframework/spin-docs/blob/main/content/v4/deploying.md" + +--- + +This page covers known deployment options for Spin applications. [Please help keep this page up to date if we've missed one.](./contributing-docs.md) + +## Spin command line + +You can run the Spin CLI on a server just as you do on your development workstation. See the [Running Applications guide](/running-apps) for more details. + +> The command line provides no recovery or failover features. This may be fine for non-essential or private projects, but for anything that needs higher reliability, you'll need to add other components to provide that. + +## Kubernetes + +You can deploy Spin applications to a Kubernetes cluster with [SpinKube](https://www.spinkube.dev/docs/overview/) - an open source project that streamlines the experience of developing, deploying, and operating Wasm workloads on Kubernetes. Like Spin, SpinKube is a CNCF Sandbox project. + +## Microsoft Azure Kubernetes Service + +[The community `spin azure` plugin](https://github.com/Mossaka/spin-plugin-azure) streamlines deploying SpinKube and Spin applications to an AKS cluster. It provides for creating SpinKube clusters, setting up workload identity, CosmosDB integration, and application deployment. + +**To install the plugin:** `spin plugins install azure` + +**To create a SpinKube cluster:** `spin azure cluster create` + +**To deploy an application:** [see the workflow here](https://github.com/Mossaka/spin-plugin-azure?tab=readme-ov-file#workflow-explanation) + +## Akamai Functions + +[Akamai Functions](https://developer.fermyon.com/wasm-functions) is an edge environment hosted in the Akamai Cloud. It enables you to run Spin applications with global distribution. + +**To install the plugin:** `spin plugins install aka` + +**To deploy an application:** `spin aka deploy` diff --git a/content/v4/distributing-apps.md b/content/v4/distributing-apps.md new file mode 100644 index 00000000..91761c2a --- /dev/null +++ b/content/v4/distributing-apps.md @@ -0,0 +1,154 @@ +title = "Publishing and Distribution" +template = "main" +date = "2023-11-04T00:00:01Z" +enable_shortcodes = true +[extra] +url = "https://github.com/spinframework/spin-docs/blob/main/content/v4/distributing-apps.md" + +--- +- [Logging Into a Registry](#logging-into-a-registry) + - [Logging In Using a Token](#logging-in-using-a-token) + - [Fallback Credentials](#fallback-credentials) +- [Publishing a Spin Application to a Registry](#publishing-a-spin-application-to-a-registry) + - [Publishing and Build Profiles](#publishing-and-build-profiles) +- [Running Published Applications](#running-published-applications) + - [Running Published Applications by Digest](#running-published-applications-by-digest) + - [Pulling a Published Application](#pulling-a-published-application) +- [Signing Spin Applications and Verifying Signatures](#signing-spin-applications-and-verifying-signatures) + +If you would like to publish a Spin application, so that other users can run it, you can do so using a _container registry_. + +{{ details "What's all this about containers?" "The registry protocol was originally created to publish and distribute Docker containers. Over time, registries have evolved to host other kinds of artifact - see [the OCI registry artifacts project](https://github.com/opencontainers/artifacts) for more information. However, the term remains, in services such as GitHub Container Registry or AWS Elastic Container Registry, and in the generic description _OCI (Open Container Initiative) registries_. When you use a 'container' registry to publish and distribute Spin applications, there are no actual containers involved at all!" }} + +Many cloud services offer public registries. Examples include GitHub Container Registry, Docker Hub, or Amazon Elastic Container Registry. These support both public and private distribution. You can also run your own registry using open source software. + +## Logging Into a Registry + +Before you can publish to a registry, or run applications whose registry artifacts are private, you must log in to the registry. This example shows logging into the GitHub Container Registry, `ghcr.io`: + + + +```bash +$ spin registry login ghcr.io +``` + +If you don't provide any options to `spin registry login`, it prompts you for a username and password. + +### Logging In Using a Token + +In a non-interactive environment such as GitHub Actions, you will typically log in using a token configured in the environment settings, rather than a password. To do this, use the `--password-stdin` flag, and `echo` the token value to the login command's standard input. This example shows logging into GHCR from a GitHub action: + + + +```bash +$ echo "$\{{ secrets.GITHUB_TOKEN }}" | spin registry login ghcr.io --username $\{{ github.actor }} --password-stdin +``` + +Other environments will have different ways of referring to the token and user but the pattern remains the same. + +### Fallback Credentials + +If you have logged into a registry using `docker login`, but not using `spin registry login`, Spin will fall back to your Docker credentials. + +## Publishing a Spin Application to a Registry + +To publish an application to a registry, use the `spin registry push` command. You must provide a _reference_ for the published application. This is a string whose format is defined by the registry standard, and generally consists of `//:`. (In specific circumstances you may be able to omit the username and/or the version. If you want more detail on references, see the OCI documentation.) + +> Remember you will (usually) need to be logged in to publish to a registry. + +Here is an example of pushing an application to GHCR: + + + +```bash +$ spin registry push ghcr.io/alyssa-p-hacker/hello-world:v1 +Pushed with digest sha256:06b19 +``` + +Notice that the username is part of the reference; the registry does not infer it from the login. Also notice that the version is specified explicitly; Spin does not infer it from the `spin.toml` file. + +> Whether newly uploaded artifacts are private or public depends on the registry. See your registry documentation. This will also tell you how to change the visibility if the default is not what you want. + +### Publishing and Build Profiles + +If your application has different [build profiles](./build.md#building-with-profiles), and you want to publish it using a profile other than the default, you must specify that profile when you publish. The pushed image will contain _only_ the selected profile. (You won't be able to specify a profile when you run it from the registry.) + + + +```bash +# Push the application in its default profile +$ spin registry push --build ghcr.io/alyssa-p-hacker/hello-world:v1 +# Push the application in its debug profile +$ spin registry push --profile debug --build ghcr.io/alyssa-p-hacker/hello-world-debug:v1 +``` + +## Running Published Applications + +To run a published application from a registry, use `spin up -f` and pass the registry reference: + + + +```bash +$ spin up -f ghcr.io/alyssa-p-hacker/hello-world:v1 +``` + +> Remember that if the artifact is private you will need to be logged in, with permission to access it. + +> Spin optimizes downloads using a local [registry cache](./cache). When running an application from a remote registry, Spin always tries to check the registry for updates, even if the application has already been pulled. However, content files that are already pulled will not be re-downloaded. This applies even if they were downloaded as part of a different application. + +### Running Published Applications by Digest + +Registry versions are mutable; that is, the owner of an application can change which build the `:v1` label points to at any time. If you want to run a specific build of the package, you can refer to it by _digest_. This is similar to a Git commit hash: it is immutable, meaning the same digest always gets the exact same data, no matter what the package owner does. To do this, use the `@sha256:...` syntax instead of the `:v...` syntax: + + + +```bash +$ spin up -f ghcr.io/alyssa-p-hacker/hello-world@sha256:06b19 +``` + +### Pulling a Published Application + +`spin up` automatically downloads the application from the registry. If you want to manually download the application, without running it, use the `spin registry pull` command: + + + +```bash +$ spin registry pull ghcr.io/alyssa-p-hacker/hello-world:v1 +$ spin registry pull ghcr.io/alyssa-p-hacker/hello-world@sha256:06b19 +``` + +> Downloaded applications are cached. When run, or pulled again, Spin checks to see if they have changed from the cached copy, and downloads only the changes if any. + +## Signing Spin Applications and Verifying Signatures + +Because Spin uses the container registry standards to distribute applications, it can also take advantage of tooling built around those standards. Here is an example of using [Cosign and Sigstore](https://docs.sigstore.dev/) to sign and verify a Spin application: + + + +```bash +# Push your Spin application to any registry that supports the OCI registry artifacts, +# such as the GitHub Container Registry, Docker Hub, Azure ACR, or AWS ECR. +$ spin registry push ghcr.io/alyssa-p-hacker/hello-world:v1 + +# You can now sign your Spin app using Cosign (or any other tool that can sign +# OCI registry objects). +$ cosign sign ghcr.io/alyssa-p-hacker/hello-world@sha256:06b19 +Generating ephemeral keys... +Retrieving signed certificate... +tlog entry created with index: 12519542 +Pushing signature to: ghcr.io/alyssa-p-hacker/hello-world + +# Someone interested in your application can now use Cosign to verify the signature +# before running the application. +$ cosign verify ghcr.io/alyssa-p-hacker/hello-world@sha256:06b19 +Verification for ghcr.io/alyssa-p-hacker/hello-world@sha256:06b19 -- +The following checks were performed on each of these signatures: + - The cosign claims were validated + - Existence of the claims in the transparency log was verified offline + - Any certificates were verified against the Fulcio roots. + +# The consumer of your app can now run it from the registry. +$ spin up -f ghcr.io/alyssa-p-hacker/hello-world@sha256:06b19 +``` + +> You'll need Cosign 2.0 or above to verify Spin artifacts. diff --git a/content/v4/dynamic-configuration.md b/content/v4/dynamic-configuration.md new file mode 100644 index 00000000..6f467bf3 --- /dev/null +++ b/content/v4/dynamic-configuration.md @@ -0,0 +1,425 @@ +title = "Runtime Configuration" +template = "main" +date = "2023-11-04T00:00:01Z" +enable_shortcodes = true +[extra] +url = "https://github.com/spinframework/spin-docs/blob/main/content/v4/dynamic-configuration.md" + +--- + +- [Application Variables Runtime Configuration](#application-variables-runtime-configuration) + - [Environment Variable Provider](#environment-variable-provider) + - [Vault Application Variable Provider](#vault-application-variable-provider) + - [Vault Application Variable Provider Example](#vault-application-variable-provider-example) + - [Azure Key Vault Application Variable Provider](#azure-key-vault-application-variable-provider) + - [Azure Key Vault Application Variable Provider Example](#azure-key-vault-application-variable-provider-example) +- [Key Value Store Runtime Configuration](#key-value-store-runtime-configuration) + - [File Key Value Store Provider](#file-key-value-store-provider) + - [Redis Key Value Store Provider](#redis-key-value-store-provider) + - [Azure CosmosDB Key Value Store Provider](#azure-cosmosdb-key-value-store-provider) + - [AWS DynamoDB Key Value Store Provider](#aws-dynamodb-key-value-store-provider) + - [Multiple and Non-Default Key-Value Stores](#multiple-and-non-default-key-value-stores) +- [SQLite Storage Runtime Configuration](#sqlite-storage-runtime-configuration) + - [LibSQL Storage Provider](#libsql-storage-provider) +- [LLM Runtime Configuration](#llm-runtime-configuration) + - [Remote Compute Provider](#remote-compute-provider) + - [`default` API](#default-api) + - [`open_ai` API](#open_ai-api) +- [Outbound HTTP Runtime Configuration](#outbound-http-runtime-configuration) + +You can change the configuration for Spin application features such as [application variables](./variables), +[key value storage](./kv-store-api-guide), [SQL storage](./sqlite-api-guide) +and [Serverless AI](./serverless-ai-api-guide) at the time you run the application, +requiring no changes to the application code itself. + +This runtime configuration data is stored in the `runtime-config.toml` file and passed to `spin up` via the `--runtime-config-file` flag. + +{{ details "Why do I need to specify `--runtime-config-file` rather than Spin detecting it automatically?" "Spin uses safe, local defaults for saving your data. The runtime config file can override those. It should be up to you to opt into that override." }} + +Let's look at each configuration category in-depth below. + +## Application Variables Runtime Configuration + +When an application needs the value of an [application variable](./variables), it obtains it from a provider. By default, the only provider Spin considers is the [environment variables provider](#environment-variable-provider). That is, application variables are derived from the environment variables of the Spin process. + +You can also tell Spin to use the [Vault provider](#vault-application-variable-provider) and/or the [Azure Key Vault provider](#azure-key-vault-application-variable-provider), by setting these up in the runtime config file. + +You can tell Spin to use multiple application variable providers. Spin prioritises them in the order they appear in the runtime config file, with higher-listed providers +taking precedence. The environment variable provider always has the highest priority. + +The provider examples below show how to use or configure each +provider. For examples on how to access these variables values within your application, see +[Using Variables from Applications](./variables#using-variables-from-applications). + +### Environment Variable Provider + +The environment variable provider gets variable values from the `spin` process's +environment (_not_ the component `environment`). Variable keys are translated +to environment variables by upper-casing and prepending with `SPIN_VARIABLE_`: + + + +```bash +$ export SPIN_VARIABLE_API_KEY="1234" # Sets the `api_key` value. +$ spin up +``` + +If the current directory contains a file named `.env`, then the provider will look there for environment variables not present in the actual environment. Entries in `.env` must follow the same `SPIN_VARIABLE_...` naming convention as 'real' environment variables, and have the same `key=value` format as setting a variable on the command line. For example: + +``` +# The .env file can contain comments +SPIN_VARIABLE_API_KEY="1234" +``` + +> The `.env` file must be in the _current_ directory (not necessarily the directory containing the application manifest). + +Entries in the `.env` file are _lower_ priority than actual environment variables. + +### Vault Application Variable Provider + +The Vault application variable provider gets secret values from [HashiCorp Vault](https://www.vaultproject.io/). +Currently, only the [KV Secrets Engine - Version 2](https://developer.hashicorp.com/vault/docs/secrets/kv/kv-v2) is supported. +You can set up the v2 kv secret engine at any mount point and provide Vault information in +the [runtime configuration](#runtime-configuration) file: + + + +```toml +[[config_provider]] +type = "vault" +url = "http://127.0.0.1:8200" +token = "root" +mount = "secret" +``` + +#### Vault Application Variable Provider Example + +1. [Install Vault](https://developer.hashicorp.com/vault/tutorials/get-started/install-binary). +2. Start Vault: + + + +```bash +$ vault server -dev -dev-root-token-id root +``` + +3. Set a password: + + + +```bash +$ export VAULT_TOKEN=root +$ export VAULT_ADDR=http://127.0.0.1:8200 +$ vault kv put secret/secret value="test_password" +$ vault kv get secret/secret +``` + +4. Go to the [Vault variable provider example](https://github.com/fermyon/enterprise-architectures-and-patterns/tree/main/application-variable-providers/vault-provider) application. +5. Build and run the `vault-provider` app: + + + +```bash +$ spin build +$ spin up --runtime-config-file runtime-config.toml +``` + +6. Test the app: + + + +```bash +$ curl localhost:3000 --data "test_password" +{"authentication": "accepted"} +``` + + +```bash +$ curl localhost:3000 --data "wrong_password" +{"authentication": "denied"} +``` + +### Azure Key Vault Application Variable Provider + +The Azure Key Vault application variable provider gets secret values from [Azure Key Vault](https://azure.microsoft.com/en-us/products/key-vault). + +Currently, only receiving the latest version of a secret is supported. + +For authenticating against Azure Key Vault, you must use the client credentials flow. To do so, create a Service Principal (SP) within your Microsoft Entra ID (previously known as Azure Active Directory) and assign the `Key Vault Secrets User` role to the SP on the scope of your Azure Key Vault instance. + +You can set up Azure Key Vault application variable provider in +the [runtime configuration](#runtime-configuration) file: + + + +```toml +[[config_provider]] +type = "azure_key_vault" +vault_url = "https://spin.vault.azure.net/" +client_id = "12345678-1234-1234-1234-123456789012" +client_secret = "some.generated.password" +tenant_id = "12345678-1234-1234-1234-123456789012" +authority_host = "AzurePublicCloud" +``` + +#### Azure Key Vault Application Variable Provider Example + +1. Deploy Azure Key Vault: + + + +```bash +# Variable Definition +$ KV_NAME=spin123 +$ LOCATION=germanywestcentral +$ RG_NAME=rg-spin-azure-key-vault + +# Create an Azure Resource Group and an Azure Key Vault +$ az group create -n $RG_NAME -l $LOCATION +$ az keyvault create -n $KV_NAME \ + -g $RG_NAME \ + -l $LOCATION \ + --enable-rbac-authorization true + +# Grab the Azure Resource Identifier of the Azure Key Vault instance +$ KV_SCOPE=$(az keyvault show -n $KV_NAME -g $RG_NAME -otsv --query "id") +``` + +2. Add a Secret to the Azure Key Vault instance: + + + +```bash +# Grab the ID of the currently signed in user in Azure CLI +$ CURRENT_USER_ID=$(az ad signed-in-user show -otsv --query "id") + +# Make the currently signed in user a "Key Vault Secrets Officer" +# on the scope of the new Azure Key Vault instance +$ az role assignment create --assignee $CURRENT_USER_ID \ + --role "Key Vault Secrets Officer" \ + --scope $KV_SCOPE + +# Create a test secret called `secret` in the Azure Key Vault instance +$ az keyvault secret set -n secret --vault-name $KV_NAME --value secret_value --onone +``` + +3. Create a Service Principal and Role Assignment for Spin: + + + +```bash +$ export SP_NAME=sp-spin + +# Create the SP +$ SP=$(az ad sp create-for-rbac -n $SP_NAME -ojson) + +# Populate local shell variables from the SP JSON +$ CLIENT_ID=$(echo $SP | jq -r '.appId') +$ CLIENT_SECRET=$(echo $SP | jq -r '.password') +$ TENANT_ID=$(echo $SP | jq -r '.tenant') + +# Assign the "Key Vault Secrets User" role to the SP +# allowing it to read secrets from the Azure Key Vault instance +$ az role assignment create --assignee $CLIENT_ID \ + --role "Key Vault Secrets User" \ + --scope $KV_SCOPE +``` + +4. Go to the [Azure Key Vault variable provider example](https://github.com/fermyon/enterprise-architectures-and-patterns/tree/main/application-variable-providers/azure-key-vault-provider) application. +5. Replace Tokens in `runtime-config.toml`. + +The `azure-key-vault-provider` application contains a `runtime-config.toml` file. Replace all tokens (e.g. `$KV_NAME$`) with the corresponding shell variables you created in the previous steps. + +6. Build and run the `azure-key-vault-provider` app: + + + +```bash +$ spin build +$ spin up --runtime-config-file runtime-config.toml +``` + +7. Test the app: + + + +```bash +$ curl localhost:3000 +Loaded Secret from Azure Key Vault: secret_value +``` + +## Key Value Store Runtime Configuration + +Spin provides built-in key-value storage. By default, this storage is backed by a file in the application `.spin` directory. However, you can use the Spin runtime configuration file (`runtime-config.toml`) to modify the file location, or to use a different backing store. The available store options are the file provider, an external Redis database, Azure CosmosDB or AWS DynamoDB. + +### File Key Value Store Provider + +To use a file as a backend for Spin's key-value store, set the type to `spin`, and provide a file path: + +```toml +[key_value_store.default] +type = "spin" +path = ".spin/precious-data.db" +``` + +Spin creates the path and file if they don't already exist. + +> If, during development, you need to examine keys and values, you can open the file using `sqlite3` or another SQLite tools. However, the file format is subject to change, and you should not rely on it. + +### Redis Key Value Store Provider + +To use a Redis store as a backend for Spin's key-value store, set the type to `redis` and provide the URL of the Redis host: + +```toml +[key_value_store.default] +type = "redis" +url = "redis://localhost" +``` + +### Azure CosmosDB Key Value Store Provider + +To use an Azure CosmosDB database as a backend for Spin's key-value store, set the type to `azure_cosmos` and specify your database account details: + +```toml +[key_value_store.default] +type = "azure_cosmos" +key = "" +account = "" +database = "" +container = "" +``` + +> Note: The CosmosDB container must be created with the default partition key, `/id`. + +### AWS DynamoDB Key Value Store Provider + +To use an Amazon Web Services DynamoDB database as a backend for Spin's key-value store, set the type to `aws_dynamo` and specify your database account details: + +```toml +[key_value_store.default] +type = "aws_dynamo" +region = "" # e.g. "us-east-1" +table = "" # e.g. "spin-key-values" +consistent_read = true # optional, to use strongly consistent reads +``` + +You may optionally provide `access_key` and `secret_key` credentials; otherwise, Spin picks these up from your [AWS environment variables](https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-envvars.html). For short-lived credentials, you can additionally provide `token` (from the [Get Session Token API](https://docs.aws.amazon.com/STS/latest/APIReference/API_GetSessionToken.html) or [`aws sts get-session-token` CLI command](https://awscli.amazonaws.com/v2/documentation/api/latest/reference/sts/get-session-token.html)). + +By default, the DynamoDB backend uses eventually consistent reads. The `consistent_read` option turns on [strongly consistent reads](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/HowItWorks.ReadConsistency.html). This ensures reads are up-to-date with writes, at an increased cost. See the [DynamoDB documentation](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/HowItWorks.ReadConsistency.html) for more information. + +> Note: The DynamoDB table must be created with the partition key `PK`. It must have no sort key (so that the partition key is the primary key). + +### Multiple and Non-Default Key-Value Stores + +Whilst a single default store may be sufficient for certain application use cases, each Spin application can be configured to support multiple stores of any `type`, as shown in the `runtime-config.toml` file below: + +```toml +# This defines a new store named user_data +[key_value_store.user_data] +type = "spin" +path = ".spin/user_data.db" + +# This defines a new store named other_data backed by a Redis database +[key_value_store.other_data] +type = "redis" +url = "redis://localhost" +``` + +You must individually grant each component access to the stores that it needs to use. To do this, use the `component.key_value_stores` entry in the component manifest within `spin.toml`. See [Spin Key Value Store](kv-store-api-guide.md) for more details. + +## SQLite Storage Runtime Configuration + +Spin provides built-in SQLite storage. By default, this is backed by a database that Spin creates for you underneath your application directory (in the `.spin` subdirectory). However, you can use the Spin runtime configuration file (`runtime-config.toml`) to add and customize SQLite databases. + +The following example `runtime-config.toml` tells Spin to map the `default` database to an SQLite database elsewhere in the file system: + +```toml +[sqlite_database.default] +type = "spin" +path = "/planning/todo.db" +``` + +If you need more than one database, you can configure multiple databases, each with its own name: + +```toml +# This defines a new store named todo +[sqlite_database.todo] +type = "spin" +path = "/planning/todo.db" + +# This defines a new store named finance +[sqlite_database.finance] +type = "spin" +path = "/super/secret/monies.db" +``` + +Spin creates any database files that don't exist. However, it is up to you to delete them when you no longer need them. + +### LibSQL Storage Provider + +Spin can also use [libSQL](https://libsql.org/) databases accessed over HTTPS. libSQL is fully compatible with SQLite but provides additional features including remote, distributed databases. + +> Spin does not provide libSQL access to file-based databases, only databases served over HTTPS. Specifically, Spin supports [the `sqld` libSQL server](https://github.com/libsql/sqld). + +To use libSQL, set `type = "libsql"` in your `runtime-config.toml` entry. You must then provide a `url` and authentication `token` instead of a file path. For example, this entry tells Spin to map the `default` database to a libSQL service running on `libsql.example.com`: + +```toml +# This tells Spin to use the remote host as its default database +[sqlite_database.default] +type = "libsql" +url = "https://sensational-penguin-ahacker.libsql.example.com" +token = "a secret" +``` + +Spin does _not_ create libSQL databases. Use your hosting service's tools to create them (or `sqld` if you are self-hosting) . You can still set up tables and data in a libSQL database via `spin up --sqlite`. + +> You must include the scheme in the `url` field. The scheme must be `http` or `https`. Non-HTTP libSQL protocols are not supported. + +The `default` database will still be defined, even if you add other databases. + +By default, components will not have access to any of these databases (even the default one). You must grant each component access to the databases that it needs to use. To do this, use the `component.sqlite_databases` entry in the component manifest within `spin.toml`. See [SQLite Database](./sqlite-api-guide.md) for more details. + +## LLM Runtime Configuration + +Spin provides a Large Language Model interface for interacting with LLMs for inferencing and embedding. The default host implementation is to use local CPU/GPU compute. However, you can use the Spin runtime configuration file (`runtime-config.toml`) to direct Spin to use remote compute using HTTP requests. + +### Remote Compute Provider + +The following is an example of how an application's `runtime-config.toml` file can be configured to use the remote compute option. Note the `type`, `url` and `auth_token` are set to `remote_http`, URL of the server and the auth token for the server. + +```toml +[llm_compute] +type = "remote_http" +url = "http://example.com" +auth_token = "" +api_type = "default" +``` + +By default, components will not have access to the LLM models unless granted explicit access through the `component.ai_models` entry in the component manifest within `spin.toml`. See [Serverless AI](./serverless-ai-api-guide) for more details. + +Remote compute supports two APIs, selected using the `api_type` field. + +#### `default` API + +If the `api_type` is `default`, Spin uses its own "Cloud GPU" API. This requires you to deploy your own LLM proxy service. You can find a reference implementation of a proxy service in the [`spin-cloud-gpu plugin repository`](https://github.com/fermyon/spin-cloud-gpu/blob/main/fermyon-cloud-gpu/src/index.ts). + +> If you have a Fermyon Cloud account, you can deploy a proxy service there using the [`cloud-gpu` plugin](https://github.com/fermyon/spin-cloud-gpu). + +#### `open_ai` API + +If the `api_type` is `open_ai`, Spin uses the [OpenAI API](https://github.com/openai/openai-openapi). This API is offered by some commercial LLM providers. + +## Outbound HTTP Runtime Configuration + +You can control applications' use of outbound HTTP via the runtime configuration file via the `[outbound_http]` section. + +* By default, Spin pools outbound HTTP connections to the same destination. You can turn this off by setting `connection_pooling = false`. +* By default, Spin does not limit the number of concurrent outbound HTTP connections the application may make. You can set a limit by setting `max_concurrent_requests = `. + +For example, the following runtime config section prevents connection pooling and limits the application to 10 concurrent requests: + +```toml +[outbound_http] +connection_pooling = false +max_concurrent_requests = 10 +``` diff --git a/content/v4/extending-and-embedding.md b/content/v4/extending-and-embedding.md new file mode 100644 index 00000000..14e13744 --- /dev/null +++ b/content/v4/extending-and-embedding.md @@ -0,0 +1,179 @@ +title = "Extending and Embedding Spin" +template = "main" +date = "2023-11-04T00:00:01Z" +[extra] +url = "https://github.com/spinframework/spin-docs/blob/main/content/v4/extending-and-embedding.md" + +--- +- [Extending Spin with a Custom Trigger](#extending-spin-with-a-custom-trigger) + - [Implement the Trigger World](#implement-the-trigger-world) + - [The Trigger Implements the `Trigger` Trait](#the-trigger-implements-the-trigger-trait) + - [The Trigger is an Executable](#the-trigger-is-an-executable) + - [The Trigger Detects Events...](#the-trigger-detects-events) + - [...and Invokes the Guest](#and-invokes-the-guest) +- [Other Ways to Extend and Use Spin](#other-ways-to-extend-and-use-spin) + +## Extending Spin with a Custom Trigger + +> The complete example for a custom trigger [can be found on GitHub](https://github.com/spinframework/spin/tree/main/examples/spin-timer). + +Spin currently implements triggers and application models for: + +- [HTTP applications](./http-trigger.md) that are triggered by incoming HTTP +requests, and that return an HTTP response +- [Redis applications](./redis-trigger.md) that are triggered by messages on Redis +channels + +The Spin internals and execution context (the part of Spin executing +components) are agnostic of the event source and application model. +In this document, we will explore how to extend Spin with custom event sources +(triggers) and application models built on top of the WebAssembly component +model, as well as how to embed Spin in your application. + +In this article, we will build a Spin trigger to run the applications based on a +timer, executing Spin components at a configured time interval. + +> Custom triggers are an experimental feature. The trigger APIs are not stabilized, and you may need to tweak your trigger code as you update to new Spin versions. + +Application entry points are defined using +[WebAssembly Interface (WIT)](https://component-model.bytecodealliance.org/design/wit.html). Here's the entry point for HTTP triggers: + + + +```fsharp +interface incoming-handler { + use types.{incoming-request, response-outparam} + + handle: func( + request: incoming-request, + response-out: response-outparam + ) +} +``` + +The entry point we want to execute for our timer trigger takes no input and doesn't return anything. This is purposefully chosen +to be a simple function signature. For simplicity, we allow guest code to use Spin's [application variables API](./variables.md) but not other Spin APIs. + +> Custom trigger guest code can't currently use the Spin SDK and must refer to the Spin WITs directly. + +Here is the resulting timer WIT: + + + +```fsharp +// examples/spin-timer/spin-timer.wit +package fermyon:example + +world spin-timer { + import fermyon:spin/variables@2.0.0 + export handle-timer-request: func() +} +``` + +`handle-timer-request` is the function that all components executed by the timer trigger must +implement, and which is used by the timer executor when instantiating and +invoking the component. + +The timer trigger itself is a Spin plugin whose name is `trigger-timer`. The first part must be `trigger` and the second part is the trigger type. + +You can see the full timer trigger code at the link above but here are some key features. + +### Implement the Trigger World + +The timer trigger implements the WIT world described in `spin-timer.wit`. To do that, it uses the `wasmtime` binding generator β€” this generates code that allows the trigger to invoke the guest's entry point, and allows the guest to invoke the Spin APIs available in the world. + + + +```rust +// examples/spin-timer/src/main.rs +wasmtime::component::bindgen!({ + path: ".", + world: "spin-timer", + async: true +}); +``` + +### The Trigger Implements the `Trigger` Trait + +Using `Trigger` allows the trigger to offload a great deal of boilerplate loader work to the `spin_trigger` crate and the `FactorsTriggerCommand` CLI helper. + +```rust +struct TimerTrigger { + speedup: u64, + component_timings: HashMap, +} + +#[async_trait] +impl Trigger for TimerTrigger { + // ... +} +``` + +The `Trigger` trait is generic in the set of _factors_ supported by the trigger - this is roughly the set of APIs available to guest code. In most circumstances, your implementation should also be generic, as shown, because your trigger is only concerned with detecting events, and can do that regardless of what APIs are available to the guests that handle those events. + +### The Trigger is an Executable + +A trigger is a separate program, so that it can be installed as a plugin. So it is a Rust `bin` project and has a `main` function. It can be useful to also provide a library crate, so that projects that embed Spin can load it in process if desired, but the timer sample doesn't currently show that. + +```rust +type Command = FactorsTriggerCommand; +// | | +// the trigger type you created above | +// the factors (APIs) available to guests + +#[tokio::main] +async fn main() -> Result<(), Error> { + let t = Command::parse(); + t.run().await +} +``` + +`FactorsTriggerCommand` allows you to configure the set of factors. `spin_runtime_factors::FactorsBuilder` will give you a set that matches the Spin CLI, which is almost always the right choice for a trigger plugin. If other hosts link your trigger, they will initialize it with the factors they support instead. (This is why it's desirable for your `Trigger` implementation to be as generic as possible!) + +### The Trigger Detects Events... + +In this case the trigger "detects" events by running a timer. In most cases, the trigger detects events by listening on a socket, completion port, or other mechanism, or by polling a resource such as a directory or an HTTP endpoint. + +```rust +for (component_id, interval_secs) in &self.component_timings { + scope.spawn(async { + let duration = + tokio::time::Duration::from_millis(*interval_secs * 1000 / speedup); + loop { + tokio::time::sleep(duration).await; + + self.handle_timer_event(&trigger_app, component_id) + .await + .unwrap(); + } + }); +} +``` + +### ...and Invokes the Guest + +The `Trigger` and `FactorsTriggerCommand` infrastructure provides the trigger with a `TriggerApp` representing the configured WebAssembly environment, already initialized with the guest Wasm for each component. When an event occurs, the trigger creates a component _instance_ from the `TriggerApp` and invokes it via the WIT interfaces. + +```rust +async fn handle_timer_event( + &self, + trigger_app: &TriggerApp, + component_id: &str, +) -> anyhow::Result<()> { + // Obtain a component instance from the app + let instance_builder = trigger_app.prepare(component_id)?; + let (instance, mut store) = instance_builder.instantiate(()).await?; + // Wrap the instance in the generated WIT bindings + let timer = SpinTimer::new(&mut store, &instance)?; + // Invoke the guest entry point via the bindings + timer.call_handle_timer_request(&mut store).await +} +``` + +## Other Ways to Extend and Use Spin + +Besides building custom triggers, the internals of Spin could also be used independently: + +- The Spin execution context can be used entirely without a `spin.toml` application manifest β€” for embedding scenarios, the configuration for the +execution can be constructed without a `spin.toml`, for example by running applications only from a registry. +- You can create a custom Spin runtime with a different set of factors to target specific scenarios. diff --git a/content/v4/go-components.md b/content/v4/go-components.md new file mode 100644 index 00000000..eaa6a653 --- /dev/null +++ b/content/v4/go-components.md @@ -0,0 +1,382 @@ +title = "Building Spin components in Go" +template = "main" +date = "2023-11-04T00:00:01Z" +[extra] +url = "https://github.com/spinframework/spin-docs/blob/main/content/v4/go-components.md" + +--- + +- [Versions](#versions) +- [HTTP Components](#http-components) +- [Sending Outbound HTTP Requests](#sending-outbound-http-requests) +- [Redis Components](#redis-components) +- [Storing Data in Redis From Go Components](#storing-data-in-redis-from-go-components) +- [Using Go Packages in Spin Components](#using-go-packages-in-spin-components) +- [Storing Data in the Spin Key-Value Store](#storing-data-in-the-spin-key-value-store) +- [Storing Data in SQLite](#storing-data-in-sqlite) +- [AI Inferencing From Go Components](#ai-inferencing-from-go-components) + +> This guide assumes you have Spin installed. If this is your first encounter with Spin, please see the [Quick Start](quickstart), which includes information about installing Spin with the Go templates, installing required tools, and creating Go applications. + +> This guide assumes you are familiar with the Go programming language, and that +> you have +> [configured the TinyGo toolchain locally](https://tinygo.org/getting-started/install/). +Using TinyGo to compile components for Spin is currently required, as the +[Go compiler doesn't currently have support for compiling to WASI](https://github.com/golang/go/issues/31105). + +> All examples from this page can be found in [the Spin Go SDK repository on GitHub](https://github.com/spinframework/spin-go-sdk/tree/main/examples). + +[**Want to go straight to the Spin SDK reference documentation?** Find it here.](https://pkg.go.dev/github.com/spinframework/spin-go-sdk/v2) + +## Versions + +TinyGo `0.35.0` is recommended, which requires Go `v1.22+`. Older versions of TinyGo may not support the command-line flags used when building Spin applications. + +## HTTP Components + +In Spin, HTTP components are triggered by the occurrence of an HTTP request, and +must return an HTTP response at the end of their execution. Components can be +built in any language that compiles to WASI, and Go has improved support for +writing applications, through its SDK. + +Building a Spin HTTP component using the Go SDK means writing a single function, +`init` β€” below is a complete implementation for such a component: + + + +```go +// A Spin component written in Go that returns "Hello, Fermyon!" +package main + +import ( + "fmt" + "net/http" + + spinhttp "github.com/spinframework/spin-go-sdk/v2/http" +) + +func init() { + spinhttp.Handle(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "text/plain") + fmt.Fprintln(w, "Hello Fermyon!") + }) +} +``` + +The Spin HTTP component (written in Go) can be built using the `tingygo` toolchain: + + + +```bash +$ tinygo build -target=wasip1 -gc=leaking -buildmode=c-shared -no-debug -o main.wasm . +``` + +Once built, we can run our Spin HTTP component using the Spin up command: + + + +```bash +$ spin up +``` + +The Spin HTTP component can now receive and process incoming requests: + + + +```bash +$ curl -i localhost:3000 +HTTP/1.1 200 OK +content-type: text/plain +content-length: 15 + +Hello Fermyon! +``` + +The important things to note in the implementation above: + +- the entry point to the component is the standard `func init()` for Go programs +- handling the request is done by calling the `spinhttp.Handle` function, +which takes a `func(w http.ResponseWriter, r *http.Request)` as parameter β€” these +contain the HTTP request and response writer you can use to handle the request +- the HTTP objects (`*http.Request`, `http.Response`, and `http.ResponseWriter`) +are the Go objects from the standard library, so working with them should feel +familiar if you are a Go developer + +## Sending Outbound HTTP Requests + +If allowed, Spin components can send outbound requests to HTTP endpoints. Let's +see an example of a component that makes a request to +[an API that returns random animal facts](https://random-data-api.fermyon.app/animals/json) and +inserts a custom header into the response before returning: + + + +```go +// A Spin component written in Go that sends a request to an API +// with random animal facts. + +package main + +import ( + "fmt" + "net/http" + "os" + + spinhttp "github.com/spinframework/spin-go-sdk/v2/http" +) + +func init() { + spinhttp.Handle(func(w http.ResponseWriter, r *http.Request) { + resp, _ := spinhttp.Get("https://random-data-api.fermyon.app/animals/json") + + fmt.Fprintln(w, resp.Body) + fmt.Fprintln(w, resp.Header.Get("content-type")) + + // `spin.toml` is not configured to allow outbound HTTP requests to this host, + // so this request will fail. + if _, err := spinhttp.Get("https://fermyon.com"); err != nil { + fmt.Fprintf(os.Stderr, "Cannot send HTTP request: %v", err) + } + }) +} +``` + +The Outbound HTTP Request example above can be built using the `tingygo` toolchain: + + + +```bash +$ tinygo build -target=wasip1 -gc=leaking -buildmode=c-shared -no-debug -o main.wasm . +``` + +Before we can execute this component, we need to add the +`random-data-api.fermyon.app` domain to the application manifest `allowed_outbound_hosts` +list containing the list of domains the component is allowed to make HTTP +requests to: + + + +```toml +# spin.toml +spin_manifest_version = 2 + +[application] +name = "spin-hello-tinygo" +version = "1.0.0" + +[[trigger.http]] +route = "/hello" +component = "tinygo-hello" + +[component.tinygo-hello] +source = "main.wasm" +allowed_outbound_hosts = [ "https://random-data-api.fermyon.app" ] +``` + +Running the application using `spin up` will start the HTTP +listener locally (by default on `localhost:3000`), and our component can +now receive requests in route `/hello`: + + + +```bash +$ curl -i localhost:3000/hello +HTTP/1.1 200 OK +content-length: 93 + +{"timestamp":1684299253331,"fact":"Reindeer grow new antlers every year"} +``` + +> Without the `allowed_outbound_hosts` field populated properly in `spin.toml`, +> the component would not be allowed to send HTTP requests, and sending the +> request would generate in a "Destination not allowed" error. + +> You can set `allowed_outbound_hosts = ["https://*:*"]` if you want to allow +> the component to make requests to any HTTP host. This is not recommended +> unless you have a specific need to contact arbitrary servers and perform your own safety checks. + +## Redis Components + +Besides the HTTP trigger, Spin has built-in support for a Redis trigger, which +will connect to a Redis instance and will execute components for new messages +on the configured channels. + +> See the [Redis trigger](./redis-trigger.md) for details about the Redis trigger. + +Writing a Redis component in Go also takes advantage of the SDK: + + + +```go +package main + +import ( + "fmt" + + "github.com/spinframework/spin-go-sdk/v2/redis" +) + +func init() { + // redis.Handle() must be called in the init() function. + redis.Handle(func(payload []byte) error { + fmt.Println("Payload::::") + fmt.Println(string(payload)) + return nil + }) +} +``` + +The manifest for a Redis application must contain the address of the Redis instance. This is set at the application level: + + + +```toml +spin_manifest_version = 2 + +[application] +name = "spin-redis" +trigger = { type = "redis", } +version = "0.1.0" + +[application.trigger.redis] +address = "redis://localhost:6379" + +[[trigger.redis]] +channel = "messages" +component = "echo-message" + +[component.echo-message] +source = "main.wasm" +[component.echo-message.build] +command = "tinygo build -target=wasip1 -gc=leaking -buildmode=c-shared -no-debug -o main.wasm ." +``` + +The application will connect to `redis://localhost:6379`, and for every new message +on the `messages` channel, the `echo-message` component will be executed: + + + +```bash +# first, start redis-server on the default port 6379 +$ redis-server --port 6379 +# then, start the Spin application +$ spin build --up +INFO spin_redis_engine: Connecting to Redis server at redis://localhost:6379 +INFO spin_redis_engine: Subscribed component 0 (echo-message) to channel: messages +``` + +For every new message on the `messages` channel: + + + +```bash +$ redis-cli +127.0.0.1:6379> publish messages "Hello, there!" +``` + +Spin will instantiate and execute the component: + + + +```bash +INFO spin_redis_engine: Received message on channel "messages" +Payload:::: +Hello, there! +``` + +## Storing Data in Redis From Go Components + +Using the Spin's Go SDK, you can use the Redis key/value store to publish +messages to Redis channels. This can be used from both HTTP and Redis triggered +components. + +Let's see how we can use the Go SDK to connect to Redis. This HTTP component demonstrates fetching a value from Redis by key, setting a +key with a value, and publishing a message to a Redis channel: + + + +```go +package main + +import ( + "net/http" + "os" + + spin_http "github.com/spinframework/spin-go-sdk/v2/http" + "github.com/spinframework/spin-go-sdk/v2/redis" +) + +func init() { + // handler for the http trigger + spin_http.Handle(func(w http.ResponseWriter, r *http.Request) { + + // addr is the environment variable set in `spin.toml` that points to the + // address of the Redis server. + addr := os.Getenv("REDIS_ADDRESS") + + // channel is the environment variable set in `spin.toml` that specifies + // the Redis channel that the component will publish to. + channel := os.Getenv("REDIS_CHANNEL") + + // payload is the data publish to the redis channel. + payload := []byte(`Hello redis from tinygo!`) + + // create a new redis client. + rdb := redis.NewClient(addr) + + if err := rdb.Publish(channel, payload); err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + // set redis `mykey` = `myvalue` + if err := rdb.Set("mykey", []byte("myvalue")); err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + // get redis payload for `mykey` + if payload, err := rdb.Get("mykey"); err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + } else { + w.Write([]byte("mykey value was: ")) + w.Write(payload) + } + }) +} +``` + +As with all networking APIs, you must grant access to Redis hosts via the `allowed_outbound_hosts` field in the application manifest: + + + +```toml +[component.storage-demo] +environment = { REDIS_ADDRESS = "redis://127.0.0.1:6379", REDIS_CHANNEL = "messages" } +allowed_outbound_hosts = ["redis://127.0.0.1:6379"] +``` + +This HTTP component can be paired with a Redis component, triggered on new messages on the `messages` Redis channel, to build an asynchronous messaging application, where the HTTP front-end queues work for a Redis-triggered back-end to execute as capacity is available. + +> You can find a complete example for using outbound Redis from an HTTP component +> in the [Spin repository on GitHub](https://github.com/spinframework/spin-go-sdk/tree/main/examples/redis-outbound). + +## Using Go Packages in Spin Components + +Any [package from the Go standard library](https://tinygo.org/docs/reference/lang-support/stdlib/) that can be imported in TinyGo and that compiles to +WASI can be used when implementing a Spin component. + +> Make sure to read [the page describing the HTTP trigger](./http-trigger.md) for more +> details about building HTTP applications. + +## Storing Data in the Spin Key-Value Store + +Spin has a key-value store built in. For information about using it from TinyGo, see [the key-value API guide](kv-store-api-guide). + +## Storing Data in SQLite + +For more information about using SQLite from TinyGo, see [SQLite storage](sqlite-api-guide). + +## AI Inferencing From Go Components + +For more information about using Serverless AI from TinyGo, see the [Serverless AI](serverless-ai-api-guide) API guide. diff --git a/content/v4/http-outbound.md b/content/v4/http-outbound.md new file mode 100644 index 00000000..f065c437 --- /dev/null +++ b/content/v4/http-outbound.md @@ -0,0 +1,315 @@ +title = "Making HTTP Requests" +template = "main" +date = "2023-11-04T00:00:01Z" +enable_shortcodes = true +[extra] +url = "https://github.com/spinframework/spin-docs/blob/main/content/v4/http-outbound.md" + +--- +- [Using HTTP From Applications](#using-http-from-applications) +- [Restrictions](#restrictions) +- [Granting HTTP Permissions to Components](#granting-http-permissions-to-components) + - [Configuration-Based Permissions](#configuration-based-permissions) +- [Making HTTP Requests Within an Application](#making-http-requests-within-an-application) + - [Local Service Chaining](#local-service-chaining) + - [Intra-Application HTTP Requests by Route](#intra-application-http-requests-by-route) + +Spin provides an interface for you to make outgoing HTTP requests. + +{{ details "Why do I need a Spin interface? Why can't I just use my language's HTTP library?" "The current version of the WebAssembly System Interface (WASI) doesn't provide a sockets interface, so HTTP libraries can't be built to Wasm. The Spin interface means Wasm modules can bypass this limitation by asking Spin to perform the HTTP request on their behalf." }} + +## Using HTTP From Applications + +The outbound HTTP interface depends on your language. + +> Under the surface, Spin uses the `wasi-http` interface. If your tools support the Wasm Component Model, you can work with that directly; but for most languages the Spin SDK is more idiomatic. + +{{ tabs "sdk-type" }} + +{{ startTab "Rust"}} + +> [**Want to go straight to the reference documentation?** Find it here.](https://docs.rs/spin-sdk/latest/spin_sdk/http/index.html) + +To send requests, use the [`spin_sdk::http::send`](https://docs.rs/spin-sdk/latest/spin_sdk/http/fn.send.html) function. This takes a request argument and returns a response (or error). + +The request you pass to `send` may be: + +* [`http::Request`](https://docs.rs/http/latest/http/request/struct.Request.html) - typically constructed via `http::Request::builder()` +* [`spin_sdk::http::Request`](https://docs.rs/spin-sdk/latest/spin_sdk/http/struct.Request.html) - typically constructed via `spin_sdk::http::Request::get()`, `spin_sdk::http::Request::post()`, or `spin_sdk::http::Request::builder()` +* Any type which implements the `spin_sdk::http::IntoRequest` trait + +Generally, you should use `OutgoingRequest` when you need to stream the outbound request body; otherwise, the `Request` types are usually simpler. + +The response is a `spin_sdk::http::Response`. + +Here is an example of doing outbound HTTP when you want to wait for the entire response and load it into memory as a single blob: + +```rust +use spin_sdk::http::{EmptyBody, FullBody, IntoResponse, Request, Response}; +use spin_sdk::http::body::IncomingBodyExt; +use spin_sdk::http_service; +use http::Method; + +#[http_service] +async fn handle_p3_body_test(req: Request) -> anyhow::Result { + // Create the outbound request object + let request = Request::builder() + .method(Method::GET) + .uri("https://spinframework.dev/") + .body(EmptyBody::new())?; + + // Send the request and await the response + let response = spin_sdk::http_wasip3::send(request).await?; + + // Get the outbound response body as a bytes::Bytes + let body = response.into_body().bytes().await?; + + // Process the response bytes + let response_len = body.len(); + + // Return a response to the inbound request + Ok((http::StatusCode::OK, response_len.to_string())) +} +``` + +Here is an example of doing outbound HTTP when you want to process the response as a stream, handling each chunk as it arrives from the server and then discarding it: + +```rust +use futures::StreamExt; +use spin_sdk::http::{EmptyBody, FullBody, IntoResponse, Request, Response}; +use spin_sdk::http::body::IncomingBodyExt; +use spin_sdk::http_service; +use http::Method; + +#[http_service] +async fn handle_request(_req: Request) -> anyhow::Result { + // Create the outbound request object + let request = Request::builder() + .method(Method::GET) + .uri("https://spinframework.dev/") + .body(EmptyBody::new())?; + + // Send the request and await the response + let response = spin_sdk::http::send(request).await?; + + // Get the outbound response body as a stream + let mut body = response.into_body().stream(); + + // Use the response stream + let mut response_len = 0; + while let Some(chunk) = body.next().await { + response_len += chunk?.len(); + } + + // Return a response to the inbound request + Ok((http::StatusCode::OK, response_len.to_string())) +} +``` + +Here is an example of doing outbound HTTP when you want to stream outbound request data to the remote host: + +```rust +use bytes::Bytes; +use futures::{SinkExt, StreamExt}; +use http_body_util::StreamBody; +use spin_sdk::http::body::IncomingBodyExt; +use spin_sdk::http::{IntoResponse, Request}; +use spin_sdk::http_service; +use http::Method; + +#[http_service] +async fn handle_p3_body_test(req: Request) -> anyhow::Result { + // Set up a channel - one end of this is the streaming body, the other + // end is where the client writes data into the streaming body. + let (mut tx, rx) = futures::channel::mpsc::channel::(1024); + + // Spawn a task to send data into the stream + spin_sdk::spawn(async move { + for index in 0..20 { + let line = format!("test line {index}\n"); + tx.send(line.into()).await.unwrap(); + } + }); + + // Create the outbound request object + let request = Request::builder() + .method(Method::POST) + .uri("https://spinframework.dev/") + .body(StreamBody::new(rx))?; + + // Adapt the Bytes-based stream to something that `http` understands + let rx = rx.map(|value| anyhow::Ok(http_body::Frame::data(value))); + + // Send the request with the streaming body, and await the response. + // The outbound body could still be streaming during the await, and + // even while the response body is being processed (although that won't + // happen in this simple case!). + let response = spin_sdk::http::send(request).await?; + + // Process the response, in this case buffering in memory, but + // this could be streaming too as in the previous example + let body = response.into_body().bytes().await?; + let text = String::from_utf8_lossy(&body[..]); + Ok((http::StatusCode::OK, text.to_string())) +} +``` + +For more examples, [see the `spin-rust-sdk` repository](https://github.com/spinframework/spin-rust-sdk/blob/main/examples). + +{{ blockEnd }} + +{{ startTab "TypeScript"}} + +HTTP operations are available via the standard JavaScript `fetch` function. The Spin runtime maps this to the underlying Wasm interface. For example: + +```javascript +const response = await fetch("https://example.com/users"); +``` + +**Notes** + +You can find a complete example of using outbound HTTP in the JavaScript SDK repository on [GitHub](https://github.com/spinframework/spin-js-sdk/tree/main/examples/common-patterns/outbound-http) + +**Note**: `fetch` currently only works when building for the HTTP trigger. + +{{ blockEnd }} + +{{ startTab "Python"}} + +> [**Want to go straight to the reference documentation?** Find it here.](https://spinframework.github.io/spin-python-sdk/v3/http/index.html) + +HTTP functions and classes are available in the `http` module. The function name is [`send`](https://spinframework.github.io/spin-python-sdk/v3/http/index.html#spin_sdk.http.send). The [request type](https://spinframework.github.io/spin-python-sdk/http/index.html#spin_sdk.http.Request) is `Request`, and the [response type](https://spinframework.github.io/spin-python-sdk/v3/http/index.html#spin_sdk.http.Response) is `Response`. For example: + +```python +from spin_sdk.http import Request, Response, send +response = send(Request("GET", "https://random-data-api.fermyon.app/animals/json", {}, None)) +``` + +**Notes** + +* For compatibility with idiomatic Python, types do not necessarily match the underlying Wasm interface. For example, `method` is a string. +* Request and response bodies are `bytes`. (You can pass literal strings using the `b` prefix.) Pass `None` for no body. +* Request and response headers are dictionaries. +* Errors are signalled through exceptions. + +You can find a complete example for using outbound HTTP in the [Python SDK repository on GitHub](https://github.com/spinframework/spin-python-sdk/tree/main/examples/outgoing-request). + +{{ blockEnd }} + +{{ startTab "TinyGo"}} + +> [**Want to go straight to the reference documentation?** Find it here.](https://pkg.go.dev/github.com/spinframework/spin-go-sdk/v2@v2.2.1/http) + +HTTP functions are available in the `github.com/spinframework/spin-go-sdk/v2/http` package. [See Go Packages for reference documentation.](https://pkg.go.dev/github.com/spinframework/spin-go-sdk/v2/http) The general function is named `Send`, but the Go SDK also surfaces individual functions, with request-specific parameters, for the `Get` and `Post` operations. For example: + +```go +import ( + spinhttp "github.com/spinframework/spin-go-sdk/v2/http" +) + +res1, err1 := spinhttp.Get("https://random-data-api.fermyon.app/animals/json") +res2, err2 := spinhttp.Post("https://example.com/users", "application/json", json) + +request, err := http.NewRequest("PUT", "https://example.com/users/1", bytes.NewBufferString(user1)) +request.Header.Add("content-type", "application/json") +res3, err3 := spinhttp.Send(req) + +``` + +**Notes** + +* In the `Post` function, the body is an `io.Reader`. The Spin SDK reads this into the underlying Wasm byte array. +* The `NewRequest` function is part of the standard library. The `Send` method adapts the standard request type to the underlying Wasm interface. +* Errors are returned through the usual Go multiple return values mechanism. + +You can find a complete example for using outbound HTTP in the [Spin repository on GitHub](https://github.com/spinframework/spin-go-sdk/tree/main/examples/http-outbound). + +{{ blockEnd }} + +{{ blockEnd }} + +## Restrictions + +Spin applications cannot set the `Host` header on outbound requests. (This is a limitation of the Wasmtime runtime which underpins Spin.) + +## Granting HTTP Permissions to Components + +By default, Spin components are not allowed to make outgoing HTTP requests. This follows the general Wasm rule that modules must be explicitly granted capabilities, which is important to sandboxing. To grant a component permission to make HTTP requests to a particular host, use the `allowed_outbound_hosts` field in the component manifest: + +```toml +[component.example] +allowed_outbound_hosts = [ "https://random-data-api.fermyon.app", "http://api.example.com:8080" ] +``` + +The Wasm module can make HTTP requests _only_ to the specified hosts. If a port is specified, the module can make requests only to that port; otherwise, the module can make requests only on the default port for the scheme. Requests to other hosts (or ports) will fail with an error. + +You can use a wildcard to allow requests to any subdomain of a domain: + +```toml +[component.example] +allowed_outbound_hosts = [ "https://*.example.com" ] +``` + +You can also pass an IPv4 CIDR address: + +```toml +[component.example] +allowed_outbound_hosts = [ "https://192.168.1.0/24" ] +``` + +For development-time convenience, you can also pass the string `"https://*:*"` in the `allowed_outbound_hosts` collection. This allows the Wasm module to make HTTP requests to _any_ host and on any port. However, once you've determined which hosts your code needs, you should remove this string and list the hosts instead. Other Spin implementations may restrict host access and disallow components that ask to connect to anything and everything! + +### Configuration-Based Permissions + +You can use [application variables](./variables.md#adding-variables-to-your-applications) in the `allowed_outbound_hosts` field. However, this feature is not yet available on Fermyon Cloud. + +## Making HTTP Requests Within an Application + +If your Spin application functions as a set of microservices, you'll often want to make requests directly from one component to another within the same application. It's best not to use a full URL for this, because that's not portable across different deployment environments - the URL in the cloud is different from the one in your development environment. Instead, Spin provides two ways to make inter-component requests: + +* By component ID ("local service chaining") +* By route + +Both of these work only from HTTP components. That is, if you want to make an intra-application request from, say, a Redis trigger, you must use a full URL. + +### Local Service Chaining + +To make an HTTP request to another component in your application, use the special `.spin.internal` host name. For example, an outbound HTTP request to `authz.spin.internal` will be handled by the `authz` component. + +In this way of doing self-requests, the request is passed in memory without ever leaving the Spin host process. This is extremely fast, as the two components are wired almost directly together, but may reduce deployment flexibility depending on the nature of the microservices. Also, public components that are the target of service chaining requests may see URLs in both routed and chained forms: therefore, if they parse the URL (for example, extracting a resource identifier from the path), they must ensure both forms are correctly handled. + +> Service chaining is the only way to call [private endpoints](./http-trigger#private-endpoints). + +You must still grant permission by including the relevant `spin.internal` hosts in `allowed_outbound_hosts`: + +```toml +allowed_outbound_hosts = ["http://authz.spin.internal", "https://reporting.spin.internal"] +``` + +You may use either the `http` or `https` scheme, and the scheme in the service chaining request is ignored: only the special host name matters. + +To allow local chaining to _any_ component in your application, you can use a subdomain wildcard: + +```toml +allowed_outbound_hosts = ["http://*.spin.internal"] +``` + +However, the wildcard implies that the component requires _all other_ components to be local to it. You will therefore not be able to use [selective deployment](./running-apps#splitting-an-application-across-environments) for an application that uses wildcard service chaining. + +> Local service chaining is not currently supported on Fermyon Cloud. + +### Intra-Application HTTP Requests by Route + +To make an HTTP request to another route with your application, you can pass just the route as the URL. For example, if you make an outbound HTTP request to `/api/customers/`, Spin prepends the route with whatever host the application is running on. It also replaces the URL scheme (`http` or `https`) with the scheme of the current HTTP request. For example, if the application is running in the cloud, Spin changes `/api` to `https://.../api`. + +> You can also use the special host `self.alt` to perform self-requests by route. This is important for the JavaScript `fetch` wrapper, which handles relative requests in a way that doesn't work with `allowed_outbound_hosts`. For example, you would write `fetch('http://self.alt/api')`. +> +In this way of doing self-requests, the request undergoes normal HTTP processing once Spin has prepended the host. For example, in a cloud deployment, the request passes through the network, and potentially back in through a load balancer or other gateway. The benefit of this is that it allows load to be distributed across the environment, but it may count against your use of bandwidth. + +You must still grant permission by including `self` or `self.alt` in `allowed_outbound_hosts`: + +```toml +allowed_outbound_hosts = ["http://self", "https://self.alt"] +``` + +> It doesn't matter which you use - either 'allow' form enables both relative and `self.alt` URLs. diff --git a/content/v4/http-trigger.md b/content/v4/http-trigger.md new file mode 100644 index 00000000..7811bb8a --- /dev/null +++ b/content/v4/http-trigger.md @@ -0,0 +1,511 @@ +title = "The Spin HTTP Trigger" +template = "main" +date = "2023-11-04T00:00:01Z" +enable_shortcodes = true +[extra] +url = "https://github.com/spinframework/spin-docs/blob/main/content/v4/http-trigger.md" + +--- +- [Specifying an HTTP Trigger](#specifying-an-http-trigger) +- [HTTP Trigger Routes](#http-trigger-routes) + - [Resolving Overlapping Routes](#resolving-overlapping-routes) + - [Private Endpoints](#private-endpoints) + - [Reserved Routes](#reserved-routes) +- [Authoring HTTP Components](#authoring-http-components) + - [The Request Handler](#the-request-handler) + - [Getting Request and Response Information](#getting-request-and-response-information) + - [Additional Request Information](#additional-request-information) + - [Inside HTTP Components](#inside-http-components) +- [Static Responses with the HTTP Trigger](#static-responses-with-the-http-trigger) +- [HTTP With Wagi (WebAssembly Gateway Interface)](#http-with-wagi-webassembly-gateway-interface) + - [Wagi Component Requirements](#wagi-component-requirements) + - [Request Handling in Wagi](#request-handling-in-wagi) + - [Wagi HTTP Environment Variables](#wagi-http-environment-variables) +- [Exposing HTTP Triggers Using HTTPS](#exposing-http-triggers-using-https) + - [Trigger Options](#trigger-options) + - [Environment Variables](#environment-variables) +- [Controlling Instance Reuse](#controlling-instance-reuse) + - [Developer Guidelines for Instance Reuse](#developer-guidelines-for-instance-reuse) + - [Preventing Instance Reuse](#preventing-instance-reuse) + +HTTP applications are an important workload in event-driven environments, +and Spin has built-in support for creating and running HTTP +components. This page covers Spin options that are specific to HTTP. + +The HTTP trigger type in Spin is a web server. When an application has HTTP triggers, Spin listens for incoming requests and, +based on the [application manifest](./writing-apps.md), it routes them to a +component, which provides an HTTP response. + +## Specifying an HTTP Trigger + +An HTTP trigger maps an HTTP route to a component. For example: + +```toml +[[trigger.http]] +route = "/..." # the route that the trigger matches +component = "my-application" # the name of the component to handle this route +``` + +Such a trigger says that HTTP requests matching the specified _route_ should be handled by the specified _component_. The `component` field works the same way across all triggers - see [Triggers](triggers) for the details. + +## HTTP Trigger Routes + +An HTTP route may be _exact_ or _wildcard_. + +An _exact_ route matches only the given route. This is the default behavior. For example, `/cart` matches only `/cart`, and not `/cart/checkout`: + + + +```toml +# Run the `shopping-cart` component when the application receives a request to `/cart`... +[[trigger.http]] +route = "/cart" +component = "shopping-cart" + +# ...and the `checkout` component for `/cart/checkout` +[[trigger.http]] +route = "/cart/checkout" +component = "checkout" +``` + +You can use wildcards to match 'patterns' of routes. Spin supports two kinds of wildcards: single-segment wildcards and trailing wildcards. + +A single-segment wildcard uses the syntax `:name`, where `name` is a name that identifies the wildcard. Such a wildcard will match only a single segment of a path, and allows further matching on segments beyond it. For example, `/users/:userid/edit` matches `/users/1/edit` and `/users/alice/edit`, but does not match `/users`, `/users/1`, or `/users/1/edit/cart`. + +A trailing wildcard uses the syntax `/...` and matches the given route and any route under it. For example, `/users/...` matches `/users`, `/users/1`, `/users/1/edit`, and so on. Any of these routes will run the mapped component. + +> In particular, the route `/...` matches all routes. + +> Browser clients often `GET /favicon.ico` after a page request. If you use the `/...` route, consider handling this case in your code! + + + +```toml +[[trigger.http]] +# Run the `user-manager` component when the application receives a request to `/users` +# or any path beginning with `/users/` +route = "/users/..." +component = "user-manager" +``` + +### Resolving Overlapping Routes + +If multiple triggers could potentially handle the same request based on their +defined routes, the trigger whose route has the longest matching prefix +takes precedence. This also means that exact matches take precedence over wildcard matches. + +In the following example, requests starting with the `/users/` prefix (e.g. `/users/1`) +will be handled by `user-manager`, even though it is also matched by the `shop` route, because the `/users` prefix is longer than `/`. +But requests to `/users/admin` will be handled by the `admin` component, not `user-manager`, because that is a more exact match still: + + + +```toml +# spin.toml + +[[trigger.http]] +route = "/users/..." +component = "user-manager" + +[[trigger.http]] +route = "/users/admin" +component = "admin" + +[[trigger.http]] +route = "/..." +component = "shop" +``` + +### Private Endpoints + +Private endpoints are where an internal microservice is not exposed to the network (does not have an HTTP route) and so is accessible only from within the application. + + + +```toml +[[trigger.http]] +route = { private = true } +component = "internal" +``` + +To access a private endpoint, use [local service chaining](./http-outbound#local-service-chaining) (where the request is passed in memory without ever leaving the Spin host process). Such calls still require the internal endpoint to be included in `allowed_outbound_hosts`. + +### Reserved Routes + +Every HTTP application automatically has a special route always configured at `/.well-known/spin/...`. This route takes priority over any routes in the application: that is, the Spin runtime handles requests to this route, and the application never sees such requests. + +You can use paths within this route for health and status checking. The following are currently defined: + +* `/.well-known/spin/health`: returns 200 OK if Spin is healthy and accepting requests +* `/.well-known/spin/info`: returns information about the application and deployment + +Other paths within the reserved space currently return 404 Not Found. + +## Authoring HTTP Components + +> Spin has two ways of running HTTP components, depending on language support for the evolving WebAssembly component standards. This section describes the default way, which is currently used by Rust, JavaScript/TypeScript, Python, and TinyGo components. For other languages, see [HTTP Components with Wagi](#http-with-wagi-webassembly-gateway-interface) below. + +By default, Spin runs components using the [WebAssembly component model](https://component-model.bytecodealliance.org/). In this model, the Wasm module exports a well-known interface that Spin calls to handle the HTTP request. + +### The Request Handler + +The exact signature of the HTTP handler, and how a function is identified to be exported as the handler, will depend on your language. + +{{ tabs "sdk-type" }} + +{{ startTab "Rust"}} + +> [**Want to go straight to the reference documentation?** Find it here.](https://docs.rs/spin-sdk/latest/spin_sdk/http/index.html) + +In Rust, the handler is identified by the [`#[spin_sdk::http_service]`](https://docs.rs/spin-sdk/latest/spin_sdk/attr.http_service.html) attribute. The handler function is an async function which receives the request as an argument, and returns the response as the return value of the function. For example: + +```rust +#[http_service] +async fn handle(request: http::Request) -> anyhow::Result { ... } +``` + +You have some flexibility in choosing the types of the request and response. The request may be: + +* [`http::Request`](https://docs.rs/http/latest/http/request/struct.Request.html) +* [`spin_sdk::http::Request`](https://docs.rs/spin-sdk/latest/spin_sdk/http/struct.Request.html) +* Any type which implements the [`spin_sdk::http::FromRequest`](https://docs.rs/spin-sdk/latest/spin_sdk/http/trait.FromRequest.html) trait + +The response may be: + +* [`http::Response`](https://docs.rs/http/latest/http/response/struct.Response.html) - typically constructed via `Response::builder()` +* [`spin_sdk::http::Response`](https://docs.rs/spin-sdk/latest/spin_sdk/http/struct.Response.html) - typically constructed via a [`ResponseBuilder`](https://docs.rs/spin-sdk/latest/spin_sdk/http/struct.ResponseBuilder.html) +* Any type which implements the [`spin_sdk::http::IntoResponse`](https://docs.rs/spin-sdk/latest/spin_sdk/http/trait.IntoResponse.html) trait +* A `Result` where the success type is one of the above and the error type is `anyhow::Error` or another error type for which you have implemented `spin_sdk::http::IntoResponse` (such as `anyhow::Result`) + +> The HTTP template generates a return type of `anyhow::Result`, so you don't need to tweak it if you change the concrete type of the response. + +For example: + +```rust +use http::{Request, Response}; +use spin_sdk::http::IntoResponse; +use spin_sdk::http_service; + +#[http_service] +async fn handle_hello_rust(_req: Request<()>) -> anyhow::Result { + Ok(Response::builder() + .status(200) + .header("content-type", "text/plain") + .body("Hello, Spin".to_string())?) +} +``` + +For a full Rust SDK reference, see the [Rust Spin SDK documentation](https://docs.rs/spin-sdk/latest/spin_sdk/index.html). + +{{ blockEnd }} + +{{ startTab "TypeScript"}} + +> [**Want to go straight to the reference documentation?** Find it here.](https://spinframework.github.io/spin-js-sdk/) + +Building a Spin HTTP component with the JavaScript/TypeScript SDK now involves adding an event listener for the `fetch` event. This event listener handles incoming HTTP requests and allows you to construct and return HTTP responses. + +Below is a complete implementation for such a component in TypeScript: + +```ts +import { AutoRouter } from 'itty-router'; + +let router = AutoRouter(); + +router + .get("/", () => new Response("hello universe")) + .get('/hello/:name', ({ name }) => `Hello, ${name}!`) + +//@ts-ignore +addEventListener('fetch', async (event: FetchEvent) => { + event.respondWith(router.fetch(event.request)); +}); +``` + +{{ blockEnd }} + +{{ startTab "Python"}} + +> [**Want to go straight to the reference documentation?** Find it here.](https://spinframework.github.io/spin-python-sdk/v3/) + +In Python, the application must define a top-level class named IncomingHandler which inherits from [IncomingHandler](https://spinframework.github.io/spin-python-sdk/v3/http/index.html#spin_sdk.http.IncomingHandler), overriding the `handle_request` method. + +```python +from spin_sdk import http +from spin_sdk.http import Request, Response + +class IncomingHandler(http.IncomingHandler): + def handle_request(self, request: Request) -> Response: + return Response( + 200, + {"content-type": "text/plain"}, + bytes("Hello from Python!", "utf-8") + ) +``` + +{{ blockEnd }} + +{{ startTab "TinyGo"}} + +> [**Want to go straight to the reference documentation?** Find it here.](https://pkg.go.dev/github.com/spinframework/spin-go-sdk/v2@v2.2.1/http) + +In Go, you register the handler as a callback in your program's `init` function. Call `spinhttp.Handle`, passing your handler as the sole argument. Your handler takes a `http.Request` record, from the standard `net/http` package, and a `ResponseWriter` to construct the response. + +```go +package main + +import ( + "fmt" + "net/http" + + spinhttp "github.com/spinframework/spin-go-sdk/v2/http" +) + +func init() { + spinhttp.Handle(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "text/plain") + fmt.Fprintln(w, "Hello Fermyon!") + }) +} + +func main() {} +``` + +> If you are moving between languages, note that in most other Spin SDKs, your handler _constructs and returns_ a response, but in Go, _Spin_ constructs a `ResponseWriter`, and you write to it; your handler does not return a value. + +{{ blockEnd }} + +{{ blockEnd }} + +### Getting Request and Response Information + +Exactly how the Spin SDK surfaces the request and response types varies from language to language; this section calls out general features. + +* In the request record, the URL contains the path and query, but not the scheme and host. For example, in a request to `https://example.com/shop/users/1/cart/items/3/edit?theme=pink`, the URL contains `/shop/users/1/cart/items/3/edit?theme=pink`. If you need the full URL, you can get it from the `spin-full-url` header - see the table below. + +### Additional Request Information + +As well as any headers passed by the client, Spin sets several headers on the request passed to your component, which you can use to access additional information about the HTTP request. + +> In the following table, the examples suppose that: +> * Spin is listening on `example.com:8080` +> * The trigger `route` is `/users/:userid/cart/...` +> * The request is to `https://example.com:8080/users/1/cart/items/3/edit?theme=pink` + +| Header Name | Value | Example | +|------------------------------|----------------------|---------| +| `spin-full-url` | The full URL of the request. This includes full host and scheme information. | `https://example.com:8080/users/1/cart/items/3/edit?theme=pink` | +| `spin-path-info` | The request path relative to the component route | `/items/3/edit` | +| `spin-path-match-n` | Where `n` is the pattern for our single-segment wildcard value (e.g. `spin-path-match-userid` will access the value in the URL that represents `:userid`) | `1` | +| `spin-matched-route` | The part of the trigger route that was matched by the route (including the wildcard indicator if present) | `/users/:userid/cart/...` | +| `spin-raw-component-route` | The component route pattern matched, including the wildcard indicator if present | `/users/:userid/cart/...` | +| `spin-component-route` | The component route pattern matched, _excluding_ any wildcard indicator | `/users/:userid/cart` | +| `spin-client-addr` | The IP address and port of the client. Some Spin runtimes do not set this header. | `127.0.0.1:53152` | + +### Inside HTTP Components + +For the most part, you'll build HTTP component modules using a language SDK (see the Language Guides section), such as a JavaScript module or a Rust crate. If you're interested in what happens inside the SDK, or want to implement HTTP components in another language, read on! + +The HTTP component interface is defined using a WebAssembly Interface (WIT) file. ([Learn more about the WIT language here.](https://component-model.bytecodealliance.org/design/wit.html)). You can find the latest WITs for Spin HTTP components at [https://github.com/spinframework/spin/tree/main/wit](https://github.com/spinframework/spin/tree/main/wit). + +The HTTP types and interfaces are defined in [https://github.com/spinframework/spin/tree/main/wit/deps/http@0.3.0-rc-2026-03-15](https://github.com/spinframework/spin/tree/main/wit/deps/http@0.3.0-rc-2026-03-15), which tracks [the `wasi-http` specification](https://github.com/WebAssembly/wasi-http). + +In particular, the entry point for Spin HTTP components is defined in [the `handler` interface](https://github.com/spinframework/spin/blob/main/wit/deps/http@0.3.0-rc-2026-03-15/worlds.wit): + + + +```fsharp +// handler.wit + +interface handler { + use types.{request, response, error-code}; + + /// This function may be called with either an incoming request read from the + /// network or a request synthesized or forwarded by another component. + handle: async func( + request: request, + ) -> result; +} +``` + +This is the interface that all HTTP components must implement, and which is used by the Spin HTTP executor when instantiating and invoking the component. + +However, this is not necessarily the interface you, the component author, work with. In many cases, you will use a more idiomatic wrapper provided by the Spin SDK, which implements the "true" interface internally. + +But if you wish, and if your language supports it, you can implement the `incoming-handler` interface directly, using tools such as the +[Bytecode Alliance `wit-bindgen` project](https://github.com/bytecodealliance/wit-bindgen). Spin will happily load and run such a component. This is exactly how Spin SDKs, such as the [Rust](rust-components) SDK, are built; as component authoring tools roll out for Go, JavaScript, Python, and other languages, you'll be able to use those tools to build `wasi-http` handlers and therefore Spin HTTP components. + +## Static Responses with the HTTP Trigger + +You can write short, static responses within the HTTP trigger by setting `static_response` (instead of `component`): + +```toml +# Example use case: fallback 404 handling +[[trigger.http]] +route = "/..." +static_response = { status_code = 404, body = "not found" } + +# Example use case: redirect +[[trigger.http]] +route = "/bob" +static_response = { status_code = 302, headers = { location = "/users/bob" } } +``` + +Static responses may have only text or empty bodies. + +## HTTP With Wagi (WebAssembly Gateway Interface) + +A number of languages support WASI Preview 1 but not the component model. To enable developers to use these languages, Spin supports an +HTTP executor based on [Wagi](https://github.com/deislabs/wagi), or the +WebAssembly Gateway Interface, a project that implements the +[Common Gateway Interface](https://datatracker.ietf.org/doc/html/rfc3875) +specification for WebAssembly. + +Wagi allows a module built in any programming language that compiles to [WASI](https://wasi.dev/) +to handle an HTTP request by passing the HTTP request information to the module's +standard input, environment variables, and arguments, and expecting the HTTP +responses through the module's standard output. +This means that if a language has support for the WebAssembly System Interface, +it can be used to build Spin HTTP components. +The Wagi model is only used to parse the HTTP request and response. Everything +else β€” defining the application, running it, or [distributing](./distributing-apps.md) +is done the same way as a component that uses the Spin executor. + +### Wagi Component Requirements + +Spin uses the component model by default, and cannot detect from the Wasm module alone whether it was built with component model support. For Wagi components, therefore, you must tell Spin in the component manifest to run them using Wagi instead of 'default' Spin. To do this, use the `executor` field in the `trigger` table: + +```toml +[[trigger.http]] +route = "/" +component = "wagi-test" +executor = { type = "wagi" } +``` + +> If, for whatever reason, you want to highlight that a component uses the default Spin execution model, you can write `executor = { type = "spin" }`. But this is the default and is rarely written out. + +Wagi supports non-default entry points, and allows you to pass an arguments string that a program can receive as if it had been passed on the command line. If you need these you can specify them in the `executor` table. For details, see the [Manifest Reference](manifest-reference#the-componenttrigger-table-for-http-applications). + +### Request Handling in Wagi + +Building a Wagi component in a particular programming language that can compile +to `wasm32-wasip2` does not require any special libraries β€” instead, +[building Wagi components](https://github.com/deislabs/wagi/tree/main/docs) can +be done by reading the HTTP request from the standard input and environment +variables, and sending the HTTP response to the module's standard output. + +In pseudo-code, this is the minimum required in a Wagi component: + +- either the `content-media` or `location` headers must be set β€” this is done by +printing its value to standard output +- an empty line between the headers and the body +- the response body printed to standard output: + + + +```text +print("content-type: text/html; charset=UTF-8\n\n"); +print("hello world\n"); +``` + +Here is a working example, written in [Grain](https://grain-lang.org/), +a programming language that natively targets WebAssembly and WASI but +does not yet support the component model: + +```js +import Process from "sys/process"; +import Array from "array"; + +print("content-type: text/plain\n"); + +// This will print all the Wagi env variables +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=). + +### Wagi HTTP Environment Variables + +Wagi passes request metadata to the program through well-known environment variables. The key path-related request variables are: + +- `X_FULL_URL` - the full URL of the request β€” + `http://localhost:3000/hello/abc/def?foo=bar` +- `PATH_INFO` - the path info, relative to the + component route β€” in our example, where the the + component route is `/hello`, this is `/abc/def`. +- `X_MATCHED_ROUTE` - the route pattern matched (including the + wildcard pattern, if applicable) β€” in our case `"/hello/..."`. +- `X_RAW_COMPONENT_ROUTE` - the route pattern matched (including the wildcard + pattern, if applicable) β€” in our case `/hello/...`. +- `X_COMPONENT_ROUTE` - the route path matched (stripped of the wildcard + pattern) β€” in our case `/hello` + +For details, and for a full list of all Wagi environment variables, see +[the Wagi documentation](https://github.com/deislabs/wagi/blob/main/docs/environment_variables.md). + +## Exposing HTTP Triggers Using HTTPS + +When exposing HTTP triggers using HTTPS you must provide `spin up` with a TLS certificate and a private key. This can be achieved by either using trigger options (`--tls-cert` and `--tls-key`) when running the `spin up` command, or by setting environment variables (`SPIN_TLS_CERT` and `SPIN_TLS_KEY`) before running the `spin up` command. + +### Trigger Options + +The `spin up` command accepts some HTTP-trigger-specific options: + +The `--listen` option sets the local IP and port that `spin up` should listen to for requests. By default, it listens to `localhost:3000`. + +The `--tls-cert` and `--tls-key` options provide a way for you to configure a TLS certificate. If they are not set, plaintext HTTP will be used. The `--tls-cert` option specifies the path to the TLS certificate to use for HTTPS. The certificate should be in PEM format. The `--tls-key` option specifies the path to the private key to use for HTTPS. The key should be in PKCS#8 format. + +### Environment Variables + +The `spin up` command can also automatically use the `SPIN_TLS_CERT` and `SPIN_TLS_KEY` environment variables instead of the respective flags (`--tls-cert` and `--tls-key`): + + + +```bash +SPIN_TLS_CERT= +SPIN_TLS_KEY= +``` + +Once set, `spin up` will automatically use these explicitly set environment variables. For example, if using a Linux-based system, you can go ahead and use the `export` command to set the variables in your session (before you run the `spin up` command): + + + +```bash +export SPIN_TLS_CERT= +export SPIN_TLS_KEY= +``` + +## Controlling Instance Reuse + +Instance reuse is when Spin handles more than one HTTP request within a single component instance. Instance reuse improves performance and density, because Spin does not need to create a new instance for every request. Spin can reuse instances both consecutively and concurrently - that is, it can assign a new request to either a freshly created instance, an instance that has finished handling a request, or an instance that is _in the middle of_ handling another request. + +The exact details depend on your component. + +A WASI P2 component is _not_ subject to instance reuse. This includes all triggers other than HTTP. + +A WASI P3 component is _always_ subject to instance reuse, unless it calls specific APIs to prevent concurrent use. You can control instance reuse behaviour using the following `spin up` flags: + +* `--max-instance-reuse-count` sets the maximum number of times a single instance can be reused +* `--max-instance-concurrent-reuse-count` sets the maximum number of requests that can be running in a single instance at the same time +* `--idle-instance-timeout` controls how long Spin will allow a reusable instance to be sit idle before evicting it + +All of these flags accept ranges. When you provide a range, Spin assigns each new instance a random value from that range. This is to help you test that your component works correctly both when fresh (e.g. you do not rely on long-running state) and when reused (e.g. you are not unintentionally leaking data from one request to another). + +### Developer Guidelines for Instance Reuse + +When writing WASI P3 HTTP components, you can take advantage of reuse in your code, by placing static data in static or global variables, which will become part of the instance state. For example, if your component contains a routing table, you could cache the parsed table in a static variable - you would then parse the table only if the cache had not been initialised (i.e. in a fresh instance), avoiding the overhead of parsing on every request. + +> Don't rely on techniques like this for expensive operations. Spin doesn't guarantee the degree of instance reuse, and reuse may vary across differnt Spin hosts. + +Conversely, take care that request data is not stored in static or global variables. If you're used to the WASI P2 model, you may have an implicit expectation that each request finds your component in an entirely fresh state. In WASI P3, that's no longer the case. You should store data that's private to a request in local variables; if you must store it in a static variable, make sure to isolate it (for example storing it in a map under a request-specific key). + +### Preventing Instance Reuse + +Although you can control instance reuse on the `spin up` command line, this isn't necessarily available in other hosts. If the structure of your code means that it's not safe to reuse instances, then you can use Wasm Component Model backpressure functions in your code to tell the host not to schedule further requests to the current instance. How these are surfaced will depend on your language - for example, in Rust you would use `spin_sdk::wit_bindgen::backpressure_inc` to suspend re-use and a balancing `spin_sdk::wit_bindgen::backpressure_dec` to re-enable it. See the [Component Model documentation](https://github.com/WebAssembly/component-model/blob/main/design/mvp/Concurrency.md#backpressure) for detailed information. diff --git a/content/v4/index.md b/content/v4/index.md new file mode 100644 index 00000000..2285895e --- /dev/null +++ b/content/v4/index.md @@ -0,0 +1,24 @@ +title = "Introducing Spin" +template = "main" +date = "2023-11-04T00:00:01Z" +enable_shortcodes = true +[extra] +url = "https://github.com/spinframework/spin-docs/blob/main/content/v4/index.md" + +--- + +Spin is a framework for building and running event-driven microservice applications with WebAssembly (Wasm) components. + +Spin uses Wasm because it is **sandboxed, portable, and fast**. Millisecond cold start times mean no need to keep applications "warm". + +Many languages have Wasm implementations, so **developers don't have to learn new languages or libraries**. + +Spin is **open source**, under the aegis of the [CNCF](https://www.cncf.io/), and **built on standards**, meaning you can take your Spin applications anywhere. There are Spin implementations for local development, for self-hosted servers, for Kubernetes, and for cloud-hosted services. + +**Want to see the kinds of things people are building with Spin?** Check out what's [Built With Spin](./see-what-people-have-built-with-spin)! + +Or dive into the documentation and get started: + +- [Take Spin for a spin](quickstart) +- [Install Spin](install) +- [Learn how to write Spin applications](writing-apps) \ No newline at end of file diff --git a/content/v4/install.md b/content/v4/install.md new file mode 100644 index 00000000..f6cc7652 --- /dev/null +++ b/content/v4/install.md @@ -0,0 +1,315 @@ +title = "Install Spin" +template = "main" +date = "2023-11-04T00:00:01Z" +enable_shortcodes = true +[extra] +url = "https://github.com/spinframework/spin-docs/blob/main/content/v4/install.md" +keywords = "install" + +--- +- [Installing Spin](#installing-spin) +- [Verifying the Release Signature](#verifying-the-release-signature) +- [Building Spin From Source](#building-spin-from-source) +- [Using Cargo to Install Spin](#using-cargo-to-install-spin) +- [Installing Templates and Plugins](#installing-templates-and-plugins) + - [Templates](#templates) + - [Plugins](#plugins) +- [Next Steps](#next-steps) + +## Installing Spin + +Spin runs on Linux (amd64 and arm64), macOS (Intel and Apple Silicon), and Windows (amd64): + +{{ tabs "os" }} + +{{ startTab "Linux"}} + +**Homebrew** + +You can manage your Spin installation via [Homebrew](https://brew.sh/). Homebrew automatically installs Spin templates and Spin plugins, and on uninstall, will prompt you to delete the directory where the templates and plugins were downloaded: + +Install the Fermyon tap, which Homebrew tracks, updates, and installs Spin from: + + + +```bash +$ brew tap spinframework/tap +``` + +Install Spin: + + + +```bash +$ brew install spinframework/tap/spin +``` + +> Note: `brew install spin` will **not** install Spin framework. Spin is accessed from the `spinframework` tap, as shown above. + +**Installer script** + +Another option (other than brew) is to use our installer script. The installer script installs Spin along with a starter set of language templates and plugins: + + + +```bash +$ curl -fsSL https://spinframework.dev/downloads/install.sh | bash +``` + +Once you have run the installer script, it is highly recommended to add Spin to a folder, which is on your path, e.g.: + + + +```bash +$ sudo mv spin /usr/local/bin/ +``` + +> If you have already installed Spin by building from source, and then install it via the installer, we recommend you remove the older source install by running `cargo uninstall spin-cli` Otherwise the Cargo path may take precedence over the "install from binary" path, and commands may get the "wrong" version of Spin. Use `spin --version` to confirm the version on the PATH is the one you intend, or `which spin` to confirm the path it is found on. + +To install a specific version (`v1.2.3` is just an example), you can pass arguments to the install script this way: + + + +```bash +$ curl -fsSL https://spinframework.dev/downloads/install.sh | bash -s -- -v v1.2.3 +``` + +To install the canary version of spin, you should pass the argument `-v canary`. The canary version is always the latest commit to the main branch of Spin: + + + +```bash +$ curl -fsSL https://spinframework.dev/downloads/install.sh | bash -s -- -v canary +``` + +{{ blockEnd }} + +{{ startTab "macOS"}} + +**Homebrew** + +You can manage your Spin installation via [Homebrew](https://brew.sh/). Homebrew automatically installs Spin templates and Spin plugins, and on uninstall, will prompt you to delete the directory where the templates and plugins were downloaded: + +Install the Fermyon tap, which Homebrew tracks, updates, and installs Spin from: + + + +```bash +$ brew tap spinframework/tap +``` + +Install Spin: + + + +```bash +$ brew install spinframework/tap/spin +``` + +> Note: `brew install spin` will **not** install Spin framework. Spin is accessed from the `spinframework` tap, as shown above. + +**Installer script** + +Another option (other than brew) is to use our installer script. The installer script installs Spin along with a starter set of language templates and plugins: + +The installer script also installs Spin along with a starter set of language templates and plugins: + + + +```bash +$ curl -fsSL https://spinframework.dev/downloads/install.sh | bash +``` + +Once you have run the installer script, it is highly recommended to add Spin to a folder, which is on your path, e.g.: + + + +```bash +$ sudo mv spin /usr/local/bin/ +``` + +> If you have already installed Spin by building from source, and then install it via the installer, we recommend you remove the older source install by running `cargo uninstall spin-cli` Otherwise the Cargo path may take precedence over the "install from binary" path, and commands may get the "wrong" version of Spin. Use `spin --version` to confirm the version on the PATH is the one you intend, or `which spin` to confirm the path it is found on. + +To install a specific version (`v1.2.3` is just an example), you can pass arguments to the install script this way: + + + +```bash +$ curl -fsSL https://spinframework.dev/downloads/install.sh | bash -s -- -v v1.2.3 +``` + +To install the canary version of spin, you should pass the argument `-v canary`. The canary version is always the latest commit to the main branch of Spin: + + + +```bash +$ curl -fsSL https://spinframework.dev/downloads/install.sh | bash -s -- -v canary +``` + +{{ blockEnd }} + +{{ startTab "Windows"}} + +If using Windows (PowerShell / cmd.exe), you can download the Windows binary release of Spin. + +Simply unzip the binary release and place the `spin.exe` in your system path. + +This does not install any Spin templates or plugins. For a starter list, see the [Installing Templates and Plugins section](#installing-templates-and-plugins). + +If you want to use WSL2 (Windows Subsystem for Linux 2), please follow the instructions for using Linux. + +{{ blockEnd }} +{{ blockEnd }} + +## Verifying the Release Signature + +The Spin project [signs releases](https://github.com/spinframework/spin/blob/main/docs/content/sips/012-signing-spin-releases.md) using [Sigstore](https://docs.sigstore.dev/), a project that helps with signing software and _stores signatures in a tamper-resistant public log_. Consumers of Spin releases can validate the integrity of the package they downloaded by performing a validation of the artifact against the signature present in the public log. Specifically, users get two main guarantees by verifying the signature: 1) that the author of the artifact is indeed the one expected (i.e. the build infrastructure associated with the Spin project, at a given revision that can be inspected), and 2) that the content generated by the build infrastructure has not been tampered with. + +To verify the release signature, first [configure Cosign v2.0.0+](https://docs.sigstore.dev/cosign/system_config/installation/). This is the CLI tool that we will use validate the signature. +The same directory where the installation script was run should also contain a signature of the Spin binary and the certificate used to perform the signature. The following command will perform the signature verification using the `cosign` CLI: + + + +```bash +$ cosign verify-blob \ + --signature spin.sig \ + --certificate crt.pem \ + --certificate-identity https://github.com/spinframework/spin/.github/workflows/release.yml@refs/tags/ \ + --certificate-oidc-issuer https://token.actions.githubusercontent.com \ + # --certificate-github-workflow-sha \ + ./spin +Verified OK +``` + +You can now move the Spin binary to the path knowing that it was indeed built by the infrastructure associated with the Spin project, and that it has not been tampered with since the build. + +## Building Spin From Source + +[Follow the contribution document](./contributing-spin.md) for a detailed guide on building Spin from source: + + + +```bash +$ git clone https://github.com/spinframework/spin +$ cd spin && make build +$ ./target/release/spin --help +``` + +> Please note: On a fresh Linux installation, you will also need the standard build toolchain (`gcc`, `make`, etc.), the SSL library headers, and on some distributions you may need `pkg-config`. For example, on Debian-like distributions, including Ubuntu, you can install the standard build toolchain with this command: + + + +```bash +$ sudo apt-get install build-essential libssl-dev pkg-config +``` + +This does not install any Spin templates or plugins. For a starter list, see the [Installing Templates and Plugins section](#installing-templates-and-plugins). + +## Using Cargo to Install Spin + +If you have [`cargo`](https://doc.rust-lang.org/cargo/getting-started/installation.html), you can clone the repo and install it to your path. + +> Note: The `main` branch may have unstable or breaking changes. It is advised to check out the latest Spin release tag, which can be found by navigating to https://github.com/spinframework/spin/releases/latest. + + + +```bash +$ git clone https://github.com/spinframework/spin +$ cd spin +$ # Check out the latest tagged release +$ # git checkout +$ rustup target add wasm32-wasip2 +$ rustup target add wasm32-unknown-unknown +$ cargo install --locked --path . +$ spin --help +``` + +> Please note: Installing Spin from source requires Rust 1.79 or newer. You can update Rust using the following command: + + + +```bash +$ rustup update +``` + +Installing Spin from source does not install any Spin templates or plugins. For a starter list, see the [Installing Templates and Plugins section](#installing-templates-and-plugins). + +## Installing Templates and Plugins + +Spin has a variety of templates and plugins to make it easier to create Spin applications in your favorite programming language. [The install script](install#installing-spin) automatically installs a starter set of templates and plugins, namely templates from the Spin repository and JavaScript and Python toolchain plugins and the Fermyon Cloud plugin. + +If you used a different installation method, we recommend you install these templates and plugins manually, as follows. + +### Templates + +Rust, Go and miscellaneous other languages: + + + +```bash +$ spin templates install --git https://github.com/spinframework/spin --upgrade +``` + +Python: + + + +```bash +$ spin templates install --git https://github.com/spinframework/spin-python-sdk --upgrade +``` + +JavaScript and TypeScript: + + + +```bash +$ spin templates install --git https://github.com/spinframework/spin-js-sdk --upgrade +``` + +To list installed templates, run: + + + +```bash +$ spin templates list +``` + +For more information please read the [managing templates](./managing-templates) section of the documentation. + +### Plugins + +First update the local cache by running the `spin plugins update` command: + + + +```bash +$ spin plugins update +``` + +Then install plugins by name. + +Fermyon Cloud: + + + +```bash +$ spin plugins install cloud --yes +``` + +To list available plugins, run: + + + +```bash +$ spin plugins search +``` + +For more information, please visit the [managing plugins](./managing-plugins) section of the documentation. + +## Next Steps + +{{suh_cards}} +{{card_element "sample" "Checklist Sample App" "A checklist app that persists data in a key value store" "/hub/preview/sample_checklist" "Typescript,Http,Kv" true }} +{{card_element "template" "Zola SSG Template" "A template for using Zola framework to create a static webpage" "/hub/preview/template_zola_ssg" "rust" true }} +{{card_element "sample" "AI-assisted News Summarizer" "Read an RSS newsfeed and have AI summarize it for you" "/hub/preview/sample_newsreader_ai" "Typescript,Javascript,Ai" true }} +{{blockEnd}} diff --git a/content/v4/javascript-components.md b/content/v4/javascript-components.md new file mode 100644 index 00000000..2c645b8c --- /dev/null +++ b/content/v4/javascript-components.md @@ -0,0 +1,455 @@ +title = "Building Spin Components in JavaScript" +template = "main" +date = "2023-11-04T00:00:01Z" +enable_shortcodes = true +[extra] +url = "https://github.com/spinframework/spin-docs/blob/main/content/v4/javascript-components.md" + +--- +- [Installing Templates](#installing-templates) +- [Structure of a JS/TS Component](#structure-of-a-jsts-component) +- [Building and Running the Template](#building-and-running-the-template) +- [HTTP Components](#http-components) +- [Sending Outbound HTTP Requests](#sending-outbound-http-requests) + - [Intra-Application Requests in JavaScript](#intra-application-requests-in-javascript) +- [Storing Data in Redis From JS/TS Components](#storing-data-in-redis-from-jsts-components) +- [Storing Data in the Spin Key-Value Store](#storing-data-in-the-spin-key-value-store) +- [Storing Data in SQLite](#storing-data-in-sqlite) +- [Storing Data in MySQL and PostgreSQL Relational Databases](#storing-data-in-mysql-and-postgresql-relational-databases) +- [AI Inferencing From JS/TS Components](#ai-inferencing-from-jsts-components) +- [Node.js Compatibility](#nodejs-compatibility) +- [Using External NPM Libraries](#using-external-npm-libraries) + - [Suggested Libraries for Common Tasks](#suggested-libraries-for-common-tasks) +- [Caveats](#caveats) + +With JavaScript being a very popular language, Spin provides an SDK to support building components. The development of the JavaScript SDK is continually being worked on to improve user experience and add features. The SDK is based on [`ComponentizeJS`](https://github.com/bytecodealliance/ComponentizeJS). + +> This guide assumes you have Spin installed. If this is your first encounter with Spin, please see the [Quick Start](quickstart), which includes information about installing Spin with the JavaScript templates and creating JavaScript and TypeScript applications. + +> This guide assumes you are familiar with the JavaScript programming language, +> but if you are just getting started, be sure to check [the MDN guide](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide). + +> All examples from this page can be found in [the JavaScript SDK repository on GitHub](https://github.com/spinframework/spin-js-sdk/tree/main/examples). + +[**Want to go straight to the Spin SDK reference documentation?** Find it here.](https://spinframework.github.io/spin-js-sdk/) + +## Installing Templates + +The JavaScript/TypeScript templates can be installed from [spin-js-sdk repository](https://github.com/spinframework/spin-js-sdk/tree/main/) using the following command: + + + +```bash +$ spin templates install --git https://github.com/spinframework/spin-js-sdk --update +``` + +which will install the `http-js` and `http-ts` templates: + + + +```text +Copying remote template source +Installing template http-ts... +Installing template http-js... +Installed 2 template(s) + ++-------------------------------------------------+ +| Name Description | ++=================================================+ +| http-js HTTP request handler using Javascript | +| http-ts HTTP request handler using Typescript | ++-------------------------------------------------+ +``` + +## Structure of a JS/TS Component + +A new JS/TS component can be created using the following command: + + + +```bash +$ spin new -t http-ts hello-world --accept-defaults +``` + +This creates a directory of the following structure: + + + +```text +hello-world +β”œβ”€β”€ package.json +β”œβ”€β”€ README.md +β”œβ”€β”€ spin.toml +β”œβ”€β”€ src +β”‚Β Β  └── index.ts +β”œβ”€β”€ tsconfig.json +└── webpack.config.js +``` + +The source for the component is present in `src/index.ts`. [Webpack](https://webpack.js.org) is used to bundle the component into a single `.js` file which will then be compiled to a `.wasm` module. + +{{ details "Going from JavaScript to Wasm" "The JS source is compiled to a `wasm` module using the `j2w` node executable provided by the `@fermyon/spin-sdk` which is a wrapper around `ComponentizeJS` that is used to manage the dependencies."}} + +## Building and Running the Template + +First, the dependencies for the template need to be installed using the following commands: + + + +```bash +$ cd hello-world +$ npm install +``` + +Next step is to use the `spin build` command to run the build script for the component. Once a Spin compatible module is created, it can be run using `spin up`: + + + +```bash +$ spin build +$ spin up +``` + +`spin build` will execute the command in the `command` key under the `[component..build]` section from `spin.toml` for each component in your application. In this case an `npm` script will be run. The command in the `package.json` will looks something like: + + + +```json +"scripts": { + "build": "npx webpack --mode=production && npx mkdirp target && npx j2w -i dist.js -d combined-wit -n combined -o target/hello-world.wasm", + "test": "echo \"Error: no test specified\" && exit 1" + } +``` + +--- + +## HTTP Components + +In Spin, HTTP components are triggered by the occurrence of an HTTP request, and +must return an HTTP response at the end of their execution. Components can be +built in any language that compiles to WASI, and Javascript/TypeScript has improved support +for writing Spin components with the Spin JS/TS SDK. + +> Make sure to read [the page describing the HTTP trigger](./http-trigger.md) for more +> details about building HTTP applications. + +Building a Spin HTTP component with the JavaScript/TypeScript SDK now involves adding an event listener for the `fetch` event. This event listener handles incoming HTTP requests and allows you to construct and return HTTP responses. + +Below is a complete implementation for such a component in TypeScript: + +```javascript +import { AutoRouter } from 'itty-router'; + +let router = AutoRouter(); + +router + .get("/", () => new Response("hello universe")) + .get('/hello/:name', ({ name }) => `Hello, ${name}!`) + +//@ts-ignore +addEventListener('fetch', async (event: FetchEvent) => { + event.respondWith(router.fetch(event.request)); +}); + +``` + +## Sending Outbound HTTP Requests + +If allowed, Spin components can send outbound HTTP requests using the `fetch` function. +Let's see an example of a component that makes a request to [an API that returns random animal facts](https://random-data-api.fermyon.app/animals/json) + +```javascript +import { AutoRouter } from 'itty-router'; + +let router = AutoRouter(); + +router + .get("*", getDataFromAPI) + +async function getDataFromAPI(_request: Request) { + let response = await fetch( + 'https://random-data-api.fermyon.app/physics/json', + ); + let data = await response.json(); + let fact = `Here is a fact: ${data.fact}`; + return new Response(fact); +} + +//@ts-ignore +addEventListener('fetch', async (event: FetchEvent) => { + event.respondWith(router.fetch(event.request)); +}); + +``` + +Before we can execute this component, we need to add the `random-data-api.fermyon.app` +domain to the application manifest `allowed_outbound_hosts` list containing the list of +domains the component is allowed to make HTTP requests to: + + + +```toml +spin_manifest_version = 2 + +[application] +authors = ["Your Name "] +description = "" +name = "hello-world" +version = "0.1.0" + +[[trigger.http]] +route = "/..." +component = "hello-world" + +[component.hello-world] +source = "target/hello-world.wasm" +exclude_files = ["**/node_modules"] +allowed_outbound_hosts = ["https://random-data-api.fermyon.app"] +[component.hello-world.build] +command = "npm run build" +``` + +The component can be built using the `spin build` command. Running the application using `spin up` will start the HTTP listener locally (by default on `localhost:3000`): + + + +```text +$ curl -i localhost:3000 +HTTP/1.1 200 OK +date = "2023-11-04T00:00:01Z" +content-type: application/json; charset=utf-8 +content-length: 185 +server: spin/0.1.0 + +Here is a fact: Reindeer grow new antlers every year +``` + +> Without the `allowed_outbound_hosts` field populated properly in `spin.toml`, +> the component would not be allowed to send HTTP requests, and sending the +> request would result in a "Destination not allowed" error. + +> You can set `allowed_outbound_hosts = ["https://*:*"]` if you want to allow +> the component to make requests to any HTTP host. This is not recommended +> unless you have a specific need to contact arbitrary servers and perform your own safety checks. + +We just built a WebAssembly component that sends an HTTP request to another +service, manipulates that result, then responds to the original request. +This can be the basis for building components that communicate with external +databases or storage accounts, or even more specialized components like HTTP +proxies or URL shorteners. + +### Intra-Application Requests in JavaScript + +JavaScript's `fetch` function handles relative URLs in a way that doesn't work well with Spin's fine-grained outbound HTTP permissions. +Therefore, when [making a request to another route within the same application](./http-outbound#intra-application-http-requests-by-route), +you must use the special pseudo-host `self.alt` rather than a relative route. For example: + +```javascript +await fetch('/api'); // Avoid! +await fetch('http://self.alt/api'); // Prefer! +``` + +You must [add `http://self` or `http://self.alt` to the component's `allowed_outbound_hosts`](./http-outbound#intra-application-http-requests-by-route). + +--- + +## Storing Data in Redis From JS/TS Components + +> You can find a complete example for using outbound Redis from an HTTP component +> in the [spin-js-sdk repository on GitHub](https://github.com/spinframework/spin-js-sdk/blob/main/examples/spin-host-apis/spin-redis). + +We can install and use the `@spinframework/spin-redis` package to use the Redis key/value store and to publish messages to Redis channels. + +Let's see how we can use the JS/TS SDK to connect to Redis: + +```javascript +import { AutoRouter } from 'itty-router'; +import { Redis } from '@spinframework/spin-redis'; + +const encoder = new TextEncoder(); +const redisAddress = 'redis://localhost:6379/'; + +let router = AutoRouter(); + +router + .get("/", () => { + try { + let db = Redis.open(redisAddress); + db.set('test', encoder.encode('Hello world')); + let val = db.get('test'); + + if (!val) { + return new Response(null, { status: 404 }); + } + return new Response(val); + } catch (e: any) { + return new Response(`Error: ${JSON.stringify(e.payload)}`, { status: 500 }); + } + }) + +//@ts-ignore +addEventListener('fetch', async (event: FetchEvent) => { + event.respondWith(router.fetch(event.request)); +}); + +``` + +This HTTP component demonstrates fetching a value from Redis by key, setting a key with a value, and publishing a message to a Redis channel. + +> When using Redis databases hosted on the internet (i.e) not on localhost, the `redisAddress` must be of the format "redis://\:\@\" (e.g) `redis://myUsername:myPassword@redis-database.com` + +As with all networking APIs, you must grant access to Redis hosts via the `allowed_outbound_hosts` field in the application manifest: + + + +```toml +[component.storage-demo] +allowed_outbound_hosts = ["redis://localhost:6379"] +``` + +## Storing Data in the Spin Key-Value Store + +Spin has a key-value store built in. For information about using it from TypeScript/JavaScript, see [the key-value API guide](kv-store-api-guide). + +## Storing Data in SQLite + +We can use the `@spinframework/spin-sqlite` package to interact with Spin's SQLite interface. + +For more information about using SQLite from TypeScript/Javascript, see [SQLite storage](sqlite-api-guide). + +## Storing Data in MySQL and PostgreSQL Relational Databases + +We can use the `@spinframework/spin-mysql`, `@spinframework/spin-postgres` package to interact with Spin's RDBMS interfaces. + +For more information about using relational databases from TypeScript/JavaScript, see [Relational Databases](rdbms-storage). + +## AI Inferencing From JS/TS Components + +We can use the `@spinframework/spin-llm` package to interact with Spin's LLM interface. + +For more information about using Serverless AI from JS/TS, see the [Serverless AI](serverless-ai-api-guide) API guide. + +## Node.js Compatibility + +The SDK does not support the full specification of `Node.js`. A limited set of APIs can be polyfilled using the [`@fermyon/wasi-ext`](https://github.com/fermyon/js-wasi-ext) library which provides a webpack plugin. It can be used by installing the library first using: + + + +```bash +$ npm install @spinframework/wasi-ext +``` + +Once installed, the plugin provided by it can be added to the webpack config: + +```js +import WasiExtPlugin from "@spinframework/wasi-ext/plugin/index.js"; + +export default config = () => { + ... + return { + ... + plugins: [ + new WasiExtPlugin() + ], + ... + } +} +``` + +This library only currently supports the following polyfills: + +- `Node.js` buffers +- `process` - certain methods are no-ops and few throw exceptions. For detailed list refer to the [upstream library](https://github.com/defunctzombie/node-process/blob/master/browser.js). **Note:** `process.env` is populated only inside the handler and returns an empty object outside the handler. +- `fs` - only implements `readFileSync` and `readdirSync`. +- `os` - Implements only `EOL` and `arch`. + + +## Using External NPM Libraries + +> Not all the NPM packages are guaranteed to work with the SDK as it is not fully compatible with the browser or `Node.js`. It implements only a subset of the API. + +Some NPM packages can be installed and used in the component. If a popular library does not work, please open an issue/feature request in the [spin-js-sdk repository](https://github.com/spinframework/spin-js-sdk/issues). + +### Suggested Libraries for Common Tasks + +These are some of the suggested libraries that have been tested and confirmed to work with the SDK for common tasks. + +{{ details "HTML parsers" "- [node-html-parser](https://www.npmjs.com/package/node-html-parser)" }} + +{{ details "Parsing formdata" "- [parse-multipart-data](https://www.npmjs.com/package/parse-multipart-data)" }} + +{{ details "Runtime schema validation" "- [zod](https://www.npmjs.com/package/zod)" }} + +{{ details "Unique ID generator" "- [nanoid](https://www.npmjs.com/package/nanoid)\n- [ulidx](https://www.npmjs.com/package/ulidx)\n- [uuid](https://www.npmjs.com/package/uuid)" }} + +## Caveats + +- All `spin-sdk` related functions and methods (like `Variables`, `Redis`, `Mysql`, `Pg`, `Kv` and `Sqlite`) can be called only inside the fetch event handler. This includes `fetch`. Any attempts to use it outside the function will lead to an error. This is due to Wizer using only Wasmtime to execute the script at build time, which does not include any Spin SDK support. +- No crypto operation that involve handling private keys are supported. + +## Debugging in VSCode + +You can use the experimental [StarlingMonkey Debugger](https://marketplace.visualstudio.com/items?itemName=BytecodeAlliance.starlingmonkey-debugger) to debug JavaScript HTTP components. + +> The debugger is a work in progress, and has some known issues and limitations. In particular, you will need to restart it for each request. + + +### Installing the Extension + +The extension can be installed from the [extension store](https://marketplace.visualstudio.com/items?itemName=BytecodeAlliance.starlingmonkey-debugger). Verify that the installed version is `0.2.1` or higher. + +### Setting up the Project for Debugging + +Templates starting from Spin v3.5 include the required setup for supporting debugging. You can verify this by looking at `.vscode/setting.json` and `.vscode/launch.json` and checking if something like the following is configured: + +```json +// .vscode/setting.json +{ + "starlingmonkey": { + "componentRuntime": { + "executable": "spin", + "options": [ + "up", + "-f", + "${workspaceFolder}", + ], + } + } +} + +// .vscode/launch.json +{ + "version": "0.2.0", + "configurations": [ + { + "type": "starlingmonkey", + "request": "launch", + "name": "Debug StarlingMonkey component", + "component": "${workspaceFolder}/dist/.wasm", + "program": "${workspaceFolder}/src/index.ts", + "stopOnEntry": false, + "trace": true + } + ] +} +``` + +If it does not exist, create or edit `settings.json` and `launch.json` yourself, using the [testcase](https://github.com/spinframework/spin-js-sdk/tree/main/test/debugger-testing/spin-ts/.vscode) as a reference. Make sure to set up the `program` field in `launch.json` based on whether it is a JavaScript or TypeScript project. + +Once that is set up, update the `build` setting in `spin.toml` to build the debug component. To do this, change the component build `command` to be `npm run build:debug`. + +You must also add add `"tcp://127.0.0.1:*"` to the list of `allowed_outbound_hosts`. + +Now you can build the app using the familiar `spin build` command. + +### Running a Project in the Debugger + +You can start the app and attach the debugger to it using the `F5` key or using the Start Debugger button as show below: + +![Starting VScode debugger](/static/image/docs/js-debugger.jpg) + +Once the debugger is attached, it can be used in the same way as the built-in VS Code debugger. For example, you can do things like setting breakpoints and stepping through the code. + +![JS debugger running](/static/image/docs/js-debugger-running.png) + +**Note:** The debugger does not follow sourcemaps for NPM dependencies, as some packages include sourcemaps but not the actual source code. Breakpoints and stepping work as expected in your own code, but may not work in third-party dependencies. diff --git a/content/v4/kv-store-api-guide.md b/content/v4/kv-store-api-guide.md new file mode 100644 index 00000000..aa81dd7d --- /dev/null +++ b/content/v4/kv-store-api-guide.md @@ -0,0 +1,195 @@ +title = "Key Value Store" +template = "main" +date = "2023-11-04T00:00:01Z" +enable_shortcodes = true +[extra] +url = "https://github.com/spinframework/spin-docs/blob/main/content/v4/kv-store-api-guide.md" + +--- +- [Using Key Value Store From Applications](#using-key-value-store-from-applications) +- [Custom Key Value Stores](#custom-key-value-stores) +- [Granting Key Value Store Permissions to Components](#granting-key-value-store-permissions-to-components) + +Spin provides an interface for you to persist data in a key value store managed by Spin. This key value store allows Spin developers to persist non-relational data across application invocations. + +{{ details "Why do I need a Spin interface? Why can't I just use my own external store?" "You can absolutely still use your own external store either with the Redis or Postgres APIs, or outbound HTTP. However, if you're interested in quick, non-relational local storage without any infrastructure set-up then Spin's key value store is a great option." }} + +## Using Key Value Store From Applications + +The Spin SDK surfaces the Spin key value store interface to your language. The following characteristics are true of keys and values: + +* Keys as large as 256 bytes (UTF-8 encoded) +* Values as large as 1 megabyte +* Capacity for 1024 key value tuples + +The set of operations is common across all SDKs: + +| Operation | Parameters | Returns | Behavior | +|------------|------------|---------|----------| +| `open` | name | store | Open the store with the specified name. If `name` is the string "default", the default store is opened, provided that the component that was granted access in the component manifest from `spin.toml`. Otherwise, `name` must refer to a store defined and configured in a [runtime configuration file](./dynamic-configuration.md#key-value-store-runtime-configuration) supplied with the application.| +| `get` | store, key | value | Get the value associated with the specified `key` from the specified `store`. | +| `set` | store, key, value | - | Set the `value` associated with the specified `key` in the specified `store`, overwriting any existing value. | +| `delete` | store, key | - | Delete the tuple with the specified `key` from the specified `store`. `error::invalid-store` will be raised if `store` is not a valid handle to an open store. No error is raised if a tuple did not previously exist for `key`.| +| `exists` | store, key | boolean | Return whether a tuple exists for the specified `key` in the specified `store`.| +| `get-keys` | store | stream | Return a stream of all the keys in the specified `store`. NOTE: errors are reported via a future (promise) which resolves once the stream has ended. | +| `close` | store | - | Close the specified `store`. | + +The exact detail of calling these operations from your application depends on your language: + +{{ tabs "sdk-type" }} + +{{ startTab "Rust"}} + +> [**Want to go straight to the reference documentation?** Find it here.](https://docs.rs/spin-sdk/latest/spin_sdk/key_value/index.html) + +Key value functions are available in the `spin_sdk::key_value` module. The function names match the operations above. For example: + +```rust +use bytes::Bytes; +use spin_sdk::{ + http::{FullBody, IntoResponse, Request, Response}, + http_service, + key_value::Store, +}; + +#[http_service] +async fn handle_request(_req: Request) -> anyhow::Result { + let store = Store::open_default().await?; + store.set("mykey", b"myvalue").await?; + let value = store.get("mykey").await?; + let response = value.unwrap_or_else(|| "not found".into()); + Ok(Response::new(FullBody::new(Bytes::from(response)))) +} +``` + +**General Notes** + +`set` **Operation** +- For set, the value argument can be of any type that implements `AsRef<[u8]>` + +`get` **Operation** +- For get, the return value is of type `Option>`. If the key does not exist it returns `None`. + +`get_keys` **Operation** +- This returns a stream containing the keys, and a future containing a `Result`. You _must_ check the future when the stream ends, to determine if the stream ended normally, or was terminated prematurely due to an error. + +`open` and `close` **Operations** +- The close operation is not surfaced; it is called automatically when the store is dropped. + +`set_json` and `get_json` **Operation** +- Rust applications can [store and retrieve serializable Rust types](./rust-components#storing-data-in-the-spin-key-value-store). + +{{ blockEnd }} + +{{ startTab "Typescript"}} + +> [**Want to go straight to the reference documentation?** Find it here.](https://spinframework.github.io/spin-js-sdk/) + +The key value functions can be accessed after opening a store using either [the `open` or the `openDefault` functions](https://spinframework.github.io/spin-js-sdk/modules/_spinframework_spin-kv.html) which returns a [handle to the store](https://spinframework.github.io/spin-js-sdk/interfaces/_spinframework_spin-kv.Store.html). For example: + +```ts +import { AutoRouter } from 'itty-router'; +import { openDefault } from '@spinframework/spin-kv'; + +let router = AutoRouter(); + +router + .get("/", () => { + let store = openDefault() + store.set("mykey", "myvalue") + return new Response(store.get("mykey") ?? "Key not found"); + }) + +//@ts-ignore +addEventListener('fetch', async (event: FetchEvent) => { + event.respondWith(router.fetch(event.request)); +}); +``` + +**General Notes** +- The SDK doesn't surface the `close` operation. It automatically closes all stores at the end of the request; there's no way to close them early. + +[`get` **Operation**](https://spinframework.github.io/spin-js-sdk/interfaces/_spinframework_spin-kv.Store.html#get) +- The result is of the type `Uint8Array | null` +- If the key does not exist, `get` returns `null` + +[`set` **Operation**](https://spinframework.github.io/spin-js-sdk/interfaces/_spinframework_spin-kv.Store.html#set) +- The value argument is of the type `Uint8Array | string | object`. + +[`setJson`](https://spinframework.github.io/spin-js-sdk/interfaces/_spinframework_spin-kv.Store.html#setjson) and [`getJson` **Operation**](https://spinframework.github.io/spin-js-sdk/interfaces/_spinframework_spin-kv.Store.html#getjson) +- Applications can store JavaScript objects using `setJson`; these are serialized within the store as JSON. These serialized objects can be retrieved and deserialized using `getJson`. If you call `getJson` on a key that doesn't exist then it returns an empty object. + +{{ blockEnd }} + +{{ startTab "Python"}} + +> [**Want to go straight to the reference documentation?** Find it here.](https://spinframework.github.io/spin-python-sdk/v3/key_value.html) + +The key value functions are provided through the `spin_key_value` module in the Python SDK. For example: + +```python +from spin_sdk import http, key_value +from spin_sdk.http import Request, Response + +class IncomingHandler(http.IncomingHandler): + def handle_request(self, request: Request) -> Response: + with key_value.open_default() as store: + store.set("test", bytes("hello world!", "utf-8")) + val = store.get("test") + + return Response( + 200, + {"content-type": "text/plain"}, + val + ) + +``` + +**General Notes** +- The Python SDK doesn't surface the `close` operation. It automatically closes all stores at the end of the request; there's no way to close them early. + +[`get` **Operation**](https://spinframework.github.io/spin-python-sdk/v3/wit/imports/key_value.html#spin_sdk.wit.imports.key_value.Store.get) +- If a key does not exist, it returns `None` + +{{ blockEnd }} + +{{ startTab "TinyGo"}} + +> [**Want to go straight to the Spin SDK reference documentation?** Find it here.](https://pkg.go.dev/github.com/spinframework/spin-go-sdk/v2@v2.2.1/kv) + +Key value functions are provided by the `github.com/spinframework/spin-go-sdk/v2/kv` module. [See Go Packages for reference documentation.](https://pkg.go.dev/github.com/spinframework/spin-go-sdk/v2/kv) For example: + +```go +import "github.com/spinframework/spin-go-sdk/v2/kv" + +func example() error { + store, err := kv.OpenStore("default") + if err != nil { + return err + } + defer store.Close() + previous, err := store.Get("mykey") + return store.Set("mykey", []byte("myvalue")) +} + +``` + +{{ blockEnd }} + +{{ blockEnd }} + +## Custom Key Value Stores + +Spin defines a key-value store named `"default"` and provides automatic backing storage. If you need to customize Spin with additional stores, or to change the backing storage for the default store, you can do so via the `--runtime-config-file` flag and the `runtime-config.toml` file. See [Key Value Store Runtime Configuration](./dynamic-configuration#key-value-store-runtime-configuration) for details. + +## Granting Key Value Store Permissions to Components + +By default, a given component of an app will not have access to any key value store. Access must be granted specifically to each component via the component manifest: + +```toml +[component.example] +# Pass in 1 or more key value stores, based on how many you'd like your component to have access to +key_value_stores = ["", ""] +``` + +For example, a component could be given access to the default store using `key_value_stores = ["default"]`. diff --git a/content/v4/language-support-overview.md b/content/v4/language-support-overview.md new file mode 100644 index 00000000..b175845c --- /dev/null +++ b/content/v4/language-support-overview.md @@ -0,0 +1,110 @@ +title = "Language Support Overview" +template = "main" +date = "2023-11-04T00:00:01Z" +enable_shortcodes = true +[extra] +url = "https://github.com/spinframework/spin-docs/blob/main/content/v4/language-support-overview.md" + +--- + +This page contains information about language support for Spin features: + +{{ tabs "sdk-type" }} + +{{ startTab "Rust"}} + +**[πŸ“„ Visit the Rust Spin SDK reference documentation](https://docs.rs/spin-sdk/latest/spin_sdk/) to see specific modules, functions, variables and syntax relating to the following Rust features.** + +| Feature | SDK Supported? | +|-----|-----| +| **Triggers** | +| [HTTP](./http-trigger) | Supported | +| [Redis](./redis-trigger) | Supported | +| **APIs** | +| [Outbound HTTP](./rust-components.md#sending-outbound-http-requests) | Supported | +| [Configuration Variables](./variables) | Supported | +| [Key Value Storage](./kv-store-api-guide) | Supported | +| [SQLite Storage](./sqlite-api-guide) | Supported | +| [MySQL](./rdbms-storage#using-mysql-and-postgresql-from-applications) | Supported | +| [PostgreSQL](./rdbms-storage#using-mysql-and-postgresql-from-applications) | Supported | +| [Outbound Redis](./rust-components.md#storing-data-in-redis-from-rust-components) | Supported | +| [Serverless AI](./serverless-ai-api-guide) | Supported | +| [MQTT Messaging](./mqtt-outbound) | Supported | +| **Extensibility** | +| [Authoring Custom Triggers](./extending-and-embedding) | Supported | + +{{ blockEnd }} + +{{ startTab "TypeScript"}} + +**[πŸ“„ Visit the JS/TS Spin SDK reference documentation](https://spinframework.github.io/spin-js-sdk/) to see specific modules, functions, variables and syntax relating to the following TS/JS features.** + +| Feature | SDK Supported? | +|-----|-----| +| **Triggers** | +| [HTTP](./javascript-components#http-components) | Supported | +| Redis | Not Supported | +| **APIs** | +| [Outbound HTTP](./javascript-components#sending-outbound-http-requests) | Supported | +| [Configuration Variables](./dynamic-configuration#custom-config-variables) | Supported | +| [Key Value Storage](./kv-store-api-guide) | Supported | +| [SQLite Storage](./sqlite-api-guide) | Supported | +| [MySQL](./rdbms-storage#using-mysql-and-postgresql-from-applications) | Supported | +| [PostgreSQL](./rdbms-storage#using-mysql-and-postgresql-from-applications) | Supported | +| [Outbound Redis](./javascript-components#storing-data-in-redis-from-jsts-components) | Supported | +| [Serverless AI](./serverless-ai-api-guide) | Supported | +| [MQTT Messaging](./mqtt-outbound) | Supported | +| **Extensibility** | +| Authoring Custom Triggers | Not Supported | + +{{ blockEnd }} + +{{ startTab "Python"}} + +**[πŸ“„ Visit the Python Spin SDK reference documentation](https://spinframework.github.io/spin-python-sdk/v3) to see specific modules, functions, variables and syntax relating to the following Python SDK.** + +| Feature | SDK Supported? | +|-----|-----| +| **Triggers** | +| [HTTP](./python-components#a-simple-http-components-example) | Supported | +| [Redis](./redis-trigger) | Supported | +| **APIs** | +| [Outbound HTTP](./python-components#an-outbound-http-example) | Supported | +| [Configuration Variables](./dynamic-configuration#custom-config-variables) | Supported | +| [Key Value Storage](./kv-store-api-guide) | Supported | +| [SQLite Storage](./sqlite-api-guide) | Supported | +| MySQL | Supported | +| PostgreSQL | Supported | +| [Outbound Redis](./python-components#an-outbound-redis-example) | Supported | +| [Serverless AI](./serverless-ai-api-guide) | Supported | +| [MQTT Messaging](./mqtt-outbound) | Not Supported | +| **Extensibility** | +| Authoring Custom Triggers | Not Supported | + +{{ blockEnd }} + +{{ startTab "TinyGo"}} + +**[πŸ“„ Visit the TinyGo Spin SDK reference documentation](https://pkg.go.dev/github.com/spinframework/spin-go-sdk/v2) to see specific modules, functions, variables and syntax relating to the following TinyGo SDK.** + +| Feature | SDK Supported? | +|-----|-----| +| **Triggers** | +| [HTTP](./go-components#http-components) | Supported | +| [Redis](./go-components#redis-components) | Supported | +| **APIs** | +| [Outbound HTTP](./go-components#sending-outbound-http-requests) | Supported | +| [Configuration Variables](./dynamic-configuration#custom-config-variables) | Supported | +| [Key Value Storage](./kv-store-api-guide) | Supported | +| [SQLite Storage](./sqlite-api-guide) | Supported | +| [MySQL](./rdbms-storage#using-mysql-and-postgresql-from-applications) | Supported | +| [PostgreSQL](./rdbms-storage#using-mysql-and-postgresql-from-applications) | Supported | +| [Outbound Redis](./go-components#storing-data-in-redis-from-go-components) | Supported | +| [Serverless AI](./serverless-ai-api-guide) | Supported | +| [MQTT Messaging](./mqtt-outbound) | Not Supported | +| **Extensibility** | +| Authoring Custom Triggers | Not Supported | + +{{ blockEnd }} + +{{ blockEnd }} diff --git a/content/v4/managing-plugins.md b/content/v4/managing-plugins.md new file mode 100644 index 00000000..9f095722 --- /dev/null +++ b/content/v4/managing-plugins.md @@ -0,0 +1,197 @@ +title = "Managing Plugins" +template = "main" +date = "2023-11-04T00:00:01Z" +enable_shortcodes = true +[extra] +url = "https://github.com/spinframework/spin-docs/blob/main/content/v4/managing-plugins.md" + +--- +- [Installing Plugins](#installing-plugins) + - [Installing Well Known Plugins](#installing-well-known-plugins) + - [Installing a Specific Version of a Plugin](#installing-a-specific-version-of-a-plugin) + - [Installing a Plugin From a URL](#installing-a-plugin-from-a-url) + - [Installing a Plugin From a File](#installing-a-plugin-from-a-file) +- [Running a Plugin](#running-a-plugin) +- [Viewing Available Plugins](#viewing-available-plugins) + - [Viewing Installed Plugins](#viewing-installed-plugins) +- [Uninstalling Plugins](#uninstalling-plugins) +- [Refreshing the Catalogue](#refreshing-the-catalogue) +- [Upgrading Plugins](#upgrading-plugins) +- [Downgrading Plugins](#downgrading-plugins) +- [Next Steps](#next-steps) + +Plugins are a way to extend the functionality of Spin. Spin provides commands for installing and removing them, so you don't need to use separate installation tools. When you have installed a plugin into Spin, you can call it as if it were a Spin subcommand. For example, Fermyon Cloud can be accessed with a plugin called `cloud`, and you run it via the `spin cloud` command. + +## Installing Plugins + +To install plugins, use the `spin plugins install` command. You can install plugins by name from a curated repository, or other plugins from a URL or file system. + +### Installing Well Known Plugins + +The Spin maintainers curate a catalogue of "known" plugins. You can install plugins from this catalogue by name: + + + +```bash +$ spin plugins install cloud +``` + +Spin checks that the plugin is available for your version of Spin and your operating system, and prompts you to confirm the installation. To skip the prompt, pass the `--yes` flag. + +> The curated plugins catalogue is stored in a GitHub repository. The first time you install a plugin from the catalogue, Spin clones this repository into a local cache and uses it for future install, list and upgrade commands (similar to OS package managers such as `apt`). If you want to see new catalogue entries - new plugins or new versions - you must update the local cache by running the `spin plugins update` command. + +### Installing a Specific Version of a Plugin + +To install a specific version of a plugin, pass the `--version` flag: + + + +```bash +$ spin plugins install cloud --version 0.9.1 +``` + +### Installing a Plugin From a URL + +If the plugin you want has been published on the Web but has not been added to the catalogue, you can install it from its manifest URL. The manifest is the JSON document that links to the binaries for different operating systems and processors. For example: + + + +```bash +$ spin plugins install --url https://github.com/spinframework/spin-befunge-sdk/releases/download/v1.4.0/befunge2wasm.json +``` + +If the URL requires authorization, pass the HTTP authorization header value via `--auth-header-value`. The value of the flag must be the _full_ header value, including the authorization scheme, not just your token or password. For example: + + + +```bash +# URL requires bearer authorization +$ spin plugins install --auth-header-value "Bearer 12345678" --url https://example.com/tell-no-one.json + +# URL requires basic authorization +$ spin plugins install --auth-header-value "Basic c2xhdHM6SWwwdjNmIXNo" --url https://example.com/tell-no-one.json +``` + +### Installing a Plugin From a File + +If the plugin you want is in your local file system, you can install it from its manifest file path. The manifest is the JSON document that links to the binaries for different operating systems and processors. For example: + + + +```bash +$ spin plugins install --file ~/dev/spin-befunge-sdk/befunge2wasm.json +``` + +## Running a Plugin + +You run plugins in the same way as built-in Spin subcommands. For example: + + + +```bash +$ spin cloud --help +``` + +## Viewing Available Plugins + +To see what plugins are available in the catalogue, run `spin plugins search`: + + + +```bash +$ spin plugins search +befunge2wasm 1.4.0 [incompatible] +cloud 0.8.0 [installed] +cloud 0.9.0 +trigger-sqs 0.1.0 +``` + +The annotations by the plugins show their status and compatibility: + +| Annotation | Meaning | +|---------------------------------|---------| +| `[incompatible]` | The plugin does not run on your operating system or processor. | +| `[installed]` | You have the plugin already installed and available to run. | +| `[requires other Spin version]` | The plugin can run on your operating system and processor, but is not compatible with the version of Spin you are running. The annotation indicates which versions of Spin it is compatible with. | + +### Viewing Installed Plugins + +To see only the plugins you have installed, run `spin plugins list --installed`. + +## Uninstalling Plugins + +You can uninstall plugins using `spin plugins uninstall` with the plugin name: + + + +```bash +$ spin plugins uninstall befunge2wasm +``` + +## Refreshing the Catalogue + +The first time you install a plugin from the catalogue, Spin creates a local cache of the catalogue. It continues to use this local cache for future install, list and upgrade commands; this is similar to OS package managers such as `apt`, and avoids rate limiting on the catalogue. However, this means that in order to see new catalogue entries - new plugins or new versions - you must first update the cache. + +To update your local cache of the catalogue, run `spin plugins update`. + +## Upgrading Plugins + +To upgrade a plugin to the latest version, first run `spin plugins update` (to refresh the catalogue), then `spin plugins upgrade`. + +The `spin plugins upgrade` command has the same options as the `spin plugins install` command (according to whether the plugin comes from the catalogue, a URL, or a file). For more information, see the command help by running `spin plugins upgrade --help`. + +> The `upgrade` command uses your local cache of the catalogue. This might not include recently added plugins or versions. So always remember to run `spin plugins update` to refresh your local cache of the catalogue before performing the `spin plugins upgrade` command. + +The following example shows how to upgrade one plugin at a time (i.e. the `cloud` plugin): + + + +```bash +$ spin plugins update +$ spin plugins upgrade cloud +``` + +The following example shows how to upgrade all installed plugins at once: + + + +```bash +$ spin plugins update +$ spin plugins upgrade --all +``` + +> Note: The above example only installs plugins from the catalogue + +The following example shows additional upgrade options. Specifically, how to upgrade using the path to a remote plugin manifest and how to upgrade using the path to a local plugin manifest: + + + +```bash +$ spin plugins upgrade --url https://github.com/spinframework/spin-befunge-sdk/releases/download/v1.7.0/befunge2wasm.json +$ spin plugins upgrade --file ~/dev/spin-befunge-sdk/befunge2wasm.json +``` + +## Downgrading Plugins + +By default, Spin will only _upgrade_ plugins. Pass the `--downgrade` flag and specify the `--version` if you want Spin to roll back to an earlier version. The following abridged example (which doesn't list the full console output for simplicity) lists the versions of plugins, downgrades the `cloud` to an older version (`0.9.0`) and then lists the versions again to show the results: + + + +```bash +$ spin plugins update +$ spin plugins list +// --snip-- +cloud 0.9.0 +cloud 0.9.1 [installed] +$ spin plugins upgrade cloud --downgrade --version 0.9.0 +$ spin plugins list +// --snip-- +cloud 0.9.0 [installed] +cloud 0.9.1 +``` + +After downgrading, the `[installed]` indicator is aligned with the `0.9.0` version of `cloud`, as intended in the example. + +## Next Steps + +- [Check out the spin cloud plugin](https://github.com/fermyon/cloud-plugin) diff --git a/content/v4/managing-templates.md b/content/v4/managing-templates.md new file mode 100644 index 00000000..5fb5d714 --- /dev/null +++ b/content/v4/managing-templates.md @@ -0,0 +1,140 @@ +title = "Managing Templates" +template = "main" +date = "2023-11-04T00:00:01Z" +enable_shortcodes = true +[extra] +url = "https://github.com/spinframework/spin-docs/blob/main/content/v4/managing-templates.md" + +--- +- [Installing Templates](#installing-templates) + - [Installing From the Spin Git Repository](#installing-from-the-spin-git-repository) + - [Installing From a Specific Branch](#installing-from-a-specific-branch) + - [Installing From a Local Directory](#installing-from-a-local-directory) + - [Installing From a Remote Tarball](#installing-from-a-remote-tarball) +- [Viewing Your Installed Templates](#viewing-your-installed-templates) +- [Uninstalling Templates](#uninstalling-templates) +- [Upgrading Templates](#upgrading-templates) + - [Upgrading Templates From a Local Directory](#upgrading-templates-from-a-local-directory) +- [Next Steps](#next-steps) + +Templates are a Spin tool for scaffolding new applications and components. You can use them via the `spin new` and `spin add` commands. For more information about creating applications with templates, see [Writing Spin Applications](writing-apps). + +## Installing Templates + +> This section covers general principles for installing templates. For information about installing templates for specific languages, see [Writing Spin Applications](writing-apps). + +To install templates, use the `spin templates install` command. You can install templates from a Git repository, or while [authoring templates](template-authoring) you can install them from a local directory. + +### Installing From the Spin Git Repository + +To install templates from the Spin Git repository, run `spin templates install --git`: + + + +```bash +$ spin templates install --git https://github.com/spinframework/spin +``` + +If you prefer a shorter command, you can just pass the repository id instead of the full URL: + + + +```bash +$ spin templates install --git fermyon/spin +``` + +The above command installs _all_ templates in the repository. + +> Language SDKs often ship templates in their repositories; see the relevant language guide to find out where to get its templates. + +### Installing From a Specific Branch + +By default, if you install templates from a Git repository, Spin tries to find a repo tag that matches the version of Spin, and installs from that tag. Failing this, it installs from `HEAD`. If you would like to install from a specific tag or branch, pass the `--branch` option: + + + +```bash +$ spin templates install --git https://github.com/spinframework/spin --branch spin/templates/v0.8 +``` + +### Installing From a Local Directory + +To install templates from your local file system, run `spin templates install --dir`. + +> The directory you pass must be one that _contains_ a `templates` directory. Don't pass the `templates` directory itself! + + + +```bash +# Expects to find a directory ~/dev/spin-befunge-sdk/templates +$ spin templates install --dir ~/dev/spin-befunge-sdk +``` + +See [Template Authoring](template-authoring) for more details on this layout. + +### Installing From a Remote Tarball + +To install templates from a remote tarball, run `spin templates install --tar`. + +> The tarball must have a `/templates` directory at its root, _or_ have a single root directory and have a `templates` directory within that. This slightly complicated rule is so that it works correctly with a GitHub release tarball, which always has a root directory named after the release. + + + +```bash +$ spin templates install --tar https://github.com/spinframework/spin/archive/refs/tags/v9.8.7.tar.gz +``` + +## Viewing Your Installed Templates + +To see what templates you have installed, run `spin templates list`. + +You can use the `--verbose` option to see additional information such as where they were installed from. + +## Uninstalling Templates + +You can uninstall templates using `spin templates uninstall` with the template name: + + + +```bash +$ spin templates uninstall redis-befunge +``` + +> Spin doesn't currently support uninstalling a whole repo-worth of templates, only individual templates. + +## Upgrading Templates + +When you upgrade Spin, you will typically want to upgrade your templates to match. This means new applications and components will get dependencies that match the Spin version you are using. To do this, run `spin templates upgrade`: + + + +```bash +$ spin templates upgrade +Select repos to upgrade. Use Space to select/deselect and Enter to confirm selection. + [x] https://github.com/spinframework/spin-python-sdk + [ ] https://github.com/spinframework/spin (at spin/templates/v1.0) +> [x] https://github.com/spinframework/spin-js-sdk +``` + +Use the cursor keys and the space bar to select the repositories you want to upgrade, then hit Enter to upgrade the selected repositories. + +> Upgrading happens at the repo level, not the individual template level. If you've uninstalled templates, upgrading the repo they came from will bring them back. + +If you want to upgrade _all_ repositories without being prompted, run `spin templates upgrade --all`. + +As mentioned above, if you want to check which templates come from which repositories use `--verbose` i.e. `spin templates list --verbose`. + +### Upgrading Templates From a Local Directory + +`spin templates upgrade` only upgrades from Git repositories. If you want to upgrade and your templates are in a local directory, run the `spin templates install` command with the `--upgrade` flag: + + + +```bash +$ spin templates install --dir ~/dev/spin-befunge-sdk --upgrade +``` + +## Next Steps + +- [Install the templates for your language](quickstart) +- [Use your language templates to create an application](writing-apps) \ No newline at end of file diff --git a/content/v4/manifest-reference.md b/content/v4/manifest-reference.md new file mode 100644 index 00000000..21c2af08 --- /dev/null +++ b/content/v4/manifest-reference.md @@ -0,0 +1,215 @@ +title = "Spin Application Manifest Reference" +template = "main" +date = "2023-11-04T00:00:01Z" +[extra] +url = "https://github.com/spinframework/spin-docs/blob/main/content/v4/manifest-reference.md" + +--- +- [Manifest Format](#manifest-format) +- [Using Variables in the Manifest](#using-variables-in-the-manifest) +- [Manifest Fields](#manifest-fields) +- [The `application` Table](#the-application-table) +- [The `application.trigger` Table](#the-applicationtrigger-table) + - [The `application.trigger.redis` Table](#the-applicationtriggerredis-table) +- [The `variables` Table](#the-variables-table) +- [The `trigger` Table](#the-trigger-table) + - [Common Fields for All `trigger.(type)` Tables](#common-fields-for-all-triggertype-tables) + - [Additional Fields for `trigger.http` Tables](#additional-fields-for-triggerhttp-tables) + - [Additional Fields for `trigger.redis` Tables](#additional-fields-for-triggerredis-tables) +- [The `component` Table](#the-component-table) +- [The `component.(id).build` Table](#the-componentidbuild-table) +- [The `component.(id).profile.(name)` Table](#the-componentidprofilename-table) +- [Next Steps](#next-steps) + +This page describes the contents of the Spin manifest file, typically called `spin.toml`. + +> There are two versions of the manifest format. The manifest format described here (version 2) is recommended. If you need to maintain an application that uses the old v1 manifest, [see the Spin 3.x documentation](../v3/manifest-reference-v1). + +## Manifest Format + +The manifest is a TOML file, and follows standard TOML syntax. See the [TOML documentation](https://toml.io/) for information about the TOML syntax. Here is an example Spin application manifest (`spin.toml`) that was generated using `spin new -t http-rust spin-manifest-example-in-rust`: + +```toml +spin_manifest_version = 2 + +[application] +name = "spin-manifest-example-in-rust" +version = "0.1.0" +authors = ["Fermyon Engineering "] +description = "An example application to generate a Spin manifest file, in this case, via the HTTP Rust template." + +[[trigger.http]] +route = "/..." +component = "spin-manifest-example-in-rust" + +[component.spin-manifest-example-in-rust] +source = "target/wasm32-wasip2/release/spin_manifest_example_in_rust.wasm" +allowed_outbound_hosts = [] +[component.spin-manifest-example-in-rust.build] +command = "cargo build --target wasm32-wasip2 --release" +watch = ["src/**/*.rs", "Cargo.toml"] +``` + +## Using Variables in the Manifest + +The following fields allow you to use [expressions](./variables.md#adding-variables-to-your-applications) in their values: + +* `application.trigger.redis.address` +* `trigger.redis.address` +* `trigger.redis.channel` +* `component.*.allowed_outbound_hosts` + +Spin resolves manifest expressions at application load time. Subsequent changes to variables do not update expression-based values. + +The only variables permitted in manifest expressions are application variables. + +> Manifest expressions are not yet supported on Fermyon Cloud. + +## Manifest Fields + +| Name | Required? | Type | Value | Example | +|-------------------------|------------|-------------|----------|-----------| +| `spin_manifest_version` | Required | Number | The version of the file format that the rest of the manifest follows. For manifests using this format, this value must be `2`. | `2` | +| `application` | Required | Table | Information about the application and application-global options. See [The `application` Table](#the-application-table) below. | `[application]`
`name = "greetings"`
`version = "1.0.0"` | +| `variables` | Optional | Table | Application configuration variables which the user can set when they run the application. See [The `variables` Table](#the-variables-table) below. | `[variables]`
`message = { default = "hello" }` | +| `trigger` | Required | Table | Associates triggers and conditions to the components that handle them - for example, mapping a HTTP route to a handling component. See [The `trigger` Table](#the-trigger-table) below. | `[[trigger.http]]`
`component = "greeter"`
`route = "/greet"` | +| `component` | Required | Table | The WebAssembly components that make up the application, together with whatever configuration and resources they depend on, such as asset files or storage access. See [The `component` Table](#the-component-table) below. | `[component.greeter]`
`source = "greeting_manager.wasm"`
`files = ["confetti.jpg"]` | + +> If you're familiar with manifest version 1, note that the way trigger parameters map to components - for example, which component handles a particular HTTP route - is now defined on the _trigger_, not on the component. In the version 2 manifest, a `component` section specifies _only_ the Wasm module and the resources it needs. + +## The `application` Table + +| Name | Required? | Type | Value | Example | +|-------------------------|------------|-------------|----------|-----------| +| `name` | Required | String | The name of the application. This may be any string of alphanumeric characters, hyphens and underscores. | `"hello-world"` | +| `version` | Optional | String | The version of the application. The must be a string of the form `major.minor.patch`, where each element is a number. | `"1.0.5"` | +| `description` | Optional | String | A human-readable description of the application. | `"The best app for all your world-greeting needs"` | +| `authors` | Optional | Array of strings | The authors of the applications. If present, this must ba an array, even if it has only one entry. | `["Jane Q Hacker ()"]` | +| `targets` | Optional | Array of strings | The environments that the application is expected to be compatible with. | ["spin-up:3.2"] | +| `trigger` | Optional | Table | Application-global trigger settings. See [The `application.trigger` Table](#the-applicationtrigger-table) below. | `[application.trigger.redis]`
`address = "redis.example.com"` | + +## The `application.trigger` Table + +The `application.trigger` should contain only one key, the trigger type whose settings you want to override. This is usually written inline as part of the TOML table header, e.g. `[application.trigger.redis]`. + +> In many cases, your trigger will have no settings, or the default ones will suffice. In this case, you can omit the `application.trigger` table. + +### The `application.trigger.redis` Table + +| Name | Required? | Type | Value | Example | +|-------------------------|------------|-------------|----------|-----------| +| `address` | Required | String | The address of the Redis instance to which the components subscribe for messages, for triggers that do not specify an address themselves. Use the `redis:` URL scheme. | `"redis://localhost:6379"` | + +## The `variables` Table + +The keys of the `variables` table are user-defined. The value of each key is another table with the fields shown in the following table. + +> Because each `variables` value usually contains only a few simple fields, you will usually see the table entries written inline with the values written using brace notation, rather than fully written out using square-brackets table syntax for each variable. For example: +> +> ```toml +> [variables] +> vessel = { description = "I'm a little teapot!", default = "teapot" } +> token = { required = true, secret = true } +> ``` + +| Name | Required? | Type | Value | Example | +|-------------------------|------------|-------------|----------|-----------| +| `description` | Optional | String | A brief description of the variable | `This a variable!` | +| `default` | Optional | String | The value of the variable if no value is supplied at runtime. If specified, the value must be a string. If not specified, `required` must be `true`. | `"teapot"` | +| `required` | Optional | Boolean | Whether a value must be supplied at runtime. If not specified, `required` defaults to `false`, and `default` must be provided | `false` | +| `secret` | Optional | Boolean | If set, this variable should be treated as sensitive. | `false` | + +## The `trigger` Table + +The `trigger` table contains only one key, the trigger type to which your application responds. The value of this key is a table array. In practice, the `trigger` table is written using table array syntax with the trigger type inlined into each entry. For example: + +```toml +[[trigger.http]] +route = "/users" +component = "user-manager" + +[[trigger.http]] +route = "/reports" +component = "report" +``` + +Each array entry contains a mix of common fields and trigger-specific fields. + +### Common Fields for All `trigger.(type)` Tables + +| Name | Required? | Type | Value | Example | +|-------------------------|------------|-----------------|----------|-----------| +| `component` | Required | String or table | The component to run when a trigger event matching the trigger setting occurs (for example, when Spin receives an HTTP request matching the trigger's `route`). It can be in one of the following formats: | | +| | | String | * A key in the `component` table | `"user-manager"` | +| | | Table | * Specifies an unnamed component to be associated with the trigger setting. This allows simple components to be written inline instead of needing a separate section. Such a table follows [the `component` table](#the-component-table) format. | { source = "reports.wasm" }` | + +### Additional Fields for `trigger.http` Tables + +| Name | Required? | Type | Value | Example | +|-------------------------|------------|-------------|----------|-----------| +| `route` | Required | String or table | The route which this component handles. Requests to the route will cause the component to execute. | | +| | | String | This may be an exact route (`/example`), which matches only the given path, or may include wildcards (`/example/:id` or `/example/...`). If two routes overlap, requests are directed to the matching route with the longest prefix. See [the HTTP trigger documentation](http-trigger) for details and examples. | `"/api/cart/..."` | +| | | Table | If the component is a private endpoint used for [local service chaining](http-outbound#local-service-chaining) then use the table value shown here. | `{ private = true }`| +| `executor` | Optional | Table | How Spin should invoke the component. If present, this is a table. The `type` key is required and may have the values `"spin"` or `"wagi"`. If omitted. the default is `{ type = "spin"}`. See [the HTTP trigger documentation](http-trigger) for details. | `{ type = "wagi" }` | +| | | | If `type = "spin"` there are no other keys defined. In this case, Spin calls the component using a standard Wasm component interface. Components built using Spin SDKs or Spin interface files use this convention. | `{ type = "spin" }` | +| | | | If `type = "wagi"`, Spin calls the component's "main" (`_start`) function using [a CGI-like interface](https://github.com/deislabs/wagi). Components built using languages or toolchains that do not support Wasm interfaces will need to be called in this way. In this case, the following additional keys may be set:

* `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 to call as the entry point to this handler. By default, it is `_start` (which in most languages translates to `main` in the source code).

See [the HTTP trigger documentation](http-trigger) for details. | `{ type = "wagi" }` | +| `static_response` | Optional | Table | The response Spin should give to requests on this route. A route may not specify both `static_response` and `component`. The response may contain the following fields, all optional:

* `status_code`: The HTTP status code. Default is 200.

* `headers`: Table of header names and values. Default is no headers.

* `body`: The response body. Default is no response body. | { status_code = 404, body = "not found" } | + +### Additional Fields for `trigger.redis` Tables + +| Name | Required? | Type | Value | Example | +|-------------------------|------------|-------------|----------|-----------| +| `address` | Optional | String | The address of the Redis instance to which the trigger subscribes. Use the `redis:` URL scheme. If omitted, defaults to `application.trigger.redis.address`. | `"redis://localhost:6379"` | +| `channel` | Required | String | The Redis channel which this component handles. Messages on this channel will cause the component to execute. | `"purchases"` | + +## The `component` Table + +The keys of the `component` table, usually written as part of the table syntax e.g. `[component.my-component]`, are user-defined. (In the preceding example, the key is `my-component`). Component names must be kebab-cased, i.e. the only permitted separator is a hyphen. + +The value of each key is a table with the following fields. + +| Name | Required? | Type | Value | Example | +|-------------------------|------------|-------------|----------|-----------| +| `description` | Optional | String | A human-readable description of the component. | `"The shopping cart API"` | +| `source` | Required | String or table | The Wasm module which should handle the component. This must be built to work with the application trigger. It can be in one of the following formats: | | +| | | String | * The path to a Wasm file (relative to the manifest file) | `dist/cart.wasm` | +| | | Table | * The URL of a Wasm file downloadable over HTTP. This must be a table containing a `url` field for the Wasm file, and a `digest` field contains a SHA256 hex digest, used to check integrity. | `{ url = "https://example.com/example.wasm", digest = "sha256:6503...2375" }` | +| | | Table (Highly Experimental) | * The registry, package and version of a component from a registry. This experimental source configuration must be a table containing a `registry` domain field, a `package` field and a `version` field. | `{ registry = "registrytest-abcd.fermyon.app", package = "component:hello-world", version="0.0.1" }` or `{ registry = "ttl.sh", package = "user:registrytest", version="1.0.0" }` | +| `files` | Optional | Array of strings and/or tables | The [files to be made available to the Wasm module at runtime](writing-apps#including-files-with-components). This is an array, and each element of the array is either: | `[ "images/*.jpg", { source = "assets/images", destination = "/pictures" } ]` | +| | | String | * A file path or glob pattern, relative to the manifest file. The matching file or files will be available in the Wasm module at the same relative paths. | `"images/*.jpg"` | +| | | Table | * A file or directory to be made available to the Wasm module at a specific path. This must be a table containing a `source` field for the file or directory relative to the manifest file, and a `destination` field containing the absolute path at which to make it available. | `{ source = "assets/images", destination = "/pictures" }` | +| `exclude_files` | Optional | Array of strings | Any files or glob patterns that should _not_ be available to the Wasm module at runtime, even though they match a `files` entry. | `[assets/images/test/**/*.*]` | +| `allowed_http_hosts` | Optional | Array of strings | The host names or addresses to which the Wasm component is allowed to send HTTP requests. This is retained to simplify transition from the [Version 1 manifest](../v3/manifest-reference-v1.md); new applications should use `allow_outbound_hosts` instead. | `["example.com", "localhost:8081"]` | +| `allowed_outbound_hosts` | Optional | Array of strings | The addresses to which the Wasm component is allowed to send network requests. This applies to the outbound HTTP, outbound Redis, MySQL and PostgreSQL APIs. (It does not apply to built-in storage services such as key-value and SQLite.) Each entry must contain both a scheme, a name (or IP address) and a port in `scheme://name:port` format. For known schemes, you may omit the port if it is the default for the scheme. Use `*` for wildcards. If this field is omitted or an empty list, no outbound access is permitted. | `["mysql://db.example.com", "*://example.com:4567", "http://127.0.0.1:*"]` | +| `key_value_stores` | Optional | Array of strings | An array of key-value stores that the Wasm module is allowed to read or write. A store named `default` is provided by the Spin runtime, though modules must still be permitted to access it. In current versions of Spin, `"default"` is the only store allowed. | `["default"]` | +| `environment` | Optional | Table | Environment variables to be set for the Wasm module. This is a table. The table keys are user-defined; the values must be strings. | `{ DB_URL = "mysql://spin:spin@localhost/dev" }` | +| `build` | Optional | Table | The command that `spin build` uses to build this component. See [The `component.(id).build` Table](#the-componentidbuild-table) below. | `[component.cart.build]`
`command = "npm run build"` | +| `variables` | Optional | Table | Application configuration values to be made available to this component. The table keys are user-defined; the values must be strings, and may use template notation as described under [Application Variables](variables#adding-variables-to-your-applications). | `[component.cart.variables]`
`api_base_url = "https://{{ api_host }}/v1"` | +| `targets` | Optional | Array of strings | The environments that the component is expected to be compatible with. The default is the application `targets`. | ["spin-up:3.2"] | +| `dependencies_inherit_configuration` | Optional | Boolean | If true, dependencies can invoke Spin APIs with the same permissions as the main component. If false, dependencies have no permissions (e.g. network, key-value stores, SQLite databases). The default is false. | `false` | +| `dependencies` | Optional | Table | Specifies how to satisfy Wasm Component Model imports of this component. See [Using Component Dependencies](writing-apps.md#using-component-dependencies). | `[component.cart.dependencies]`
`"example:calculator/adder" = { registry = "example.com", package = "example:adding-calculator", version = "1.0.0" }` | +| `profile` | Optional | Table | Overrides corresponding component fields when using a [build profile](build.md#building-with-profiles). Each profile is its own named table. | `[component.cart.profile.debug]`
`source = "debug/cart.was"` | + +## The `component.(id).build` Table + +| Name | Required? | Type | Value | Example | +|-------------------------|------------|-------------|----------|-----------| +| `command` | Required | String | The command to execute on `spin build`. | `"cargo build --target wasm32-wasip2 --release"` | +| `workdir` | Optional | String | The directory in which to execute `command`, relative to the manifest file. The default is the directory containing the manifest file. An example of where this is needed is a multi-component application where each component is its own source tree in its own directory. | `"my-project"` | +| `watch` | Optional | Array of strings | The files or glob patterns which `spin watch` should monitor to determine if the component Wasm file needs to be rebuilt. These are relative to `workdir`, or to the directory containing the manifest file if `workdir` is not present. | `["src/**/*.rs", "Cargo.toml"]` | + +## The `component.(id).profile.(name)` Table + +| Name | Required? | Type | Value | Example | +|-------------------------|------------|-------------|----------|-----------| +| `source` | Optional | String or table | The Wasm module which should handle the component when run with this profile. The same formats are permitted as for `source` in the `component` table. If omitted, defaults to the value in the `component` table. | `"debug/cart.wasm"` | +| `environment` | Optional | Table | Additional environment variables to be set for the Wasm module when run with this profile. This is a table, and is merged with the base `environment` table (with values here taking priority). | `{ TRACE_LEVEL = "full", DB_URL = "mysql://spin:spin@localhost/scratch" }` | +| `dependencies` | Optional | Table | Overrides dependencies when run with this profile. See `dependencies` in the main `component` table. | `[component.cart.profile.debug.dependencies]`
`"example:calculator/adder" = { path = "logging-calculator.wasm" }` | +| `build.command` | Required | String | The command to execute on `spin build --profile (name)`. | `"cargo build --target wasm32-wasip2"` | + +## Next Steps + +- Learn about [writing Spin applications and their manifests](writing-apps) +- Learn about [dynamic and runtime configuration](dynamic-configuration) +- See more information about the [HTTP trigger](http-trigger) +- See more information about the [Redis trigger](redis-trigger) diff --git a/content/v4/mqtt-outbound.md b/content/v4/mqtt-outbound.md new file mode 100644 index 00000000..6c41b3d7 --- /dev/null +++ b/content/v4/mqtt-outbound.md @@ -0,0 +1,114 @@ +title = "MQTT Messaging" +template = "main" +date = "2023-11-04T00:00:01Z" +enable_shortcodes = true +[extra] +url = "https://github.com/spinframework/spin-docs/blob/main/content/v4/mqtt-outbound.md" + +--- +- [Sending MQTT Messages From Applications](#sending-mqtt-messages-from-applications) +- [Granting Network Permissions to Components](#granting-network-permissions-to-components) + - [Configuration-Based Permissions](#configuration-based-permissions) +- [Known Issues](#known-issues) + +Spin provides an experimental interface for you to send messages using the MQTT protocol. + +{{ details "Why do I need a Spin interface? Why can't I just use my language's MQTT library?" "Few MQTT libraries have been updated to work over the WASI 0.2 sockets interface. The Spin interface means Wasm modules can bypass this limitation by asking Spin to make the MQTT connection on their behalf." }} + +> Want to receive MQTT messages? Use [the MQTT trigger](https://github.com/spinkube/spin-trigger-mqtt) to handle messages in your Spin application. + +## Sending MQTT Messages From Applications + +The Spin SDK surfaces the Spin MQTT interface to your language. The set of operations defined in Spin's API is as follows: + +| Operation | Parameters | Returns | Behavior | +|--------------|---------------------|---------|----------| +| `open` | address, username, password, keep-alive | connection resource | Opens a connection to the specified MQTT server. The host must be listed in `allowed_outbound_hosts`. Other operations must be called through a connection. | +| `publish` | topic, payload, QoS | - | Publishes the payload (a binary blob) as a message to the specified topic. | + +The exact detail of calling these operations from your application depends on your language: + +{{ tabs "sdk-type" }} + +{{ startTab "Rust"}} + +> [**Want to go straight to the reference documentation?** Find it here.](https://docs.rs/spin-sdk/latest/spin_sdk/mqtt/index.html) + +MQTT functions are available in the `spin_sdk::mqtt` module. + +To access an MQTT server, use the `Connection::open` function. + +```rust +let connection = spin_sdk::mqtt::Connection::open(&address, &username, &password, keep_alive_secs).await?; +``` + +You can then call the `Connection::publish` function to send MQTT messages: + +```rust +let cat_picture: Vec = request.body().to_vec(); +connection.publish("pets", &cat_picture, spin_sdk::mqtt::Qos::AtLeastOnce).await?; +``` + +For full details of the MQTT API, see the [Spin SDK reference documentation](https://docs.rs/spin-sdk/latest/spin_sdk/mqtt/index.html); + +You can find a complete Rust code example for using outbound MQTT from an HTTP component in the [Spin Rust SDK repository on GitHub](https://github.com/spinframework/spin-rust-sdk/tree/main/examples/mqtt-outbound). + +{{ blockEnd }} + +{{ startTab "TypeScript"}} + +> [**Want to go straight to the reference documentation?** Find it here.](https://spinframework.github.io/spin-js-sdk/modules/_spinframework_spin-mqtt.html) + +To access an MQTT server, use the `open` function. + +```ts +import { open, QoS } from "@spinframework/spin-mqtt"; + +let connection = Mqtt.open(address, username, password, keepAliveSecs); +``` + +You can then call the `publish` method on the connection to send MQTT messages: + +```ts +let catPicture = new Uint8Array(await req.arraybuffer()); +connection.publish("pets", catPicture, QoS.AtleastOnce); +``` + +For full details of the MQTT API, see the [Spin SDK reference documentation](https://spinframework.github.io/spin-js-sdk/modules/_spinframework_spin-mqtt.html) + +You can find a complete Rust code example for using outbound MQTT from an HTTP component in the [Spin Rust SDK repository on GitHub](https://github.com/spinframework/spin-js-sdk/tree/main/examples/spin-host-apis/spin-mqtt). + +{{ blockEnd }} + +{{ startTab "Python"}} + +MQTT is not available in the current version of the Python SDK. + +{{ blockEnd }} + +{{ startTab "TinyGo"}} + +MQTT is not available in the current version of the Go SDK. + +{{ blockEnd }} + +{{ blockEnd }} + +## Granting Network Permissions to Components + +By default, Spin components are not allowed to make outgoing network requests, including MQTT. This follows the general Wasm rule that modules must be explicitly granted capabilities, which is important to sandboxing. To grant a component permission to make network requests to a particular host, use the `allowed_outbound_hosts` field in the component manifest, specifying the host and allowed port: + +```toml +[component.uses-mqtt] +allowed_outbound_hosts = ["mqtt://messaging.example.com:1883"] +``` + +### Configuration-Based Permissions + +You can use [application variables](./variables.md#adding-variables-to-your-applications) in the `allowed_outbound_hosts` field. However, this feature is not yet available on Fermyon Cloud. + +## Known Issues + +The MQTT API is experimental and subject to change. The following issues are known: + +* The MQTT sender interface in the current version of Spin is known to occasionally drop errors, especially if under load. A fix is in progress. diff --git a/content/v4/observing-apps.md b/content/v4/observing-apps.md new file mode 100644 index 00000000..ca87ab9d --- /dev/null +++ b/content/v4/observing-apps.md @@ -0,0 +1,132 @@ +title = "Observing Applications" +template = "main" +date = "2023-11-04T00:00:01Z" +enable_shortcodes = true +[extra] +url = "https://github.com/spinframework/spin-docs/blob/main/content/v4/observing-apps.md" + +--- + +- [Application Logs](#application-logs) +- [OpenTelemetry (OTel)](#opentelemetry-otel) +- [The `otel` Plugin for Spin](#the-otel-plugin-for-spin) + - [Supported Observability Stacks](#supported-observability-stacks) + - [Using the `otel` Plugin for Spin](#using-the-otel-plugin-for-spin) +- [Configuring your own observability stack](#configuring-your-own-observability-stack) + - [Configure the Docker compose stack](#configure-the-docker-compose-stack) + - [Configuring Spin](#configuring-spin) + - [Traces](#traces) + - [Metrics](#metrics) + - [Logs](#logs) + +## Application Logs + +Spin handles application logs by default, storing output and error messages from file system-run applications in the `.spin/logs` directory alongside the manifest file's location. Users have the option to direct logs to a specific folder using the `--log-dir` flag of the `spin up` command. Additionally, if users wish to prevent `stdout` and `stderr` from being written to disk, they can specify an empty string for the `--log-dir` flag, i.e. `spin up --log-dir ""` - effectively disabling log storage. See the [persistent logs](./running-apps#persistent-logs) section for more details. + +## OpenTelemetry (OTel) + +Spin now has support for the [OpenTelemetry (OTel)](https://opentelemetry.io/) observability standard. You can learn more about observability [here](https://opentelemetry.io/docs/concepts/observability-primer/). When configured, Spin will emit telemetry about your Spin App in the form of OTel [signals](https://opentelemetry.io/docs/concepts/signals/): traces, metrics, and logs. + +## The `otel` Plugin for Spin + +We have a plugin that makes it easy to use OpenTelemetry with Spin. If you would like to examine the source code, you can visit the [GitHub repository](https://github.com/fermyon/otel-plugin). Otherwise, follow these instructions: + +- To install the plugin, run the commands below: + + ```sh + spin plugins update + spin plugins install otel + ``` + +### Supported Observability Stacks + +The `otel` plugin for Spin currently supports the following observability stacks: + +- **Default**: A multi-container observability stack based on Prometheus, Loki, Grafana and Jaeger +- **Aspire**: A single-container observability stack using .NET Aspire Standalone Dashboard + +### Using the `otel` Plugin for Spin + +Setting up an observability stack is as easy as executing either `spin otel setup` or `spin otel setup --aspire` in the folder where your Spin app remains. + +The `otel` plugin comes with a handy `spin otel up` command, which you can use to start a Spin app and instruct it to emit telemetry data to your observability stack of choice. + +As your observability stack of choice is executed using traditional containers, you may want to stop them once you don't need them anymore. To do so, run `spin otel cleanup`. + +To see all available commands of the `otel` plugin, you can run `spin otel --help`. + +## Configuring your own observability stack + +Follow this portion of the guide if you want to use Spin and OTel, but want to have more control than what the OTel plugin offers. + +### Configure the Docker compose stack + +In order to view the telemetry data you need to run an OTel compliant [collector](https://opentelemetry.io/docs/collector/) and the proper backends for each signal type. If you have Docker on your system you can easily start all the observability tools you need with the following commands: + +```sh +cd ~ +git clone git@github.com:fermyon/spin.git +cd spin/hack/o11y-stack +docker compose up -d +``` + +This will start the following services: + +- [OTel Collector](https://opentelemetry.io/docs/collector/): Collector to receive OTel signals from Spin and forward to the appropriate backends. +- [Jaeger](https://www.jaegertracing.io/): Backend for traces. +- [Tempo](https://grafana.com/oss/tempo/): Alternative backend for traces. +- [Loki](https://grafana.com/oss/loki/): Backend for logs. +- [Prometheus](https://prometheus.io/): Backend for metrics. +- [Grafana](https://grafana.com/oss/grafana/): Dashboard for viewing data stored in Tempo, Loki, and Prometheus. + +### Configuring Spin + +To have Spin export OTel telemetry to the collector you need to set the following environment variable: + +```sh +OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318 spin up +``` + +This will enable all OTel signals. If you only want to enable specific signals you can set the following environment variables individually: + +- Traces: `OTEL_EXPORTER_OTLP_TRACES_ENDPOINT` +- Metrics: `OTEL_EXPORTER_OTLP_METRICS_ENDPOINT` +- Logs: `OTEL_EXPORTER_OTLP_LOGS_ENDPOINT` + +For example this would enable exporting of traces and metrics: + +```sh +OTEL_EXPORTER_OTLP_TRACES_ENDPOINT=http://localhost:4318/v1/traces OTEL_EXPORTER_OTLP_METRICS_ENDPOINT=http://localhost:4318/v1/metrics spin up +``` + +Storing lots of trace data can get expensive. You may want to sample traces to reduce the amount of data stored. You can set the following environment variable to control the sampling rate: + +```sh +OTEL_TRACES_SAMPLER=traceidratio OTEL_TRACES_SAMPLER_ARG={desired_ratio} OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318 spin up +``` + +Under high request loads Spin will start dropping OTel data. If keeping all of this data is important to you there are [environment variables](https://opentelemetry.io/docs/specs/otel/configuration/sdk-environment-variables/#batch-span-processor) you can set: + +```sh +OTEL_BSP_MAX_CONCURRENT_EXPORTS=4 OTEL_BSP_MAX_QUEUE_SIZE=4096 OTEL_BSP_SCHEDULE_DELAY=1000 OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318 spin up +``` + +Spin supports a wide array of OTel configuration options beyond what we've covered here. You can read more about them [here](https://opentelemetry.io/docs/specs/otel/protocol/exporter/) and [here](https://opentelemetry.io/docs/specs/otel/configuration/sdk-environment-variables/#general-sdk-configuration). + +### Traces + +After sending some requests to your Spin app, navigate to Jaeger [http://localhost:16686](http://localhost:16686) to view the traces. + +![Traces from app](/static/image/jaeger-traces.png) + +Spin supports both inbound and outbound [trace context propagation](https://opentelemetry.io/docs/concepts/context-propagation/). This allows you to include Spin in your distributed traces that span all your services. + +### Metrics + +Navigate to [http://localhost:5050/explore](http://localhost:5050/explore) to view the metrics in Grafana. Make sure to choose the Prometheus data source from the top left dropdown menu. + +### Logs + +Navigate to [http://localhost:5050/explore](http://localhost:5050/explore) to view the logs in Grafana. Make sure to choose the Loki data source from the top left dropdown menu. + +Spin will still emit application logs as described in the [Application Logs](#application-logs) section. However, it will also send the logs to the OTel collector. diff --git a/content/v4/other-languages.md b/content/v4/other-languages.md new file mode 100644 index 00000000..293e3ddc --- /dev/null +++ b/content/v4/other-languages.md @@ -0,0 +1,73 @@ +title = "Building Spin components in other languages" +template = "main" +date = "2023-11-04T00:00:01Z" +[extra] +url = "https://github.com/spinframework/spin-docs/blob/main/content/v4/other-languages.md" + +--- +- [C/C++](#cc) +- [C# and .NET Languages](#c-and-net-languages) +- [Grain](#grain) +- [Ruby](#ruby) +- [Zig](#zig) + +> This document is continuously evolving as we improve language SDKs and add +> more examples on how to build Spin components in various programming languages. + +> See the document on writing [Rust](./rust-components.md) and [Go](./go-components.md) +> components for Spin for detailed guides. + +WebAssembly is becoming [a popular compilation target for programming languages](https://www.fermyon.com/wasm-languages/webassembly-language-support), and as language toolchains add support for the +[WebAssembly component model](https://github.com/WebAssembly/component-model), +building Spin components will also become supported. + +As a general rule: + +- if your language supports the +[WebAssembly component model](https://component-model.bytecodealliance.org/), +you can build Spin components either through an official Spin SDK +(such as [the Spin SDK for Rust](./rust-components.md)), or through using +bindings generators like [`wit-bindgen`](https://github.com/bytecodealliance/wit-bindgen) +(for languages such as C and C++) +- if your language compiles to WASI, but doesn't have support for the component +model, you can build [Spin HTTP components](./http-trigger.md) that use the +Wagi executor β€” for example in languages such as +[Grain](https://github.com/deislabs/hello-wagi-grain) or [Swift](https://github.com/fermyon/wagi-python). +- if your language doesn't currently compile to WASI, there is no way to +build and run Spin components in that programming language + +## C/C++ + +C and C++ are both broadly supported in the WebAssembly ecosystem. WASI/Wagi support means that both can be used to write Spin apps. + +- The [C entry in the Wasm Language Guide](https://www.fermyon.com/wasm-languages/c-lang) has examples. +- The [C++ entry in the Wasm Language Guide](https://www.fermyon.com/wasm-languages/cpp) has specific caveats for writing C++ (like exception handling) +- The [yo-wasm](https://github.com/deislabs/yo-wasm) project makes setting up C easier. + +## C# and .NET Languages + +.NET has experimental support for WASI, so many (if not all) .NET languages, including C# and F#, can be used to write Spin applications. + +- The [C# entry in the Wasm Language Guide](https://www.fermyon.com/wasm-languages/c-sharp) has a full example. + +## Grain + +[Grain](https://grain-lang.org/), a new functional programming language, has WASI/Wagi support and can be used to write Spin apps. + +- The [Grain entry in the Wasm Language Guide](https://www.fermyon.com/wasm-languages/grain) has details +- A simple [Hello World example](https://github.com/deislabs/hello-wagi-grain) shows how to use Grain +- For a production-quality example. the [Wagi Fileserver](https://github.com/deislabs/wagi-fileserver) is written in Grain + +## Ruby + +Upstream [Ruby](https://www.ruby-lang.org/en/) officially supports WebAssembly and WASI, and we here at Fermyon have successfully run Ruby apps in Spin. + +- The [Ruby entry in the Wasm Language Guide](https://www.fermyon.com/wasm-languages/ruby) has the latest information +- [Ruby's 3.2.0 Preview 1 release notes](https://www.ruby-lang.org/en/news/2022/04/03/ruby-3-2-0-preview1-released/) detail WASI support + +## Zig + +Zig is a low-level systems language that has support for Wasm and WASI, and can be used to write Spin apps. + +- The [Zig entry in the Wasm Language Guide](https://www.fermyon.com/wasm-languages/zig) covers the basics +- Zig's [0.4 release notes](https://ziglang.org/download/0.4.0/release-notes.html#WebAssembly-Support) explain WebAssembly support \ No newline at end of file diff --git a/content/v4/plugin-authoring.md b/content/v4/plugin-authoring.md new file mode 100644 index 00000000..ced536f7 --- /dev/null +++ b/content/v4/plugin-authoring.md @@ -0,0 +1,143 @@ +title = "Creating Spin Plugins" +template = "main" +date = "2023-11-04T00:00:01Z" +[extra] +url = "https://github.com/spinframework/spin-docs/blob/main/content/v4/plugin-authoring.md" + +--- +- [What Are Spin Plugins?](#what-are-spin-plugins) +- [How to Find and Use a Spin Plugin](#how-to-find-and-use-a-spin-plugin) +- [Authoring a Spin Plugin](#authoring-a-spin-plugin) + - [Environment Variables Available to the Plugin Executable](#environment-variables-available-to-the-plugin-executable) + - [Packaging a Plugin](#packaging-a-plugin) + - [Creating a Spin Plugin Manifest](#creating-a-spin-plugin-manifest) + - [Installing a Local Plugin](#installing-a-local-plugin) + - [Contributing a Plugin](#contributing-a-plugin) + +Spin plugins add new functionality or subcommands to Spin without modifying the +Spin codebase. They make Spin easily extensible while keeping it lightweight. +Spin plugins can add new triggers to Spin (such as the [example timer +trigger](https://github.com/spinframework/spin/blob/main/examples/spin-timer/trigger-timer.json)), +add deployment integrations (such as +[`kube`](https://github.com/spinframework/spin-plugins/blob/main/manifests/kube/kube.json)), +and more. + +This document will cover what Spin plugins are, how to use a plugin, and how to +create a plugin. + +## What Are Spin Plugins? + +A Spin plugin is an executable that is added to Spin's plugins directory +(`$XDG_DATA_HOME/spin/plugins`) upon a `spin plugins install `. The +plugin is then ready to be used. If the plugin is an extension to the Spin CLI, +it can now be executed directly as a subcommand: `spin `. If the +plugin is a trigger plugin, it will be executed during `spin up` when an app +using that trigger is run. + +While for now plugins are assumed to be executables, in the future, support for +plugging in WebAssembly modules may be desirable. + +## How to Find and Use a Spin Plugin + +Spin maintains a centralized catalogue of available Spin plugins in the [Spin +plugins repository](https://github.com/spinframework/spin-plugins). During plugin +installation, if it does not already exist, Spin fetches the remote catalogue +and creates a local snapshot. To ensure that the local snapshot is up to date, +it is best to run `spin plugins update` before installing any plugins. + +To list available plugins, run `spin plugins search`. Now, decide which plugin to +install. For example, the `kube` plugin, which is needed in order to deploy +applications to [SpinKube](https://www.spinkube.dev/), can be installed by running: + + + +```bash +$ spin plugins install kube +``` + +With the plugin installed, you can now call `spin kube` to run it. + +To upgrade installed plugins to newer versions, run `spin plugin update` to +fetch the latest plugins to the local catalogue and `spin plugin upgrade` to perform the +upgrade on the installed plugins. + +## Authoring a Spin Plugin + +Spin plugins are implemented as a manifest that points to one or more `.tar.gz` archives which contain the plugin executables. So, to create a plugin you must: + +1. Create tar archives of the executables for the platforms you want to support +2. Compose a manifest that describes the plugin and lists the URLs for those tar archives + +### Environment Variables Available to the Plugin Executable + +Your plugin may need to know information about the instance of Spin it's running in. For example, suppose your plugin wants to call `spin build`. The trouble is that you don't know if it's on the user's system PATH. Suppose, further, that your plugin would prefer to call `spin build -c` (to build only a specific component) if it's available but can fall back to `spin build` (to build everything) if it's not. The `-c` option only exists in Spin 1.4 and above, so this optimization requires that you know which version of Spin you're running in. + +To help with this, when a user uses Spin to run your plugin, Spin sets a number of environment variables on the plugin process. Your code can use these environment variables to find out things like the path to the Spin binary and which version of Spin it is. When your plugin runs, the parent Spin process will set these to the right values for the _user's_ instance of Spin. In the example above, when your plugin wants to run `spin build`, it can consult the `SPIN_BIN_PATH` environment variable for the program path, and be confident that the `SPIN_VERSION` environment variable matches the Spin binary at that location. + +The variables Spin sets are: + +| Name | Meaning | Example | +|--------------------|-----------------------------------------------------------------------------------------------------------------------|---------| +| SPIN_BIN_PATH | The path to the Spin executable that the user is running. Use this if your plugin issues commands using the Spin CLI. | /Users/alice/.cargo/bin/spin | +| SPIN_BRANCH | The Git branch from which the Spin executable was built. | main | +| SPIN_BUILD_DATE | The date on which the Spin executable was built, in yyyy-mm-dd format. | 2023-05-15 | +| SPIN_COMMIT_DATE | The date of the Git commit from which the Spin executable was built, in yyyy-mm-dd format. | 2023-05-15 | +| SPIN_COMMIT_SHA | The SHA of the Git commit from which the Spin executable was built. | 49fb11b | +| SPIN_DEBUG | Whether the Spin executable is a debug build. | false | +| SPIN_TARGET_TRIPLE | The processor and operating system for which the Spin executable was built, in Rust target-triple format. | aarch64-apple-darwin | +| SPIN_VERSION | The version of Spin. This can be used to detect features availability, or to determine pre-stable command syntax. | 1.3.0 | +| SPIN_VERSION_MAJOR | The major version of Spin. | 1 | +| SPIN_VERSION_MINOR | The minor version of Spin. | 3 | +| SPIN_VERSION_PRE | The prerelease version string, or empty if this is a released version of Spin. | pre0 | + +> These variables aren't set if the launching Spin instance is version 1.3 or earlier. If you depend on these variables, set the `spinCompatibility` entry in the manifest to require 1.4 or above. + +### Packaging a Plugin + +After creating your plugin executable, package it along with its license as a +`tar.gz` archive. Note that the `name` field in the plugin manifest must match +both the binary and license name. See the [`spin-plugins` +repository +README](https://github.com/spinframework/spin-plugins#spin-plugin-naming-conventions) +for more details on naming conventions. + +Refer to the aptly named [`example` +plugin](https://github.com/spinframework/spin-plugins/tree/main/example) for an +example of how to build a plugin. + +### Creating a Spin Plugin Manifest + +A Spin plugin manifest is a JSON file that conforms to the [a specific JSON +schema](https://github.com/spinframework/spin-plugins/blob/main/json-schema/spin-plugin-manifest-schema-0.1.json). +A manifest defines a plugin’s name, version, license, homepage (i.e. GitHub +repo), compatible Spin version, and gives a short description of the plugin. It +also lists the URLs of the tar archives of the plugin for various operating +systems and platforms. The URL can point to the local path to the file by using +the file scheme `file://`, for example, `file:///tmp/my-plugin.tar.gz`. + +To ensure your plugin manifest is valid, follow the steps in the [`spin-plugins` +repository +README](https://github.com/spinframework/spin-plugins#validating-plugin-schemas). + +### Installing a Local Plugin + +By default, Spin will look in the plugins catalogue for a plugin. However, when +developing and testing a plugin, it is unlikely to be in the the catalogue. For +both installs and upgrades, the `--file` or `--url` flags can be used to point +to specific local or remote plugin manifests. For example, a local manifest +called `practice.json` can be installed and run as follows: + + + +```bash +$ spin plugin install --file practice.json +$ spin practice +``` + +> While developing a plugin, you can [use the `pluginify` plugin to automate packaging and installation](/hub/preview/plugin_spin_pluginify) for testing in the Spin environment. This saves going through the package-manifest-install cycle every time you want to try an update! + +### Contributing a Plugin + +If you think the community would benefit from your newly created plugin, create +a PR to add it to the [Spin plugins +catalogue](https://github.com/spinframework/spin-plugins/tree/main/manifests)! diff --git a/content/v4/python-components.md b/content/v4/python-components.md new file mode 100644 index 00000000..b2809cc8 --- /dev/null +++ b/content/v4/python-components.md @@ -0,0 +1,519 @@ +title = "Building Spin Components in Python" +template = "main" +date = "2023-11-04T00:00:01Z" +[extra] +url = "https://github.com/spinframework/spin-docs/blob/main/content/v4/python-components.md" + +--- +- [Prerequisite](#prerequisite) +- [Spin's Python HTTP Request Handler Template](#spins-python-http-request-handler-template) +- [Creating a New Python Component](#creating-a-new-python-component) + - [System Housekeeping (Use a Virtual Environment)](#system-housekeeping-use-a-virtual-environment) + - [Requirements](#requirements) +- [Structure of a Python Component](#structure-of-a-python-component) +- [A Simple HTTP Components Example](#a-simple-http-components-example) + - [Building and Running the Application](#building-and-running-the-application) +- [A HTTP Request Parsing Example](#a-http-request-parsing-example) + - [Building and Running the Application](#building-and-running-the-application-1) +- [An Outbound HTTP Example](#an-outbound-http-example) + - [Configuring Outbound Requests](#configuring-outbound-requests) + - [Building and Running the Application](#building-and-running-the-application-2) +- [An Outbound Redis Example](#an-outbound-redis-example) + - [Configuring Outbound Redis](#configuring-outbound-redis) + - [Building and Running the Application](#building-and-running-the-application-3) +- [Storing Data in the Spin Key-Value Store](#storing-data-in-the-spin-key-value-store) +- [Storing Data in SQLite](#storing-data-in-sqlite) +- [AI Inferencing From Python Components](#ai-inferencing-from-python-components) +- [Troubleshooting](#troubleshooting) + +With Python being a very popular language, Spin provides support for building components with Python; [using an experimental SDK](https://github.com/spinframework/spin-python-sdk). The development of the Python SDK is continually being worked on to improve user experience and also add new features. + +> This guide assumes you have Spin installed. If this is your first encounter with Spin, please see the [Quick Start](quickstart), which includes information about installing Spin with the Python templates, installing required tools, and creating Python applications. + +> This guide assumes you are familiar with the Python programming language, but if you are just getting started, be sure to check out the official Python documentation and comprehensive language reference. + +[**Want to go straight to the Spin SDK reference documentation?** Find it here.](https://spinframework.github.io/spin-python-sdk/v3) + +## Prerequisite + +Ensure that you have Python 3.10 or later installed on your system. You can check your Python version by running: + +```bash +python3 --version +``` + +If you do not have Python 3.10 or later, you can install it by following the instructions [here](https://www.python.org/downloads/). + +## Spin's Python HTTP Request Handler Template + +Spin's Python HTTP Request Handler Template can be installed from [spin-python-sdk repository](https://github.com/spinframework/spin-python-sdk/tree/main/) using the following command: + + + +```bash +$ spin templates install --git https://github.com/spinframework/spin-python-sdk --update +``` + +The above command will install the `http-py` template and produce an output similar to the following: + + + +```text +Copying remote template source +Installing template http-py... +Installed 1 template(s) + ++---------------------------------------------+ +| Name Description | ++=============================================+ +| http-py HTTP request handler using Python | ++---------------------------------------------+ +``` + +**Please note:** For more information about managing Spin templates, see the [templates guide](./managing-templates). + +## Creating a New Python Component + +A new Python component can be created using the following command: + + + +```bash +$ spin new -t http-py hello-world --accept-defaults +``` + +### System Housekeeping (Use a Virtual Environment) + +Once the component is created, we can change into the `hello-world` directory, create and activate a virtual environment and then install the component's requirements: + + + +```console +$ cd hello-world +``` + +Create a virtual environment directory (we are still inside the Spin app directory): + + + +```console +# python -m venv +$ python3 -m venv venv-dir +``` + +Activate the virtual environment (this command depends on which operating system you are using): + + + +```console +# macOS command to activate +$ source venv-dir/bin/activate +``` + +If you are using Windows, use the following commands: + +```bash +C:\Work> python3 -m venv venv +C:\Work> venv\Scripts\activate +``` + +The `(venv-dir)` will prefix your terminal prompt now: + + + +```console +(venv-dir) user@123-456-7-8 hello-world % +``` + +### Requirements + +The `requirements.txt`, by default, contains the references to the `spin-sdk` and [`componentize-py`](https://github.com/bytecodealliance/componentize-py) packages. These can be installed in your virtual environment using the following command: + + + +```bash +$ pip3 install -r requirements.txt +``` + +## Structure of a Python Component + +The `hello-world` directory structure created by the Spin `http-py` template is shown below: + + + +```text +β”œβ”€β”€ app.py +β”œβ”€β”€ spin.toml +└── requirements.txt +``` + +The `spin.toml` file will look similar to the following: + + + +```toml +spin_manifest_version = 2 + +[application] +name = "hello-world" +version = "0.1.0" +authors = ["Your Name "] +description = "" + +[[trigger.http]] +route = "/..." +component = "hello-world" + +[component.hello-world] +source = "app.wasm" +[component.hello-world.build] +command = "componentize-py -w spin-http componentize app -o app.wasm" +``` + +## A Simple HTTP Components Example + +In Spin, HTTP components are triggered by the occurrence of an HTTP request and must return an HTTP response at the end of their execution. Components can be built in any language that compiles to WASI. If you would like additional information about building HTTP applications you may find [the HTTP trigger page](./http-trigger.md) useful. + +Building a Spin HTTP component using the Python SDK means defining a top-level class named IncomingHandler which inherits from [`IncomingHandler`](https://spinframework.github.io/spin-python-sdk/v3/wit/exports/index.html#spin_sdk.wit.exports.IncomingHandler), overriding the `handle_request` method. Here is an example of the default Python code which the previous `spin new` created for us; a simple example of a request/response: + + + +```python +from spin_sdk.http import IncomingHandler, Request, Response + +class IncomingHandler(IncomingHandler): + def handle_request(self, request: Request) -> Response: + return Response( + 200, + {"content-type": "text/plain"}, + bytes("Hello from Python!", "utf-8") + ) +``` + +The important things to note in the implementation above: + +- the `handle_request` method is the entry point for the Spin component. +- the component returns a `spin_sdk.http.Response`. + +### Building and Running the Application + +All you need to do is run the `spin build` command from within the project's directory; as shown below: + + + +```bash +$ spin build +``` + +Essentially, we have just created a new Spin compatible module which can now be run using the `spin up` command, as shown below: + + + +```bash +$ spin up +``` + +With Spin running our application in our terminal, we can now go ahead (grab a new terminal) and call the Spin application via an HTTP request: + + + +```bash +$ curl -i localhost:3000 + +HTTP/1.1 200 OK +content-type: text/plain +content-length: 25 + +Hello from Python! +``` + +## A HTTP Request Parsing Example + +The following snippet shows how you can access parts of the request e.g. the `request.method` and the `request.body`: + + + +```python +import json +from spin_sdk import http +from spin_sdk.http import Request, Response + +class IncomingHandler(http.IncomingHandler): + def handle_request(self, request: Request) -> Response: + # Access the request.method + if request.method == 'POST': + # Read the request.body as a string + json_str = request.body.decode('utf-8') + # Create a JSON object representation of the request.body + json_object = json.loads(json_str) + # Access a value in the JSON object + name = json_object['name'] + # Print the variable to console logs + print(name) + # Print the type of the variable to console logs + print(type(name)) + # Print the available methods of the variable to the console logs + print(dir(name)) + return Response(200, + {"content-type": "text/plain"}, + bytes(f"Practicing reading the request object", "utf-8")) +``` + +### Building and Running the Application + +All you need to do is run the `spin build --up` command from within the project's directory; as shown below: + + + +```bash +$ spin build --up +``` + +With Spin running our application in our terminal, we can now go ahead (grab a new terminal) and call the Spin application via an HTTP request: + + + +```bash +$ curl --header "Content-Type: application/json" \ + --request POST \ + --data '{"name":"Python"}' \ + http://localhost:3000/ + +HTTP/1.1 200 OK +content-type: text/plain +content-length: 37 +date: Mon, 15 Apr 2024 04:26:00 GMT + +Practicing reading the request object +``` + +The response "Practicing reading the request object" is returned as expected. In addition, if we check the terminal where Spin is running, we will see that the console logs printed the following: + +The value of the variable called `name`: + + + +```bash +Python +``` + +The `name` variable type (in this case a Python string): + + + +```bash + +``` + +The methods available to that type: + + + +```bash +['__add__', '__class__', '__contains__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', +... abbreviated ... +'rstrip', 'split', 'splitlines', 'startswith', 'strip', 'swapcase', 'title', 'translate', 'upper', 'zfill'] +``` + +> **Please note:** All examples from this documentation page can be found in [the Python SDK repository on GitHub](https://github.com/spinframework/spin-python-sdk/tree/main/examples). If you are following along with these examples and don't get the desired result perhaps compare your own code with our previously built examples (mentioned above). Also please feel free to reach out on [Spin CNCF Slack channel](https://cloud-native.slack.com/archives/C089NJ9G1V0) if you have any questions or need any additional support. + +## An Outbound HTTP Example + +This next example will create an outbound request, to obtain a random fact about animals, which will be returned to the calling code. If you would like to try this out, you can go ahead and update your existing `app.py` file from the previous step; using the following source code: + + + +```python +from spin_sdk import http +from spin_sdk.http import Request, Response, send + +class IncomingHandler(http.IncomingHandler): + def handle_request(self, request: Request) -> Response: + resp = send(Request("GET", "https://random-data-api.fermyon.app/animals/json", {}, None)) + + return Response(200, + {"content-type": "text/plain"}, + bytes(f"Here is an animal fact: {str(resp.body, 'utf-8')}", "utf-8")) + +``` + +### Configuring Outbound Requests + +The Spin framework protects your code from making outbound requests to just any URL. For example, if we try to run the above code **without any additional configuration**, we will correctly get the following error `AssertionError: HttpError::DestinationNotAllowed`. To allow our component to request the `random-data-api.fermyon.app` domain, all we have to do is add that domain to the specific component of the application that is making the request. Here is an example of an updated `spin.toml` file where we have added `allowed_outbound_hosts`: + + + +```toml +spin_manifest_version = 2 + +[application] +name = "hello-world" +version = "0.1.0" +authors = ["Your Name "] +description = "" + +[[trigger.http]] +route = "/..." +component = "hello-world" + +[component.hello-world] +source = "app.wasm" +allowed_outbound_hosts = ["https://random-data-api.fermyon.app"] +[component.hello-world.build] +command = "componentize-py -w spin-http componentize app -o app.wasm" +watch = ["*.py", "requirements.txt"] +``` + +### Building and Running the Application + +Run the `spin build --up` command from within the project's directory; as shown below: + + + +```bash +$ spin build --up +``` + +With Spin running our application in our terminal, we can now go ahead (grab a new terminal) and call the Spin application via an HTTP request: + + + +```bash +$ curl -i localhost:3000 +HTTP/1.1 200 OK +content-type: text/plain +content-length: 99 +date: Mon, 15 Apr 2024 04:52:45 GMT + +Here is an animal fact: {"timestamp":1713156765221,"fact":"Bats are the only mammals that can fly"} +``` + +## An Outbound Redis Example + +In this final example, we talk to an existing Redis instance. You can find the official [instructions on how to install Redis here](https://redis.io/docs/latest/operate/oss_and_stack/install/install-stack/). We also gave a quick run-through on setting up Redis with Spin in our previous article called [Persistent Storage in Webassembly Applications](https://www.fermyon.com/blog/persistent-storage-in-webassembly-applications), so please take a look at that blog if you need a hand. + +### Configuring Outbound Redis + +After installing Redis on localhost, we add two entries to the `spin.toml` file: + +* `variables = { redis_address = "redis://127.0.0.1:6379" }` externalizes the URL of the server to access +* `allowed_outbound_hosts = ["redis://127.0.0.1:6379"]` enables network access to the host and port where Redis is running + + + +```toml +spin_manifest_version = 2 + +[application] +name = "hello-world" +version = "0.1.0" +authors = ["Your Name "] +description = "" + +[[trigger.http]] +route = "/..." +component = "hello-world" + +[component.hello-world] +id = "hello-world" +source = "app.wasm" +variables = { redis_address = "redis://127.0.0.1:6379" } +allowed_outbound_hosts = ["redis://127.0.0.1:6379"] +[component.hello-world.build] +command = "spin py2wasm app -o app.wasm" +``` + +If you are still following along, please go ahead and update your `app.py` file one more time, as follows: + + + +```python +from spin_sdk import http, redis, variables +from spin_sdk.http import Request, Response + +class IncomingHandler(http.IncomingHandler): + def handle_request(self, request: Request) -> Response: + with redis.open(variables.get("redis_address")) as db: + db.set("foo", b"bar") + value = db.get("foo") + db.incr("testIncr") + db.sadd("testSets", ["hello", "world"]) + content = db.smembers("testSets") + db.srem("testSets", ["hello"]) + assert value == b"bar", f"expected \"bar\", got \"{str(value, 'utf-8')}\"" + + return Response(200, + {"content-type": "text/plain"}, + bytes(f"Executed outbound Redis commands: {request.uri}", "utf-8")) +``` + +### Building and Running the Application + +Run the `spin build --up` command from within the project's directory; as shown below: + + + +```bash +$ spin build --up +``` + +In a new terminal, make the request via the curl command, as shown below: + + + +```bash +$ curl -i localhost:3000 +HTTP/1.1 200 OK +content-type: text/plain +content-length: 35 +date: Mon, 15 Apr 2024 05:53:17 GMT + +Executed outbound Redis commands: / +``` + +If we go into our Redis CLI on localhost we can see that the value `foo` which was set in the Python source code ( `redis_set(redis_address, "foo", b"bar")` ) is now correctly set to the value of `bar`: + + + +```bash +redis-cli +127.0.0.1:6379> get foo +"bar" +``` + +## Storing Data in the Spin Key-Value Store + +Spin has a key-value store built in. For information about using it from Python, see [the key-value store API guide](kv-store-api-guide). + +## Storing Data in SQLite + +For more information about using SQLite from Python, see [SQLite storage](sqlite-api-guide). + +## AI Inferencing From Python Components + +For more information about using Serverless AI from Python, see the [Serverless AI](serverless-ai-api-guide) API guide. + +## Troubleshooting + +If you bump into issues when installing the requirements.txt. For example: + + + +```console +error: externally-managed-environment +Γ— This environment is externally managed +``` + +Please note, this error is specific to Homebrew-installed Python installations and occurs because installing a **non-brew-packaged** Python package requires you to either: +- create a virtual environment using `python3 -m venv path/to/venv`, or +- use the `--break-system-packages` option in your `pip3 install` command i.e. `pip3 install -r requirements.txt --break-system-packages` + +We recommend installing a virtual environment using `venv`, as shown in the [system housekeeping section](#system-housekeeping-use-a-virtual-environment) above. + +For all Python examples, please ensure that you have Python 3.10 or later installed on your system. You can check your Python version by running: + +```bash +python3 --version +``` + +If you do not have Python 3.10 or later, you can install it by following the instructions [here](https://www.python.org/downloads/). diff --git a/content/v4/quickstart.md b/content/v4/quickstart.md new file mode 100644 index 00000000..13df9495 --- /dev/null +++ b/content/v4/quickstart.md @@ -0,0 +1,888 @@ +title = "Taking Spin for a spin" +template = "main" +date = "2023-11-04T00:00:01Z" +enable_shortcodes = true +[extra] +url = "https://github.com/spinframework/spin-docs/blob/main/content/v4/quickstart.md" +keywords = "quickstart" + +--- +- [Install Spin](#install-spin) +- [Install the Prerequisites](#install-the-prerequisites) + - [Install a Template](#install-a-template) + - [Install the Tools](#install-the-tools) +- [Create Your First Application](#create-your-first-application) +- [Structure of a Python Component](#structure-of-a-python-component) +- [Build Your Application](#build-your-application) +- [Run Your Application](#run-your-application) +- [Next Steps](#next-steps) + +Let's get Spin and take it from nothing to a "hello world" application! + + + +## Install Spin + +{{ tabs "os" }} + +{{ startTab "Linux"}} + +Download the `spin` binary along with a starter set of templates and plugins using the `install.sh` script hosted on this site: + + + +
$ curl -fsSL https://spinframework.dev/downloads/install.sh | bash
+
+ +Then move the `spin` binary somewhere in your path, so you can run it from anywhere. For example: + + + +```bash +$ sudo mv ./spin /usr/local/bin/spin +``` + +{{ blockEnd }} + +{{ startTab "macOS"}} + +Download the `spin` binary along with a starter set of templates and plugins using the `install.sh` script hosted on this site: + + + +
$ curl -fsSL https://spinframework.dev/downloads/install.sh | bash
+
+ +Then move the `spin` binary somewhere in your path, so you can run it from anywhere. For example: + + + +```bash +$ sudo mv ./spin /usr/local/bin/spin +``` + +{{ blockEnd }} + +{{ startTab "Windows"}} + +Download the Windows binary release of Spin from GitHub. + +Unzip the binary release and place the `spin.exe` in your system path. + +{{ blockEnd }} +{{ blockEnd }} + +[See more options for installing Spin.](install) + +## Install the Prerequisites + +### Install a Template + +> If you used the installer script above, the templates are already installed, and you can skip this section! + +The quickest and most convenient way to start a new application is to install and use a Spin template for your preferred language. + +{{ tabs "sdk-type" }} + +{{ startTab "Rust"}} + + + +```bash +$ spin templates install --git https://github.com/spinframework/spin --update +Copying remote template source +Installing template redis-rust... +Installing template http-rust... +... other templates omitted ... ++------------------------------------------------------------------------+ +| Name Description | ++========================================================================+ +| ... other templates omitted ... | +| http-rust HTTP request handler using Rust | +| redis-rust Redis message handler using Rust | +| ... other templates omitted ... | ++------------------------------------------------------------------------+ +``` + +Note: The Rust templates are in a repo that contains several other languages; they will all be installed together. + +{{ blockEnd }} + +{{ startTab "TypeScript" }} + + + +```bash +$ spin templates install --git https://github.com/spinframework/spin-js-sdk --update +Copying remote template source +Installing template http-js... +Installing template http-ts... ++------------------------------------------------------------------------+ +| Name Description | ++========================================================================+ +| http-js HTTP request handler using Javascript | +| http-ts HTTP request handler using Typescript | ++------------------------------------------------------------------------+ +``` + +{{ blockEnd }} + +{{ startTab "Python" }} + + + +```bash +$ spin templates install --git https://github.com/spinframework/spin-python-sdk --update +Copying remote template source +Installing template http-py... ++---------------------------------------------+ +| Name Description | ++=============================================+ +| http-py HTTP request handler using Python | ++---------------------------------------------+ +``` + +{{ blockEnd }} + +{{ startTab "TinyGo" }} + + + +```bash +$ spin templates install --git https://github.com/spinframework/spin --update +Copying remote template source +Installing template redis-go... +Installing template http-go... ++------------------------------------------------------------------------+ +| Name Description | ++========================================================================+ +| ... other templates omitted ... | +| http-go HTTP request handler using (Tiny)Go | +| redis-go Redis message handler using (Tiny)Go | +| ... other templates omitted ... | ++------------------------------------------------------------------------+ +``` + +Note: The Go templates are in a repo that contains several other languages; they will all be installed together. + +{{ blockEnd }} + +{{ blockEnd }} + +### Install the Tools + +Some languages require additional tool support for Wasm: + +{{ tabs "sdk-type" }} + +{{ startTab "Rust"}} + +You'll need the `wasm32-wasip2` target for Rust: + + + +```bash +$ rustup target add wasm32-wasip2 +``` + +[Learn more in the language guide.](rust-components) + +{{ blockEnd }} + +{{ startTab "TypeScript" }} + +You will need `npm` installed and on the path. `npm` will install any additional build tools as part of building the application. + +[Learn more in the language guide.](javascript-components) + +{{ blockEnd }} + +{{ startTab "Python" }} + +Ensure that you have Python 3.10 or later installed on your system. You can check your Python version by running: + +```bash +python3 --version +``` + +If you do not have Python 3.10 or later, you can install it by following the instructions [here](https://www.python.org/downloads/). + +You'll install all the required Python tools as part of building the application. We'll cover that in the Build Your Application section below. For now, there's nothing to do here! + +[Learn more in the language guide.](python-components) + +{{ blockEnd }} + +{{ startTab "TinyGo" }} + +You'll need the TinyGo compiler, as the standard Go compiler does not yet support WASI exports. See the [TinyGo installation guide](https://tinygo.org/getting-started/install/). + +[Learn more in the language guide.](go-components) + +{{ blockEnd }} + +{{ blockEnd }} + +## Create Your First Application + +{{suh_cards}} +{{card_element "sample" "Checklist Sample App" "A checklist app that persists data in a key value store" "/hub/preview/sample_checklist" "Typescript,Http,Kv" true }} +{{card_element "sample" "AI-assisted News Summarizer" "Read an RSS newsfeed and have AI summarize it for you" "/hub/preview/sample_newsreader_ai" "Typescript,Javascript,Ai" true }} +{{card_element "template" "Zola SSG Template" "A template for using Zola framework to create a static webpage" "/hub/preview/template_zola_ssg" "rust" true }} +{{blockEnd}} + +Now you are ready to create your first Spin application: + +{{ tabs "sdk-type" }} + +{{ startTab "Rust"}} + +Use the `spin new` command and the `http-rust` template to scaffold a new Spin application: + + + +```bash +$ spin new +Pick a template to start your application with: + http-c (HTTP request handler using C and the Zig toolchain) + http-csharp (HTTP request handler using C# (EXPERIMENTAL)) + http-go (HTTP request handler using (Tiny)Go) + http-grain (HTTP request handler using Grain) +> http-rust (HTTP request handler using Rust) + http-swift (HTTP request handler using SwiftWasm) + http-zig (HTTP request handler using Zig) + redis-go (Redis message handler using (Tiny)Go) + redis-rust (Redis message handler using Rust) + +Enter a name for your new application: hello_rust +Project description: My first Rust Spin application +HTTP path: /... +``` + +This command created a directory with the necessary files needed to build and run a Rust Spin application. Change to that directory, and look at the files. It looks very much like a normal Rust library project: + + + +```bash +$ cd hello_rust +$ tree +. +β”œβ”€β”€ .gitignore +β”œβ”€β”€ Cargo.toml +β”œβ”€β”€ spin.toml +└── src + └── lib.rs +``` + +The additional `spin.toml` file is the manifest file, which tells Spin what events should trigger what components. In this case our trigger is HTTP, for a Web application, and we have only one component, at the route `/...`. This is a wildcard that matches any route: + + + +```toml +spin_manifest_version = 2 + +[application] +name = "hello_rust" +version = "0.1.0" +authors = ["Your Name "] +description = "My first Rust Spin application" + +[[trigger.http]] +route = "/..." +component = "hello-rust" + +[component.hello-rust] +source = "target/wasm32-wasip2/release/hello_rust.wasm" +allowed_outbound_hosts = [] +[component.hello-rust.build] +command = "cargo build --target wasm32-wasip2 --release" +watch = ["src/**/*.rs", "Cargo.toml"] +``` + +This represents a simple Spin HTTP application (triggered by an HTTP request). It has: + +* A single HTTP trigger, for the `/...` route, associated with the `hello-rust` component. `/...` is a wildcard, meaning it will match any route. When the application gets an HTTP request that matches this route - that is, any HTTP request at all! - Spin will run the `hello-rust` component. +* A single component called `hello-rust`, whose implementation is in the associated `hello_rust.wasm` WebAssembly component. When, in response to the HTTP trigger, Spin runs this component, it will execute the HTTP handler in `hello_rust.wasm`. (We're about to see the source code for that.) + +[Learn more about the manifest here.](./writing-apps) + +Now let's have a look at the code. Below is the complete source +code for a Spin HTTP component written in Rust β€” a regular Rust function that +takes an HTTP request as a parameter and returns an HTTP response, and it is +annotated with the `http_service` macro which identifies it as the entry point +for HTTP requests: + +```rust +use spin_sdk::http::{IntoResponse, Request, Response}; +use spin_sdk::http_service; + +/// A simple Spin HTTP component. +#[http_service] +async fn handle_hello_rust(req: Request) -> anyhow::Result { + println!("Handling request to {:?}", req.headers().get("spin-full-url")); + Ok(Response::builder() + .status(200) + .header("content-type", "text/plain") + .body("Hello World!".to_string())?) +} +``` + +{{ blockEnd }} + +{{ startTab "TypeScript"}} + +Use the `spin new` command and the `http-ts` template to scaffold a new Spin application. (If you prefer JavaScript to TypeScript, the `http-js` template is very similar.): + + + +```bash +$ spin new +Pick a template to start your application with: + http-js (HTTP request handler using Javascript) +> http-ts (HTTP request handler using Typescript) +Enter a name for your new application: hello_typescript +Project description: My first TypeScript Spin application +HTTP path: /... +``` + +This command created a directory with the necessary files needed to build and run a TypeScript Spin application. Change to that directory, and look at the files. It looks similar to a lot of NPM projects: + + + +```bash +$ cd hello_typescript +$ tree +. +β”œβ”€β”€ config +β”‚ └── knitwit.json +β”œβ”€β”€ package.json +β”œβ”€β”€ spin.toml +β”œβ”€β”€ src +β”‚ └── index.ts +β”œβ”€β”€ tsconfig.json +└── webpack.config.js +``` + +The additional `spin.toml` file is the manifest file, which tells Spin what events should trigger what components. In this case our trigger is HTTP, for a Web application, and we have only one component, at the route `/...`. This is a wildcard that matches any route: + + + +```toml +spin_manifest_version = 2 + +[application] +name = "hello_typescript" +version = "0.1.0" +authors = ["Your Name "] +description = "My first TypeScript Spin application" + +[[trigger.http]] +route = "/..." +component = "hello-typescript" + +[component.hello-typescript] +source = "dist/hello-typescript.wasm" +exclude_files = ["**/node_modules"] +[component.hello-typescript.build] +command = "npm run build" +``` + +This represents a simple Spin HTTP application (triggered by an HTTP request). It has: + +* A single HTTP trigger, for the `/...` route, associated with the `hello-typescript` component. `/...` is a wildcard, meaning it will match any route. When the application gets an HTTP request that matches this route - that is, any HTTP request at all! - Spin will run the `hello-typescript` component. +* A single component called `hello-typescript`, whose implementation is in the associated `hello-typescript.wasm` WebAssembly component. When, in response to the HTTP trigger, Spin runs this component, it will execute the HTTP handler in `hello-typescript.wasm`. (We're about to see the source code for that.) + +[Learn more about the manifest here.](./writing-apps) + +Now let's have a look at the code. Below is the complete source +code for a Spin HTTP component written in TypeScript β€” A function is attached to the fetch event listener which receives and responds to the HTTP request. + +```javascript +❯ cat hello-world/src/index.ts +import { AutoRouter } from 'itty-router'; + +let router = AutoRouter(); + +router + .get("/", () => new Response("hello universe")) + .get('/hello/:name', ({ name }) => `Hello, ${name}!`) + +//@ts-ignore +addEventListener('fetch', async (event: FetchEvent) => { + event.respondWith(router.fetch(event.request)); +}); +``` + +{{ blockEnd }} + +{{ startTab "Python"}} + +You can install the Spin template for Python HTTP handlers from the [spin-python-sdk repository](https://github.com/spinframework/spin-python-sdk) using the following command: + + + +```bash +$ spin templates install --git https://github.com/spinframework/spin-python-sdk --update +``` + +The above command will install the `http-py` template and produce an output similar to the following: + + + +```text +Copying remote template source +Installing template http-py... +Installed 1 template(s) + ++---------------------------------------------+ +| Name Description | ++=============================================+ +| http-py HTTP request handler using Python | ++---------------------------------------------+ +``` + +**Please note:** For more information about managing Spin templates, see the [templates guide](./managing-templates). + +This command created a directory with the necessary files needed to build and run a Python Spin application. Change to that directory, and look at the files. It contains a minimal Python application: + + + +```bash +$ spin new -t http-py hello-python --accept-defaults +``` + +Once the component is created, we can change into the `hello-python` directory, create and activate a virtual environment and then install the component's requirements: + + + +```console +$ cd hello-python +``` + +Create a virtual environment directory (we are still inside the Spin app directory): + + + +```console +# python -m venv +$ python3 -m venv venv-dir +``` + +Activate the virtual environment (this command depends on which operating system you are using): + + + +```console +# macOS & Linux command to activate +$ source venv-dir/bin/activate +``` + +If you are using Windows, use the following commands: + +```bash +C:\Work> python3 -m venv venv +C:\Work> venv\Scripts\activate +``` + +The `(venv-dir)` will prefix your terminal prompt now: + + + +```console +(venv-dir) user@123-456-7-8 hello-python % +``` + +The `requirements.txt`, by default, contains the references to the `spin-sdk` and `componentize-py` packages. These can be installed in your virtual environment using the following command: + + + +```bash +$ pip3 install -r requirements.txt +Collecting spin-sdk==3.1.0 (from -r requirements.txt (line 1)) + Using cached spin_sdk-3.1.0-py3-none-any.whl.metadata (16 kB) +Collecting componentize-py==0.13.3 (from -r requirements.txt (line 2)) + Using cached componentize_py-0.13.3-cp37-abi3-macosx_10_12_x86_64.whl.metadata (3.4 kB) +Using cached spin_sdk-3.1.0-py3-none-any.whl (94 kB) +Using cached componentize_py-0.13.3-cp37-abi3-macosx_10_12_x86_64.whl (38.8 MB) +Installing collected packages: spin-sdk, componentize-py +Successfully installed componentize-py-0.13.3 spin-sdk-3.1.0 +``` + +## Structure of a Python Component + +The `hello-python` directory structure created by the Spin `http-py` template is shown below: + + + +```text +β”œβ”€β”€ app.py +β”œβ”€β”€ spin.toml +└── requirements.txt +``` + +The additional `spin.toml` file is the manifest file, which tells Spin what events should trigger what components. In this case our trigger is HTTP, for a Web application, and we have only one component, at the route `/...`. This is a wildcard that matches any route. + + + +```toml +spin_manifest_version = 2 + +[application] +name = "hello-python" +version = "0.1.0" +authors = ["Your Name "] +description = "My first Python Spin application" + +[[trigger.http]] +route = "/..." +component = "hello-python" + +[component.hello-python] +source = "app.wasm" +[component.hello-python.build] +command = "componentize-py -w spin-http componentize app -o app.wasm" +``` +This represents a simple Spin HTTP application (triggered by an HTTP request). It has: + +* A single HTTP trigger, for the `/...` route, associated with the `hello-python` component. `/...` is a wildcard, meaning it will match any route. When the application gets an HTTP request that matches this route - that is, any HTTP request at all! - Spin will run the `hello-python` component. +* A single component called `hello-python`, whose implementation is in the associated `app.wasm` WebAssembly component. When, in response to the HTTP trigger, Spin runs this component, it will execute the HTTP handler in `app.wasm`. (We're about to see the source code for that.) + +[Learn more about the manifest here.](./writing-apps) + +Now let's have a look at the code. Below is the complete source +code for a Spin HTTP component written in Python β€” a regular function named `handle_request` that +takes an HTTP request as a parameter and returns an HTTP response. + + + +```python +from spin_sdk.http import IncomingHandler, Request, Response + +class IncomingHandler(IncomingHandler): + def handle_request(self, request: Request) -> Response: + return Response( + 200, + {"content-type": "text/plain"}, + bytes("Hello from the Python SDK!", "utf-8") + ) +``` + +{{ blockEnd }} + +{{ startTab "TinyGo"}} + +Use the `spin new` command and the `http-go` template to scaffold a new Spin application. + + + +```bash +$ spin new +Pick a template to start your application with: + http-c (HTTP request handler using C and the Zig toolchain) + http-empty (HTTP application with no components) +> http-go (HTTP request handler using (Tiny)Go) + http-grain (HTTP request handler using Grain) + http-php (HTTP request handler using PHP) + http-rust (HTTP request handler using Rust) +Enter a name for your new application: hello_go +Description: My first Go Spin application +HTTP path: /... +``` + +This command created a directory with the necessary files needed to build and run a Go Spin application using the TinyGo compiler. Change to that directory, and look at the files. It looks very much like a normal Go project: + + + +```bash +$ cd hello_go +$ tree +. +β”œβ”€β”€ go.mod +β”œβ”€β”€ go.sum +β”œβ”€β”€ main.go +└── spin.toml +``` + +The additional `spin.toml` file is the manifest file, which tells Spin what events should trigger what components. In this case our trigger is HTTP, for a Web application, and we have only one component, at the route `/...`. This is a wildcard that matches any route. + + + +```toml +spin_manifest_version = 2 + +[application] +name = "hello_go" +version = "0.1.0" +authors = ["Your Name "] +description = "My first Go Spin application" + +[[trigger.http]] +route = "/..." +component = "hello-go" + +[component.hello-go] +source = "main.wasm" +allowed_outbound_hosts = [] +[component.hello-go.build] +command = "tinygo build -target=wasip1 -gc=leaking -buildmode=c-shared -no-debug -o main.wasm ." +``` + +This represents a simple Spin HTTP application (triggered by an HTTP request). It has: + +* A single HTTP trigger, for the `/...` route, associated with the `hello-go` component. `/...` is a wildcard, meaning it will match any route. When the application gets an HTTP request that matches this route - that is, any HTTP request at all! - Spin will run the `hello-go` component. +* A single component called `hello-go`, whose implementation is in the associated `main.wasm` WebAssembly component. When, in response to the HTTP trigger, Spin runs this component, it will execute the HTTP handler in `main.wasm`. (We're about to see the source code for that.) + +[Learn more about the manifest here.](./writing-apps) + +Now let's have a look at the code. Below is the complete source +code for a Spin HTTP component written in Go. Notice where the work is done. Because this is a component +rather than an application, there is no `main` function. Instead, the `init` function +sets up a callback, and passes that callback to `spinhttp.Handle` to register it as +the handler for HTTP requests. You can learn more about this structure +in the [Go language guide](go-components). + +```go +package main + +import ( + "fmt" + "net/http" + + spinhttp "github.com/spinframework/spin-go-sdk/v2/http" +) + +func init() { + spinhttp.Handle(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "text/plain") + fmt.Fprintln(w, "Hello Fermyon!") + }) +} +``` + +{{ blockEnd }} + +{{ blockEnd }} + +## Build Your Application + +The Spin template creates starter source code. Now you need to turn that into a Wasm module. The template puts build instructions for each component into the manifest. Use the `spin build` command to run them: + +{{ tabs "sdk-type" }} + +{{ startTab "Rust"}} + +```bash +$ spin build +Executing the build command for component hello-rust: cargo build --target wasm32-wasip2 --release + Updating crates.io index + Updating git repository `https://github.com/spinframework/spin` + Updating git repository `https://github.com/bytecodealliance/wit-bindgen` + Compiling anyhow v1.0.69 + Compiling version_check v0.9.4 + # ... + Compiling spin-sdk v6.0.0 + Compiling hello-rust v0.1.0 (/home/ivan/testing/start/hello_rust) + Finished release [optimized] target(s) in 11.94s +Finished building all Spin components +``` + +If the build fails, check: + +* Are you in the `hello_rust` directory? +* Did you successfully [install the `wasm32-wasip2` target](#install-the-tools)? +* Is your version of Rust up to date (`cargo --version`)? The Spin SDK needs Rust 1.91 or above. + +If you would like to know what build command Spin runs for a component, you can find it in the manifest, in the `component.(id).build` section: + +```toml +[component.hello-rust.build] +command = "cargo build --target wasm32-wasip2 --release" +``` + +You can always run this command manually; `spin build` is a shortcut to save you having to remember it. + +{{ blockEnd }} + +{{ startTab "TypeScript"}} + +As normal for NPM projects, before you build for the first time, you must run `npm install`: + + + +```bash +$ npm install + +added 141 packages, and audited 142 packages in 13s + +20 packages are looking for funding + run `npm fund` for details + +found 0 vulnerabilities +``` + +Then run `spin build`: + + + +```bash +$ spin build +Executing the build command for component hello-typescript: npm run build + +> hello-typescript@1.0.0 build +> knitwit --out-dir build/wit/knitwit --out-world combined && npx webpack --mode=production && npx mkdirp target && npx j2w -i dist.js -d combined-wit -n combined -o target/hello-typescript.wasm + +asset spin.js 4.57 KiB [emitted] (name: main) +runtime modules 670 bytes 3 modules +./src/index.ts 2.85 KiB [built] [code generated] +webpack 5.75.0 compiled successfully in 1026 ms + +Finished building all Spin components +``` + +If you would like to know what build command Spin runs for a component, you can find it in the manifest, in the `component.(id).build` section: + +```toml +[component.hello-typescript.build] +command = ["npm install", "npm run build"] +``` + +You can always run this command manually; `spin build` is a shortcut. + +{{ blockEnd }} + +{{ startTab "Python"}} + +As a standard practice for Python, create and activate a virtual env: + +If you are on a Mac/Linux based operating system use the following commands: + +```bash +$ python3 -m venv venv +$ source venv/bin/activate +``` + +If you are using Windows, use the following commands: + +```bash +C:\Work> python3 -m venv venv +C:\Work> venv\Scripts\activate +``` + +Install `componentize-py` and `spin-sdk` packages + + + +```bash +$ pip3 install -r requirements.txt +``` + +Then run: + + + +```bash +$ spin build +Executing the build command for component hello-python: "componentize-py -w spin-http componentize app -o app.wasm" +Finished building all Spin components +``` + +If the build fails, check: + +* Are you in the `hello-python` directory? +* Did you install the requirements? +* Is your virtual environment still activated? + +If you would like to know what build command Spin runs for a component, you can find it in the manifest, in the `component.(id).build` section: + +```toml +[component.hello-python.build] +command = "componentize-py -w spin-http componentize app -o app.wasm" +``` + +You can always run this command manually; `spin build` is a shortcut. + +{{ blockEnd }} + +{{ startTab "TinyGo"}} + + + +```bash +$ spin build +Executing the build command for component hello-go: tinygo build -target=wasip1 -gc=leaking -buildmode=c-shared -no-debug -o main.wasm . +go: downloading github.com/fermyon/spin/sdk/go v0.10.0 +Finished building all Spin components +``` + +If the build fails, check: + +* Are you in the `hello_go` directory? +* Did you successfully [install TinyGo](#install-the-tools)? +* Are your versions of Go and TinyGo up to date? The Spin SDK needs TinyGo 0.35 or above and Go 1.22 or above. +* Set Environment Variable `CGO_ENABLED=1`. (Since the Go SDK is built using CGO, it requires the CGO_ENABLED=1 environment variable to be set.) + +If you would like to know what build command Spin runs for a component, you can find it in the manifest, in the `component.(id).build` section: + +```toml +[component.hello-go.build] +command = "tinygo build -target=wasip1 -gc=leaking -buildmode=c-shared -no-debug -o main.wasm ." +``` + +You can always run this command manually; `spin build` is a shortcut to save you having to remember it. + +{{ blockEnd }} + +{{ blockEnd }} + +> `spin build` can be used to build all components defined in the Spin manifest +> file at the same time, and also has a flag that starts the application after +> finishing the compilation, `spin build --up`. +> +> For more details, see the [page about building Spin applications](./build.md). + +## Run Your Application + +Now that you have created the application and built the component, you can _spin up_ +the application (pun intended): + + + +```bash +$ spin up +Serving http://127.0.0.1:3000 +Available Routes: + hello-typescript: http://127.0.0.1:3000 (wildcard) +``` + +> If another program is using port 3000, add the `--listen` flag. E.g. `spin up --listen 127.0.0.1:12345`. + +> You can also run a Spin application using `spin watch`. This automatically rebuilds code and reloads content whenever they change. [Learn more.](./running-apps.md#monitoring-applications-for-changes) + +Spin instantiates all components from the application manifest, and +creates the router configuration for the HTTP trigger according to the routes in the manifest. The +component can now be invoked by making requests to `http://localhost:3000/` +(or any path under that, since it's a wildcard): + + + +```bash +$ curl -i localhost:3000 +HTTP/1.1 200 OK +content-type: text/plain +content-length: 14 +date = "2023-11-04T00:00:01Z" + +Hello, Spin +``` + +> The `curl` output may vary based on which language SDK you use. + +You'll also see any logging (stdout/stderr) from the generated code printed to the console where Spin is running. For more details, see the [page about running Spin applications](./running-apps.md). + +Congratulations! You just created, built and ran your first Spin application! + +## Next Steps + +- Learn more about [writing Spin components and manifests](writing-apps) +- Learn how to [build your Spin application code](build) +- Learn how to [deploy your application to a cloud host](deploying) diff --git a/content/v4/rdbms-storage.md b/content/v4/rdbms-storage.md new file mode 100644 index 00000000..797acc66 --- /dev/null +++ b/content/v4/rdbms-storage.md @@ -0,0 +1,264 @@ +title = "Relational Database Storage" +template = "main" +date = "2023-11-04T00:00:01Z" +enable_shortcodes = true +[extra] +url = "https://github.com/spinframework/spin-docs/blob/main/content/v4/rdbms-storage.md" + +--- +- [Using MySQL and PostgreSQL From Applications](#using-mysql-and-postgresql-from-applications) +- [Application Development Considerations](#application-development-considerations) + - [PostgreSQL Range Queries](#postgresql-range-queries) +- [Granting Network Permissions to Components](#granting-network-permissions-to-components) + - [Configuration-Based Permissions](#configuration-based-permissions) + +Spin provides two interfaces for relational (SQL) databases: + +* A built-in [SQLite Database](./sqlite-api-guide), which is always available and requires no management on your part. +* "Bring your own database" support for MySQL and PostgreSQL, where you host and manage the database outside of Spin. + +This page covers the "bring your own database" scenario. See [SQLite Storage](./sqlite-api-guide) for the built-in service. + +{{ details "Why do I need a Spin interface? Why can't I just use my language's database libraries?" "The current version of the WebAssembly System Interface (WASI) doesn't provide a sockets interface, so database libraries that depend on sockets can't be built to Wasm. The Spin interface means Wasm modules can bypass this limitation by asking Spin to make the database connection on their behalf." }} + +## Using MySQL and PostgreSQL From Applications + +The Spin SDK surfaces the Spin MySQL and PostgreSQL interfaces to your language. The set of operations is the same across both databases: + +| Operation | Parameters | Returns | Behavior | +|------------|----------------------------|---------------------|----------| +| `open` | address | connection resource | Opens a connection to the specified database. The host must be listed in `allowed_outbound_hosts`. Other operations must be called through a connection. | +| `query` | statement, SQL parameters | database records | Runs the specified statement against the database, returning the query results as a set of rows. | +| `execute` | statement, SQL parameters | integer (not MySQL) | Runs the specified statement against the database, returning the number of rows modified by the statement. (MySQL does not return the modified row count.) | + +> The PostgreSQL interface is asynchronous (a blocking one is available for backward compatibility); the MySQL interface is blocking. + +The exact detail of calling these operations from your application depends on your language: + +{{ tabs "sdk-type" }} + +{{ startTab "Rust"}} + +> [**Want to go straight to the reference documentation?** Find it here.](https://docs.rs/spin-sdk/latest/spin_sdk/index.html) + +MySQL functions are available in the `spin_sdk::mysql` module, and PostgreSQL functions in the `spin_sdk::pg` module. + +The function names match the operations above. This example shows MySQL (blocking): + +```rust +use spin_sdk::mysql::{self, Connection, Decode, ParameterValue}; + +let connection = Connection::open(&address)?; + +let params = vec![ParameterValue::Int32(id)]; + +let rowset = connection.query("SELECT id, name FROM pets WHERE id = ?", ¶ms)?; + +// MySQL returns the rows as a vector +match rowset.rows.first() { + None => /* no rows matched query */, + Some(row) => { + let name = String::decode(&row[1])?; + } +} +``` + +And PostgreSQL (async): + +```rust +use spin_sdk::pg::{self, Connection, Decode, ParameterValue}; + +let connection = Connection::open(&address).await?; + +let params = vec![ParameterValue::Int32(id)]; + +let (columns, rows, result) = connection.query("SELECT id, name FROM pets WHERE id = $1", ¶ms).await?; + +// PostgreSQL returns the rows as a stream +while let Some(row) = rows.next().await { + let name = String::decode(&row[1])?; + // ... more processing ... +} + +// Check if the row stream ended due to completion or an error +result.await?; +``` + +**Notes** + +* Parameters are instances of the `ParameterValue` enum; you must wrap raw values in this type. +* A row is a vector of the `DbValue` enum. Use the `Decode` trait to access conversions to common types. +* Using PostgreSQL works in the same way, except that you `use` the `spin_sdk::pg` module instead of `spin_sdk::mysql`. +* Modified row counts are returned as `u64`. (MySQL `execute` does not return the modified row count.) +* All functions wrap the return in `anyhow::Result`. + +You can find complete examples for using relational databases in the Spin repository on GitHub ([MySQL](https://github.com/spinframework/spin-rust-sdk/tree/main/examples/mysql), [PostgreSQL](https://github.com/spinframework/spin-rust-sdk/tree/main/examples/postgres)). + +For full information about the MySQL and PostgreSQL APIs, see [the Spin SDK reference documentation](https://docs.rs/spin-sdk/latest/spin_sdk/index.html). + +{{ blockEnd }} + +{{ startTab "TypeScript"}} + +> [**Want to go straight to the reference documentation?** Find it here.](https://spinframework.github.io/spin-js-sdk/) + +The code below is an [Outbound MySQL example](https://github.com/spinframework/spin-js-sdk/tree/main/examples/spin-host-apis/spin-mysql). There is also an outbound [PostgreSQL example](https://github.com/spinframework/spin-js-sdk/tree/main/examples/spin-host-apis/spin-postgres) available. + +```ts +// https://itty.dev/itty-router/routers/autorouter +import { AutoRouter } from 'itty-router'; +import { open } from '@spinframework/spin-mysql'; + +// Connects as the root user without a password +const DB_URL = "mysql://root:@127.0.0.1/spin_dev" + +/* + Run the following commands to setup the instance: + create database spin_dev; + use spin_dev; + create table test(id int, val int); + insert into test values (4,4); +*/ + +let router = AutoRouter(); + +router + .get("/", () => { + let conn = open(DB_URL); + conn.execute('delete from test where id=?', [4]); + conn.execute('insert into test values (4,5)', []); + let ret = conn.query('select * from test', []); + + return new Response(JSON.stringify(ret, null, 2)); + }) + +//@ts-ignore +addEventListener('fetch', async (event: FetchEvent) => { + event.respondWith(router.fetch(event.request)); +}); +``` + +{{ blockEnd }} + +{{ startTab "Python"}} + +> [**Want to go straight to the reference documentation?** Find it here.](https://spinframework.github.io/spin-python-sdk/v3/) + +The code below is an [Outbound MySQL example](https://github.com/spinframework/spin-python-sdk/tree/main/examples/spin-mysql). There is also an outbound [PostgreSQL example](https://github.com/spinframework/spin-python-sdk/tree/main/examples/spin-postgres) available. + +```python +from spin_sdk import http +from spin_sdk.http import Request, Response +from spin_sdk import mysql + +class IncomingHandler(http.IncomingHandler): + def handle_request(self, request: Request) -> Response: + with mysql.open("mysql://root:@127.0.0.1/spin_dev") as db: + print(db.query("select * from test", [])) + + return Response( + 200, + {"content-type": "text/plain"}, + bytes("Hello from Python!", "utf-8") + ) +``` + +{{ blockEnd }} + +{{ startTab "TinyGo"}} + +> [**Want to go straight to the reference documentation?** Find it here.](https://pkg.go.dev/github.com/spinframework/spin-go-sdk/v2) + +MySQL functions are available in the `github.com/spinframework/spin-go-sdk/v2/mysql` package, and PostgreSQL in `github.com/spinframework/spin-go-sdk/v2/pg`. [See Go Packages for reference documentation.](https://pkg.go.dev/github.com/spinframework/spin-go-sdk/v2) + +The package follows the usual Go database API. Use `Open` to return a connection to the database of type `*sql.DB` - see the [Go standard library documentation](https://pkg.go.dev/database/sql#DB) for usage information. For example: + +```go +package main + +import ( + "encoding/json" + "fmt" + "net/http" + "os" + + spinhttp "github.com/spinframework/spin-go-sdk/v2/http" + "github.com/spinframework/spin-go-sdk/v2/pg" +) + +type Pet struct { + ID int64 + Name string + Prey *string // nullable field must be a pointer + IsFinicky bool +} + +func init() { + spinhttp.Handle(func(w http.ResponseWriter, r *http.Request) { + + // addr is the environment variable set in `spin.toml` that points to the + // address of the Mysql server. + addr := os.Getenv("DB_URL") + + // For MySQL, use `mysql.Open` + db := pg.Open(addr) + defer db.Close() + + // For MySQL, use `?` placeholder syntax + _, err := db.Query("INSERT INTO pets VALUES ($1, 'Maya', $2, $3);", int32(4), "bananas", true) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + rows, err := db.Query("SELECT * FROM pets") + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + var pets []*Pet + for rows.Next() { + var pet Pet + if err := rows.Scan(&pet.ID, &pet.Name, &pet.Prey, &pet.IsFinicky); err != nil { + fmt.Println(err) + } + pets = append(pets, &pet) + } + json.NewEncoder(w).Encode(pets) + }) +} +``` + +{{ blockEnd }} + +{{ blockEnd }} + +## Application Development Considerations + +This section contains notes and gotchas for developers using Spin's relational database APIs. + +### PostgreSQL Range Queries + +The PostgreSQL "range contains" operator, `<@`, is overloaded for "contains value" and "contains another range." This ambiguity can result in "wrong type" errors when executing "range contains" queries where the left hand side is parameterised. + +To avoid this, use a type annotation on the parameter placeholder, e.g.: + +``` +SELECT name FROM cats WHERE $1::int4 <@ reign +``` + +The ambiguity is tracked at https://github.com/sfackler/rust-postgres/issues/1258. + +## Granting Network Permissions to Components + +By default, Spin components are not allowed to make outgoing network requests, including MySQL or PostgreSQL. This follows the general Wasm rule that modules must be explicitly granted capabilities, which is important to sandboxing. To grant a component permission to make network requests to a particular host, use the `allowed_outbound_hosts` field in the component manifest, specifying the host and allowed port: + +```toml +[component.uses-db] +allowed_outbound_hosts = ["postgres://postgres.example.com:5432"] +``` + +### Configuration-Based Permissions + +You can use [application variables](./variables.md#adding-variables-to-your-applications) in the `allowed_outbound_hosts` field. However, this feature is not yet available on Fermyon Cloud. diff --git a/content/v4/redis-outbound.md b/content/v4/redis-outbound.md new file mode 100644 index 00000000..e7206ac5 --- /dev/null +++ b/content/v4/redis-outbound.md @@ -0,0 +1,184 @@ +title = "Redis Storage" +template = "main" +date = "2023-11-04T00:00:01Z" +enable_shortcodes = true +[extra] +url = "https://github.com/spinframework/spin-docs/blob/main/content/v4/redis-outbound.md" + +--- +- [Using Redis From Applications](#using-redis-from-applications) +- [Granting Network Permissions to Components](#granting-network-permissions-to-components) + - [Configuration-Based Permissions](#configuration-based-permissions) + +Spin provides an interface for you to read and write the Redis key/value store, and to publish Redis pub-sub messages. + +{{ details "Why do I need a Spin interface? Why can't I just use my language's Redis library?" "The current version of the WebAssembly System Interface (WASI) doesn't provide a sockets interface, so Redis libraries that depend on sockets can't be built to Wasm. The Spin interface means Wasm modules can bypass this limitation by asking Spin to make the Redis connection on their behalf." }} + +## Using Redis From Applications + +The Spin SDK surfaces the Spin Redis interface to your language. The set of operations is common across all SDKs: + +| Operation | Parameters | Returns | Behavior | +|--------------|---------------------|---------|----------| +| `open` | address | connection resource | Opens a connection to the specified Redis instance. The host must be listed in `allowed_outbound_hosts`. Other operations must be called through a connection. (Exception: for JavaScript, do not call `open`, and pass the address to each data operation - see the language guides below.) | +| Single value operations | +| `get` | key | bytes | Returns the value of `key`. If the key does not exist, returns a zero-length array. | +| `set` | key, bytes | - | Sets the value of `key`, overwriting any existing value. | +| `incr` | key | integer | Increments the value at `key` by 1. If the key does not exist, its value is set to 0 first (and immediately incremented to 1). If the current value isn't an integer, or a string that represents an integer, it errors and the value is not changed. | +| `del` | list of keys | - | Removes the specified keys. Keys that don't exist are ignored (they do _not_ cause an error). | +| Set value operations | +| `sadd` | key, list of strings | integer | Adds the strings to the set of values of `key`, and returns the number of newly added values. If the key does not exist, its value is set to the set of values | +| `smembers` | key | list of strings | Returns the set of values of `key`. if the key does not exist, this is an empty set. | +| `srem` | key, list of strings | integer | Removes the strings from the set of values of `key`, and returns the number of newly removed values. If the key does not exist, this does nothing. | +| Pub-sub operations | +| `publish` | channel, bytes | - | Publishes a message to the specified channel, with the specified payload bytes. | +| General operations | +| `execute` | command, list of argument values | list of results | Executes the specified command with the specified arguments. This is a general-purpose 'escape hatch' if you need a command that isn't covered by the built-in operations. | + +The exact detail of calling these operations from your application depends on your language: + +{{ tabs "sdk-type" }} + +{{ startTab "Rust"}} + +> [**Want to go straight to the reference documentation?** Find it here.](https://docs.rs/spin-sdk/latest/spin_sdk/redis/index.html) + +Redis functions are available in the `spin_sdk::redis` module. + +To access a Redis instance, use the `Connection::open` function. + +```rust +let connection = spin_sdk::redis::Connection::open(&address).await?; +``` + +You can then call functions on the `Connection` to work with the Redis instance: + +```rust +connection.set("my-key", &"my-value".into()).await?; +let data = connection.get("my-key").await?; +``` + +For full details of the Redis API, see the [Spin SDK reference documentation](https://docs.rs/spin-sdk/latest/spin_sdk/redis/index.html); + +**General Notes** + +* Keys are of type `&str`. +* Bytes parameters are of type `&[u8]` and return values are `Vec`. +* Numeric return values are of type `i64`. +* All functions wrap the return in `anyhow::Result`. + +**`get` Operation** + +* This returns a `Result>>`. If the key is not found, the return value is `Ok(None)`. + +**`del` Operation** + +* The list of keys is passed as `&[String]`. + +**Set Operations** + +* List arguments are passed as `&[String]` and returned as `Vec`. + +**`execute` Operation** + +* The arguments and results are enums, representing integers, binary payloads, and (for results) status and nil values. + +You can find a complete Rust code example for using outbound Redis from an HTTP component in the [Spin repository on GitHub](https://github.com/spinframework/spin-rust-sdk/tree/main/examples/redis-outbound). Please also see this, related, [outbound Redis (using Rust) section](./rust-components#storing-data-in-redis-from-rust-components). + +{{ blockEnd }} + +{{ startTab "TypeScript"}} + +> [**Want to go straight to the reference documentation?** Find it here.](https://spinframework.github.io/spin-js-sdk/) + +Redis functions are available on [the `Redis` module](https://spinframework.github.io/spin-js-sdk/modules/_spinframework_spin-redis.html). The function names match the operations above. For example: + +```javascript +import { open } from "@spinframework/spin-redis" + +let db = open("redis://localhost:6379") +let value = db.get(key); +``` + +**General Notes** +* Key parameters are strings. +* Bytes parameters and return values are buffers (TypeScript `Uint8Array`). +* Lists are passed and returned as arrays. + +**`execute` Operation** + +* The arguments and results can be either numbers or buffers. (In TypeScript they are union types, e.g. `BigInt | Uint8Array`.) + +You can find a complete TypeScript example for using outbound Redis from an HTTP component in the [JavaScript SDK repository on GitHub](https://github.com/spinframework/spin-js-sdk/tree/main/examples/spin-host-apis/spin-redis). Please also see this, related, [outbound Redis (using TypeScript) section](./javascript-components#storing-data-in-redis-from-jsts-components). + +{{ blockEnd }} + +{{ startTab "Python"}} + +> [**Want to go straight to the reference documentation?** Find it here.](https://spinframework.github.io/spin-python-sdk/v3/redis.html) + +Redis functions are available in [the `redis` module](https://spinframework.github.io/spin-python-sdk/v3/redis.html). The function names are prefixed `redis_`. You must pass the Redis instance address to _each_ operation as its first parameter. For example: + +```python +from spin_sdk import redis +with redis.open("redis://localhost:6379") as db: + val = db.get("test") +``` + +**General Notes** + +* Address and key parameters are strings (`str`). +* Bytes parameters and return values are `bytes`. (You can pass literal strings using the `b` prefix, e.g. `redis_set(address, key, b"hello")`.) +* Numeric return values are of type `int64`. +* Lists are passed and returned as Python lists. +* Errors are signalled through exceptions. + +You can find a complete Python code example for using outbound Redis from an HTTP component in the [Python SDK repository on GitHub](https://github.com/spinframework/spin-python-sdk/tree/main/examples/spin-redis). Please also see this, related, [outbound Redis (using Python) section](./python-components#an-outbound-redis-example). + +{{ blockEnd }} + +{{ startTab "TinyGo"}} + +> [**Want to go straight to the reference documentation?** Find it here.](https://pkg.go.dev/github.com/spinframework/spin-go-sdk/v2@v2.2.1/redis) + +Redis functions are available in the `github.com/spinframework/spin-go-sdk/v2/redis` package. [See Go Packages for reference documentation.](https://pkg.go.dev/github.com/spinframework/spin-go-sdk/v2/redis) The function names are TitleCased. For example: + +```go +import ( + "github.com/spinframework/spin-go-sdk/v2/redis" +) + +rdb := redis.NewClient(addr) +payload, err := rdb.Get(key) +``` + +**General Notes** + +* Key parameters are strings. +* Bytes parameters are byte slices (`[]byte`). +* Lists are passed as slices. For example, `redis.Del` takes the keys to delete as a `[]string`. +* Errors are return through the usual Go multiple return values mechanism. + +**`execute` Operation** + +* The arguments are passed as `[]redis.RedisParameter`. You can construct `RedisParameter` instances around an `interface{}` but must provide a `Kind`. For example, `hello := redis.RedisParameter{Kind: redis.RedisParameterKindBinary, Val: []byte("hello")}`. +* The results are returned as `[]redis.Result`. You can use the `Kind` member of `redis.Result` to interpret the `Val`. + +You can find a complete TinyGo example for using outbound Redis from an HTTP component in the [Spin repository on GitHub](https://github.com/spinframework/spin-go-sdk/tree/main/examples/redis-outbound). Please also see this, related, [outbound Redis (using TinyGo) section](./go-components#storing-data-in-redis-from-go-components). + +{{ blockEnd }} + +{{ blockEnd }} + +## Granting Network Permissions to Components + +By default, Spin components are not allowed to make outgoing network requests, including Redis. This follows the general Wasm rule that modules must be explicitly granted capabilities, which is important to sandboxing. To grant a component permission to make network requests to a particular host, use the `allowed_outbound_hosts` field in the component manifest, specifying the host and allowed port: + +```toml +[component.uses-redis] +allowed_outbound_hosts = ["redis://redis.example.com:6379"] +``` + +### Configuration-Based Permissions + +You can use [application variables](./variables.md#adding-variables-to-your-applications) in the `allowed_outbound_hosts` field. However, this feature is not yet available on Fermyon Cloud. diff --git a/content/v4/redis-trigger.md b/content/v4/redis-trigger.md new file mode 100644 index 00000000..bda9ff9d --- /dev/null +++ b/content/v4/redis-trigger.md @@ -0,0 +1,162 @@ +title = "The Spin Redis trigger" +template = "main" +date = "2023-11-04T00:00:01Z" +enable_shortcodes = true +[extra] +url = "https://github.com/spinframework/spin-docs/blob/main/content/v4/redis-trigger.md" + +--- +- [Specifying a Redis Trigger](#specifying-a-redis-trigger) + - [Setting a Default Server](#setting-a-default-server) +- [Redis Trigger Authentication](#redis-trigger-authentication) +- [Redis Subscriber Components](#redis-subscriber-components) + - [The Message Handler](#the-message-handler) +- [Inside Redis Components](#inside-redis-components) + +Pub-sub (publish-subscribe) messaging is a popular architecture for asynchronous message processing. Spin has built-in support to creating and running applications in response to messages on [pub-sub Redis channels](https://redis.io/topics/pubsub). + +The Redis trigger in Spin subscribes to messages from a given Redis instance, and dispatches those messages to components for handling. + +> This page deals with the Redis trigger for subscribing to pub-sub messages. For information about reading and writing the Redis key-value store, or for publishing messages, see the Language Guides. + +> The Redis trigger is not yet available in Fermyon Cloud. + +## Specifying a Redis Trigger + +A Redis trigger maps a Redis channel to a component. For example: + +```toml +[[trigger.redis]] +address = "redis://notifications.example.com:6379" # the Redis instance that the trigger subscribes to (optional - see below) +channel = "messages" # the channel that the trigger subscribes to +component = "my-application" # the name of the component to handle this route +``` + +Such a trigger says that Redis messages on the specified _channel_ should be handled by the specified _component_. The `component` field works the same way across all triggers - see [Triggers](triggers) for the details. + +> Spin subscribes only to the channels that are mapped to components. Other channels are ignored. If multiple components subscribe to the same channel, a message on that channel will activate all of the components. + +You can use [application variables](./variables.md#adding-variables-to-your-applications) in the `address` and `channel` fields. + +### Setting a Default Server + +In many applications, all components listen to the same Redis server (on different channels, of course). For this case, it is more convenient to specify the server at the application level instead of on each component. This is done via the `[application.trigger.redis]` section of manifest: + +```toml +[application.trigger.redis] +address = "redis://notifications.example.com:6379" +``` + +> If you create an application from a Redis template, the trigger will be already set up for you. + +You can use [application variables](./variables.md#adding-variables-to-your-applications) in the `address` field. This can be particularly useful for credentials, allowing you to pass credentials in via [variables providers](./dynamic-configuration.md#application-variables-runtime-configuration) rather than including them in `spin.toml`; you can also use it to use a local Redis in your dev environment and a cloud instance when deployed. + +## Redis Trigger Authentication + +By default, Spin does not authenticate to Redis. You can work around this by providing a password in the `redis://` URL. For example: `address = "redis://:\{{ password }}@localhost:6379"`. Here `password` is an [application variable](./variables.md#adding-variables-to-your-applications) as mentioned above. + +> Do not hard-code passwords in code committed to version control systems. + +## Redis Subscriber Components + +To run code that listens for Redis messages, Spin uses the [WebAssembly component model](https://component-model.bytecodealliance.org/). In this model, the Wasm module exports a well-known interface that Spin calls to handle the Redis message. You can export this interface directly using [`wit-bindgen`](https://github.com/bytecodealliance/wit-bindgen) or use a convenience wrapper provided by a language SDK. + +### The Message Handler + +The exact signature of the Redis handler, and how a function is identified to be exported as the handler, will depend on your language. + +{{ tabs "sdk-type" }} + +{{ startTab "Rust"}} + +> [**Want to go straight to the reference documentation?** Find it here.](https://docs.rs/spin-sdk/latest/spin_sdk/index.html) + +In Rust, the handler is identified by the `#[spin_sdk::redis_subscriber]` attribute. The handler is an `async` function. Its sole argument is a `Vec`, representing the raw payload of the Redis message. The function returns an `anyhow::Result` indicating success or an error with details. This example just logs the payload as a string: + +```rust +use anyhow::Result; +use spin_sdk::redis_subscriber; +use std::str::from_utf8; + +/// A simple Spin Redis component. +#[redis_subscriber] +async fn on_message(message: Vec) -> Result<()> { + println!("{}", from_utf8(&message)?); + Ok(()) +} +``` + +{{ blockEnd }} + +{{ startTab "TypeScript"}} + +The JavaScript/TypeScript SDK doesn't currently support Redis components. Please [let us know](https://github.com/spinframework/spin-js-sdk/issues) if this is important to you. + +{{ blockEnd }} + +{{ startTab "Python"}} + +In Python, the handler needs to implement the [`InboundRedis`](https://spinframework.github.io/spin-python-sdk/v3/wit/exports/index.html#spin_sdk.wit.exports.InboundRedis) class, and override the `handle_message` method: + +```python +from spin_sdk.wit import exports +class InboundRedis(exports.InboundRedis): + def handle_message(self, message: bytes): + print(message) +``` + +{{ blockEnd }} + +{{ startTab "TinyGo"}} + +> [**Want to go straight to the reference documentation?** Find it here.](https://pkg.go.dev/github.com/spinframework/spin-go-sdk/v2@v2.2.1/redis) + +In Go, you register the handler as a callback in your program's `init` function. Call `redis.Handle` (from the Spin SDK `redis` package), passing your handler as the sole argument. Your handler takes a single byte slice (`[]byte`) argument, and may return an error or `nil`. + +> The do-nothing `main` function is required by TinyGo but is not used; the action happens in the `init` function and handler callback. + +This example just logs the payload as a string: + +```go +package main + +import ( + "fmt" + + "github.com/spinframework/spin-go-sdk/v2/redis" +) + +func init() { + redis.Handle(func(payload []byte) error { + fmt.Println(string(payload)) + return nil + }) +} +``` + +{{ blockEnd }} + +{{ blockEnd }} + +## Inside Redis Components + +For the most part, you'll build Redis component modules using a language SDK (see the Language Guides section), such as a Rust crate or Go package. If you're interested in what happens inside the SDK, or want to implement Redis components in another language, read on! + +The Redis component interface is defined using a WebAssembly Interface (WIT) file. ([Learn more about the WIT language here.](https://component-model.bytecodealliance.org/design/wit.html)). You can find the latest WITs for Spin Redis components at [https://github.com/spinframework/spin/tree/main/wit](https://github.com/spinframework/spin/tree/main/wit). + +In particular, the entry point for Spin Redis components is defined in [the `inbound-redis` interface](https://github.com/spinframework/spin/blob/main/wit/deps/spin-redis%403.0.0/redis.wit): + + + +```fsharp +interface inbound-redis { + use redis.{payload, error}; + + // The entrypoint for a Redis handler. + handle-message: async func(message: payload) -> result<_, error>; +} +``` + +This is the interface that all Redis components must implement, and +which is used by Spin when instantiating and invoking the component. +However, it is implemented internally by the Spin SDK - you don't need to implement it directly. diff --git a/content/v4/running-apps.md b/content/v4/running-apps.md new file mode 100644 index 00000000..4904f623 --- /dev/null +++ b/content/v4/running-apps.md @@ -0,0 +1,201 @@ +title = "Running Spin Applications" +template = "main" +date = "2023-11-04T00:00:01Z" +enable_shortcodes = true +[extra] +url = "https://github.com/spinframework/spin-docs/blob/main/content/v4/running-apps.md" + +--- +- [Specifying the Application to Run](#specifying-the-application-to-run) +- [Specifying the Wasm File to Run](#specifying-the-wasm-file-to-run) + - [Testing HTTP Applications](#testing-http-applications) +- [Application Output](#application-output) +- [Persistent Logs](#persistent-logs) +- [Trigger-Specific Options](#trigger-specific-options) +- [Monitoring Applications for Changes](#monitoring-applications-for-changes) +- [Splitting an Application Across Environments](#splitting-an-application-across-environments) +- [The Always Build Option](#the-always-build-option) +- [Next Steps](#next-steps) + +Once you have created and built your application, it's ready to run. To run an application, use the `spin up` command. + +## Specifying the Application to Run + +By default, `spin up` looks for a file named `spin.toml` in the current directory. + +If your manifest is named something different, or isn't in your current directory, use the `-f` (`--from`) flag. You also use `-f` to run remote applications. + +| `-f` Value | `spin up` Behavior | +|---------------------------------|---------------------| +| File name: `-f demo.toml` | Runs the specified manifest file | +| Directory name: `-f demo/` | Looks for a `spin.toml` file in that directory and runs that | +| Registry reference: `-f ghcr.io/fermyon/example:v1` | Pulls the application from the registry and runs that | + +> If Spin misunderstands a registry reference as a file name, or vice versa, you can use `--from-file` or `--from-registry` instead of `-f` to disambiguate. + +> If you see the error `failed to resolve content at "example.wasm"` (where `example.wasm` is the module file of a component), check that the application has been built. + +> If your application doesn't run, you can [run `spin doctor`](./troubleshooting-application-dev.md) to check for problems with your Spin configuration and tools. + +## Specifying the Wasm File to Run + +The `spin up --from` (`spin up -f`) option can point to a pre-existing Wasm binary executable instead of an application's manifest: + + + +```bash +$ spin up --from myapp.wasm +``` + +Please note that the uses for performing `spin up` using just a Wasm file are very limited outside of basic testing of the Wasm file. This is because Wasm files run in this way have no access to application-level configuration that allows storage, outbound HTTP and so on. Only incoming HTTP handlers are supported. + +### Testing HTTP Applications + +By default, HTTP applications listen on `localhost:3000`. You can override this with the `--listen` option. Spin prints links to the application components to make it easy to open them in the browser or copy them to `curl` commands for testing. + +## Application Output + +By default, Spin prints application output, and any of its own error messages, to the console. + +To hide application output, pass the `--quiet` flag: + + + +```bash +$ spin up --quiet +``` + +To limit application output to specific components, pass the `--follow` flag: + + + +```bash +$ spin up --follow cart --follow cart-api +``` + +## Persistent Logs + +By default: + +* If you run an application from the file system (a TOML file), Spin saves a copy of the application output and error messages. This is saved in the `.spin/logs` directory, under the directory containing the manifest file. +* If you run an application from a registry reference, Spin does not save a copy of the application output and error messages; they are only printed to the console. + +To control logging, pass the `--log-dir` flag. The logs will be saved to the specified directory (no matter whether the application is local or remote): + + + +```bash +$ spin up --log-dir ~/dev/bugbash +``` + +If you prefer **not** to have the `stdout` and `stderr` of your application's components written to disk (as in the example above), you can pass the `--log-dir` flag with an empty string, like this: + + + +```bash +$ spin up --log-dir "" +``` + +## Trigger-Specific Options + +Some trigger types support additional `spin up` flags. For example, HTTP applications can have a `--listen` flag to specify an address and port to listen on. See the [HTTP trigger](http-trigger) and [Redis trigger](redis-trigger) pages for more details. + +## Monitoring Applications for Changes + +Spin's `watch` command rebuilds and restarts Spin applications whenever files change. You can use the `spin watch` command in place of the `spin build` and `spin up` commands, to build, run and then keep your Spin application running without manual intervention while staying on the latest code and files. + +> The `watch` command accepts valid `spin up` options and passes them through to `spin up` for you when running/rerunning the Spin application. +E.g. `spin watch --listen 127.0.0.1:3001` + +By default, Spin watch monitors: + +* The application manifest (`spin.toml` file) +* Any files specified in the `component.(id).build.watch` sections of the `spin.toml` file +* Any files specified in the `component.(id).files` sections of the `spin.toml` file +* The files specified in the `component.(id).source` sections of the `spin.toml` file + +If any of these change, Spin will rebuild the application if necessary, then restart the application with the new files. + +> Spin watch does not consider changes to a file's metadata (file permissions or when it was last modified) as a change. + +The following `spin.toml` configuration (belonging to a Spin `http-rust` application) is configured to ensure that the application is both **rebuilt** (via `cargo build --target wasm32-wasip2 --release`) and **rerun** whenever changes occur in any Rust source (`.rs`) files, the `Cargo.toml` file or the `spin.toml` file, itself. When changes occur in either the Wasm binary file (`target/wasm32-wasip2/release/test.wasm`) or the text file (`my-files/changing-file.txt`) the application is only **rerun** using the initial `spin up` command: + + + + ```toml +[component.test] +// -- snip +files = ["my-files/changing-file.txt"] +source = "target/wasm32-wasi/release/test.wasm" +[component.test.build] +command = "cargo build --target wasm32-wasi --release" +# Example watch configuration for a Rust application +watch = ["src/**/*.rs", "Cargo.toml"] +``` + +If the `build` section specifies a `workdir`, then `watch` patterns are relative to that directory. Otherwise, `watch` patterns are relative to the directory containing the `spin.toml` file. + +If you would prefer Spin watch to only rerun the application (without a rebuild) when changes occur, you can use the `--skip-build` option when running the `spin watch` command. In this case, Spin will ignore the `component.(id).build.watch` section, and monitor only the `spin.toml`, `component.source` and `component.files`. + +The table below outlines exactly which files `spin watch` will monitor for changes depending on how you run the command. `spin watch` uses the configuration found on every component in your application. + +| Files | `spin watch` monitors for changes | `spin watch --skip-build` monitors for changes | +| ----------------------- | ---------------------------------------------- | ---------------------------------------------- | +| Application manifest `spin.toml` | Yes | Yes | +| Component `build.watch` | Yes | No | +| Component `files` | Yes | Yes | +| Component `source` | No (Yes if the component has no build command) | Yes + +Spin watch waits up to 100 milliseconds before responding to filesystem events, then processes all events that occurred in that interval together. This is so that if you make several changes close together (for example, using a Save All command), you get them all processed in one rebuild/reload cycle, rather than going through a cycle for each one. You can override the interval by passing in the `--debounce` option; e.g. `spin watch --debounce 1000` will make Spin watch respond to filesystem events at most once per second. + +> Note: If the build step (`spin build`) fails, `spin up` will not be re-run, and the previous version of the app will remain. + +Passing the `--clear` flag clears the screen anytime a rebuild or rerun occurs. Spin watch does not clear the screen between rebuild and rerun as this provides you with an opportunity to see any warnings. + +For additional information about Spin's `watch` feature, please see the [Spin watch - live reload for Wasm app development](https://www.fermyon.com/blog/spin-watch-live-reloading) blog article. + +> The application manifests shown in the blog post are the version 1 manifest, but the content applies equally to the version 2 format. + +## Splitting an Application Across Environments + +You can run a subset of the components in an application by using the `--component-id` (`-c`) flag. Set the flag multiple times to select multiple components. + +> This is an experimental feature. Unlike stable features, it may change even between minor versions. + +This supports _selective deployment_ - that is, splitting an application across environments while still distributing it as a single unit. For example, suppose your application contains the following layers: + +* A front end which handles browser and API requests, which you want to run in edge data centres close to users +* A database backend which must be run in a single central location +* An AI service which requires an LLM model to be loaded onto the hardware + +You could deploy the application across the environments by using `--component-id`. For example: + + + +```console +# on the US data centre edge nodes +$ spin up -f registry.example.com/app:1.1.0 -c ui -c api -c assets + +# on the European data centre edge nodes +$ spin up -f registry.example.com/app:1.1.0 -c ui -c api -c assets + +# on the European data centre core nodes +$ spin up -f registry.example.com/app:1.1.0 -c database + +# in the LLM environment +$ spin up -f registry.example.com/app:1.1.0 -c chat-engine -c sentiment-analyzer +``` + +> In practice you'd set these commands up in a scheduler or orchestrator rather than typing them interactively - or, more likely, use a selection-aware scheduler such as [SpinKube](https://www.spinkube.dev/) rather than running `spin up` directly. + +If you run a subset which includes a component that uses [local service chaining](./http-outbound#local-service-chaining), then you must also include all chaining targets in the subset - Spin checks this at load time. (Wildcard service chaining - the `*.spin.internal` host - means that _all_ components are potential chaining targets, so you will not be able to use selective deployment if any component uses wildcard service chaining.) [Self-requests](./http-outbound#making-http-requests-within-an-application) will work only if the target route maps to a component in the subset, but this is not checked at load time - instead, self-requests to unselected components will fail at request time with 404 Not Found. + +## The Always Build Option + +Some people find it frustrating having to remember to build their applications before running `spin up`. If you want to _always_ build your projects when you run them, set the `SPIN_ALWAYS_BUILD` environment variable in your profile or session. If this is set, `spin up` runs [`spin build`](build) before starting your applications. + +## Next Steps + +- Learn how to [create and update a Spin application](writing-apps) +- Learn about how to [configure your application at runtime](dynamic-configuration) +- See how to [package and distribute your application](distributing-apps) diff --git a/content/v4/rust-components.md b/content/v4/rust-components.md new file mode 100644 index 00000000..b13219ad --- /dev/null +++ b/content/v4/rust-components.md @@ -0,0 +1,594 @@ +title = "Building Spin components in Rust" +template = "main" +date = "2023-11-04T00:00:01Z" +[extra] +url = "https://github.com/spinframework/spin-docs/blob/main/content/v4/rust-components.md" + +--- +- [Prerequisites](#prerequisites) + - [Install the Templates](#install-the-templates) + - [Install the Tools](#install-the-tools) +- [HTTP Service Components](#http-service-components) +- [Redis Subscriber Components](#redis-subscriber-components) +- [Sending Outbound HTTP Requests](#sending-outbound-http-requests) +- [Routing in a Component](#routing-in-a-component) +- [Storing Data in Redis From Rust Components](#storing-data-in-redis-from-rust-components) +- [Async and Streaming Idioms in Rust](#async-and-streaming-idioms-in-rust) +- [Storing Data in the Spin Key-Value Store](#storing-data-in-the-spin-key-value-store) + - [Serializing Objects to the Key-Value Store](#serializing-objects-to-the-key-value-store) +- [Storing Data in SQLite](#storing-data-in-sqlite) +- [Storing Data in Relational Databases](#storing-data-in-relational-databases) +- [Using External Crates in Rust Components](#using-external-crates-in-rust-components) +- [AI Inferencing From Rust Components](#ai-inferencing-from-rust-components) +- [Troubleshooting](#troubleshooting) +- [Manually Creating New Projects With Cargo](#manually-creating-new-projects-with-cargo) +- [Read the Rust Spin SDK Documentation](#read-the-rust-spin-sdk-documentation) + +Spin aims to have best-in-class support for building components in Rust, and +writing such components should be familiar for Rust developers. + +> This guide assumes you have Spin installed. If this is your first encounter with Spin, please see the [Quick Start](quickstart), which includes information about installing Spin with the Rust templates, installing required tools, and creating Rust applications. + +> This guide assumes you are familiar with the Rust programming language, +> but if you are just getting started, be sure to check [the +official resources for learning Rust](https://www.rust-lang.org/learn). + +> All examples from this page can be found in [the Spin Rust SDK repository on GitHub](https://github.com/spinframework/spin-rust-sdk/tree/main/examples). + +[**Want to go straight to the Spin SDK reference documentation?** Find it here.](https://docs.rs/spin-sdk/latest/spin_sdk/index.html) + +## Prerequisites + +### Install the Templates + +You don't need the Spin Rust templates to work on Rust components, but they speed up creating new applications and components. You can install them as follows: + + + +```bash +$ spin templates install --git https://github.com/spinframework/spin --update +Copying remote template source +Installing template redis-rust... +Installing template http-rust... +... other templates omitted ... ++------------------------------------------------------------------------+ +| Name Description | ++========================================================================+ +| ... other templates omitted ... | +| http-rust HTTP request handler using Rust | +| redis-rust Redis message handler using Rust | +| ... other templates omitted ... | ++------------------------------------------------------------------------+ +``` + +Note: The Rust templates are in a repo that contains several other languages; they will all be installed together. + +### Install the Tools + +To build Spin components, you'll need the `wasm32-wasip2` target for Rust. + + + +```bash +$ rustup target add wasm32-wasip2 +``` + +If you don't have the target installed, then when you try to build a Spin component, you'll see an error similar to this: + +``` +error[E0463]: can't find crate for `core` + | + = note: the `wasm32-wasip2` target may not be installed + = help: consider downloading the target with `rustup target add wasm32-wasip2` + +For more information about this error, try `rustc --explain E0463`. +``` + +Check that you have the WASI target installed by running `rustup target list --installed`, or follow the instructions in the message and run `rustup target add wasm32-wasip2`. This is the most common source of problems when starting out with Rust in Spin! + +## HTTP Service Components + +In Spin, HTTP service components are triggered by the occurrence of an HTTP request, and +must return an HTTP response at the end of their execution. Components can be +built in any language that compiles to WASI, but Rust has improved support +for writing Spin components with the Spin Rust SDK. + +> Make sure to read [the page describing the HTTP trigger](./http-trigger.md) for more +> details about building HTTP applications. + +Building a Spin HTTP component using the Rust SDK means writing a single function decorated with the [`#[http_service]`](https://docs.rs/spin-sdk/latest/spin_sdk/attr.http_service.html) attribute. The function must be `async`. It takes an HTTP request as its only parameter, and returns an HTTP response. (More precisely, the parameter can be anything that implements the `FromRequest` trait, and the result can be anything that implements the `IntoResponse` trait.) Here's a typical simple example: + +```rust +use spin_sdk::http::{Request, Response, IntoResponse}; +use spin_sdk::http_service; + +/// A simple Spin HTTP component. +#[http_service] +async fn handle_hello_rust(_req: Request) -> anyhow::Result { + Ok(Response::builder() + .status(200) + .header("content-type", "text/plain") + .body("Hello, World!".to_string())?) +} +``` + +The important things to note in the implementation above: + +- the [`spin_sdk::http_service`](https://docs.rs/spin-sdk/latest/spin_sdk/attr.http_service.html) macro marks the function as the entry point for the Spin component +- the function signature β€” `async fn hello_world(req: Request) -> Result` β€” + the Spin HTTP component allows for a flexible set of response types via the [`IntoResponse`](https://docs.rs/spin-sdk/latest/spin_sdk/http/trait.IntoResponse.html) trait, including the SDK's `Response` type and the `Response` type from the Rust [`http` crate](https://crates.io/crates/http). See the section on [using the `http` crate](#using-the-http-crate) for more information. + +> The `spin_sdk::http` types are re-exports from the `http` crate. You can use them with other Rust crates that work with `http`, and related ecosystem crates such as `http_body` and `http_body_util`. We'll show an example of working with the popular Axum framework below. + +## Redis Subscriber Components + +Besides the HTTP trigger, Spin has built-in support for a Redis trigger β€” +which will connect to a Redis instance and will execute Spin components for +new messages on the configured channels. + +> See the [Redis trigger](./redis-trigger.md) for details about the Redis trigger. + +Writing a Redis component in Rust also takes advantage of the SDK: + +```rust +use anyhow::Result; +use spin_sdk::redis_subscriber; + +/// A simple Spin Redis component. +#[redis_subscriber] +async fn on_message(message: Vec) -> Result<()> { + println!("{}", std::str::from_utf8(&message)?); + Ok(()) +} +``` + +- the `spin_sdk::redis_subscriber` macro marks the function as the + entry point for the Spin component +- in the function signature β€” `async fn on_message(msg: Vec) -> anyhow::Result<()>` β€” +`msg` contains the payload from the Redis channel +- the component returns a Rust `anyhow::Result`, so if there is an error +processing the request, it returns an `anyhow::Error`. + +The component can be built with Cargo by executing: + + + +```bash +$ cargo build --target wasm32-wasip2 --release +``` + +The manifest for a Redis application must contain the address of the Redis +instance the trigger must connect to: + + + +```toml +spin_manifest_version = 2 +name = "spin-redis" +version = "0.1.0" + +[application.trigger.redis] +address = "redis://localhost:6379" + +[[trigger.redis]] +channel = "messages" +component = { source = "target/wasm32-wasip2/release/spinredis.wasm" } +``` + +This application will connect to `redis://localhost:6379`, and for every new +message on the `messages` channel, the `echo-message` component will be executed: + + + +```bash +# first, start redis-server on the default port 6379 +$ redis-server --port 6379 +# then, start the Spin application +$ spin up +# the application log file will output the following +INFO spin_redis_engine: Connecting to Redis server at redis://localhost:6379 +INFO spin_redis_engine: Subscribed component 0 (echo-message) to channel: messages +``` + +For every new message on the `messages` channel: + + + +```bash +$ redis-cli +127.0.0.1:6379> publish messages "Hello, there!" +``` + +Spin will instantiate and execute the component we just built, which will emit the `println!` message to the application log file: + +``` +INFO spin_redis_engine: Received message on channel "messages" +Hello, there! +``` + +> You can find a complete example for a Redis-triggered Rust component in the +> [Spin repository on GitHub](https://github.com/spinframework/spin-rust-sdk/tree/main/examples/redis). + +## Sending Outbound HTTP Requests + +If allowed, Spin components can send outbound HTTP requests. +Let's see an example of a component that makes a request to +[an API that returns random animal facts](https://random-data-api.fermyon.app/animals/json) and +inserts a custom header into the response before returning: + +```rust +use anyhow::Result; +use spin_sdk::{ + http::{EmptyBody, IntoResponse, Request, Method, Response}, + http_service, +}; + +#[http_service] +async fn send_outbound(_req: Request) -> Result { + // Create the outbound request object + let req = Request::builder() + .method(Method::Get) + .uri("https://random-data-api.fermyon.app/animals/json") + .body(EmptyBody::new())?; + + // Send the request and await the response + let res: Response = spin_sdk::http::send(req).await?; + + println!("Random data status code {}", res.status()); // log the response status + + // Pass the upstream response back to the client + Ok(res) +} +``` + +Before we can execute this component, we need to add the `random-data-api.fermyon.app` +domain to the component's `allowed_outbound_hosts` list in the application manifest. This contains the list of +domains the component is allowed to make network requests to: + + + +```toml +# spin.toml +spin_manifest_version = 2 + +[application] +name = "animal-facts" +version = "1.0.0" + +[[trigger.http]] +route = "/..." +component = "get-animal-fact" + +[component.get-animal-fact] +source = "get-animal-fact/target/wasm32-wasip2/release/get_animal_fact.wasm" +allowed_outbound_hosts = ["https://random-data-api.fermyon.app"] +``` + +Running the application using `spin up` will start the HTTP +listener locally (by default on `localhost:3000`), and our component can +now receive requests in route `/outbound`: + + + +```bash +$ curl -i localhost:3000 +HTTP/1.1 200 OK +date: Fri, 27 Oct 2023 03:54:36 GMT +content-type: application/json; charset=utf-8 +content-length: 185 +spin-component: get-animal-fact + +{"timestamp":1684299253331,"fact":"Reindeer grow new antlers every year"} +``` + +> Without the `allowed_outbound_hosts` field populated properly in `spin.toml`, +> the component would not be allowed to send HTTP requests, and sending the +> request would result in a "Destination not allowed" error. + +> You can set `allowed_outbound_hosts = ["https://*:*"]` if you want to allow +> the component to make requests to any HTTP host. This is not recommended +> unless you have a specific need to contact arbitrary servers and perform your own safety checks. + +We just built a WebAssembly component that sends an HTTP request to another +service, manipulates that result, then responds to the original request. +This can be the basis for building components that communicate with external +databases or storage accounts, or even more specialized components like HTTP +proxies or URL shorteners. + +> The Spin SDK for Rust provides more flexibility than we show here, including allowing streaming uploads or downloads. See the [Outbound HTTP API Guide](./http-outbound.md) for more information. + +## Routing in a Component + +You can route within a Rust HTTP component using your favourite Rust HTTP routing library. + +> Earlier versions of the Spin SDK included a their own `Router`, which was able to work with WASI/Spin HTTP types. From Spin SDK 6, the SDK uses the ecosystem-standard `http` types, and so you can ecosystem routers without changes. + +This example uses the Axum router in a Spin component: + +```rust +use axum::{ + routing::{any, get}, + Router, +}; +use spin_sdk::http::{IntoResponse, Request, Response}; +use spin_sdk::http_service; +use tower_service::Service; + +/// Demonstrates integration with the Axum web framework +#[http_service] +async fn handler(req: Request) -> impl IntoResponse { + Router::new() + .route("/{*all}", any(api::echo_wildcard)) + .route("/goodbye/{planet}", get(api::goodbye_planet)) + .call(req) + .await +} + +mod api { + use super::*; + use axum::extract::Path; + + // /goodbye/:planet + pub async fn goodbye_planet(Path(planet): Path) -> impl axum::response::IntoResponse { + Response::new(planet) + } + + // /* + pub async fn echo_wildcard(Path(all): Path) -> impl axum::response::IntoResponse { + Response::new(all) + } +} +``` + +## Storing Data in Redis From Rust Components + +Using the Spin Rust SDK, you can use the Redis key/value store, and publish +messages to Redis channels. This can be used from both HTTP and Redis triggered +components. + +Let's see how we can use the Rust SDK to connect to Redis: + +```rust +use anyhow::Context; +use spin_sdk::{ + http::{responses::internal_server_error, EmptyBody, IntoResponse, Request, Response}, + http_service, + redis::Connection, +}; + +// The environment variable set in `spin.toml` that points to the +// address of the Redis server that the component will publish +// a message to. +const REDIS_ADDRESS_ENV: &str = "REDIS_ADDRESS"; + +// The environment variable set in `spin.toml` that specifies +// the Redis channel that the component will publish to. +const REDIS_CHANNEL_ENV: &str = "REDIS_CHANNEL"; + +/// This HTTP component demonstrates fetching a value from Redis +/// by key, setting a key with a value, and publishing a message +/// to a Redis channel. The component is triggered by an HTTP +/// request served on the route configured in the `spin.toml`. +#[http_service] +async fn publish(_req: Request) -> anyhow::Result { + let address = std::env::var(REDIS_ADDRESS_ENV)?; + let channel = std::env::var(REDIS_CHANNEL_ENV)?; + + // Establish a connection to Redis + let conn = Connection::open(&address).await?; + + // Get the message to publish from the Redis key "mykey" + let payload = conn + .get("mykey") + .await + .context("Error querying Redis")? + .context("'mykey' was unexpectedly empty")?; + + // Set the Redis key "spin-example" to value "Eureka!" + conn.set("spin-example", &"Eureka!".to_owned().into_bytes()) + .await + .context("Error executing Redis set command")?; + + // Set the Redis key "int-key" to value 0 + conn.set("int-key", &format!("{:x}", 0).into_bytes()) + .await + .context("Error executing Redis set command")?; + let int_value = conn + .incr("int-key") + .await + .context("Error executing Redis incr command")?; + assert_eq!(int_value, 1); + + // Publish to Redis + match conn.publish(&channel, &payload).await { + Ok(()) => Ok(Response::new(EmptyBody::new())), + Err(_e) => Ok(internal_server_error()) + } +} +``` + +As with all networking APIs, you must grant access to Redis hosts via the `allowed_outbound_hosts` field in the application manifest: + + + +```toml +[component.redis-test] +environment = { REDIS_ADDRESS = "redis://127.0.0.1:6379", REDIS_CHANNEL = "messages" } +# Note this contains only the host and port - do not include the URL! +allowed_outbound_hosts = ["redis://127.0.0.1:6379"] +``` + +This HTTP component can be paired with a Redis component, triggered on new +messages on the `messages` Redis channel. + +> You can find a complete example for using outbound Redis from an HTTP component +> in the [Spin repository on GitHub](https://github.com/spinframework/spin-rust-sdk/tree/main/examples/redis-outbound). + +## Async and Streaming Idioms in Rust + +When a Spin API returns a potentially large number of values, such as database query APIs, the convention is to return the values as a stream, plus a future containing the result of the operation. For example, the key-value `Store::get_keys` function returns `(StreamReader, impl Future>)`. This signature is likely to be unfamiliar. The way to read it is: + +- Spin will stream values to you until either there are no more values, or an error occurs. +- When that happens, you must `await` the future to find out which one it was. + +For example, here's how you might use `Store::get_keys`: + +```rust +let (keys, result) = store.get_keys().await; +while let Some(key) = key.next().await { + // do something with `key` +} +result.await?; // check if the key stream hit an error +``` + +The future does not resolve until the stream ends, so be sure not to await it until you've finished with the stream. + +## Storing Data in the Spin Key-Value Store + +Spin has a key-value store built in. For information about using it from Rust, see [the key-value API guide](kv-store-api-guide). + +### Serializing Objects to the Key-Value Store + +The Spin key-value API stores and retrieves only lists of bytes. The Rust SDK provides helper functions that allow you to store and retrieve [Serde](https://docs.rs/serde/latest/serde) serializable values in a typed way. The underlying storage format is JSON (and is accessed via the `get_json` and `set_json` helpers). + +To make your objects serializable, you will also need a reference to `serde`. The relevant `Cargo.toml` entries look like this: + +``` +[dependencies] +// --snip -- +serde = { version = "1", features = ["derive"] } +// --snip -- +``` + +We configure our application to provision the default `key_value_stores` by adding the following line to our application's manifest (the `spin.toml` file), at the component level: + +``` +[component.json-test] +key_value_stores = ["default"] +``` + +The Rust code below shows how to store and retrieve serializable objects from the key-value store (note how the example below implements Serde's `derive` feature): + +```rust +use anyhow::Context; +use serde::{Deserialize, Serialize}; +use spin_sdk::{ + http::{IntoResponse, Request, Response}, + http_service, + key_value::Store, +}; + +// Define a serializable User type +#[derive(Serialize, Deserialize)] +struct User { + fingerprint: String, + location: String, +} + +#[http_service] +async fn handle_request(_req: Request) -> anyhow::Result { + // Open the default key-value store + let store = Store::open_default().await?; + + // Create an instance of a User object and populate the values + let user = User { + fingerprint: "0x1234".to_owned(), + location: "Brisbane".to_owned(), + }; + // Store the User object using the "my_json" key + store.set_json("my_json", &user).await?; + // Retrieve the user object from the key-value store, using the "my_json" key + let retrieved_user: User = store.get_json("my_json").await?.context("user not found")?; + // Return the user's fingerprint as the response body + Ok(Response::new(retrieved_user.fingerprint)) +} +``` + +Once built and running (using `spin up --build`) you can test the above example in your browser (by visiting localhost:3000) or via curl, as shown below: + + + +```bash +$ curl localhost:3000 +HTTP/1.1 200 OK + +0x1234 +``` + +For more information on the Rust key-value API see [the Spin SDK documentation](https://docs.rs/spin-sdk/latest/spin_sdk/key_value/index.html). + +## Storing Data in SQLite + +For more information about using SQLite from Rust, see [SQLite storage](sqlite-api-guide). + +## Storing Data in Relational Databases + +Spin provides clients for MySQL and PostgreSQL. For information about using them from Rust, see [Relational Databases](rdbms-storage). + +## Using External Crates in Rust Components + +In Rust, Spin components are regular libraries that contain a function +annotated using the `http_service` macro, compiled to the `wasm32-wasip2` target. +This means that any [crate](https://crates.io) that compiles to `wasm32-wasip2` can +be used when implementing the component. + +## AI Inferencing From Rust Components + +For more information about using Serverless AI from Rust, see the [Serverless AI](serverless-ai-api-guide) API guide. + +## Troubleshooting + +If you bump into issues building and running your Rust component, here are some common causes of problems: + +- Make sure `cargo` is present in your path. +- Make sure the [Rust](https://www.rust-lang.org/) version is recent. + +![Rust Version](https://img.shields.io/badge/dynamic/toml?url=https%3A%2F%2Fraw.githubusercontent.com%2Ffermyon%2Fspin%2Fmain%2FCargo.toml&query=$[%27workspace%27][%27package%27][%27rust-version%27]&label=Rust%20Version&logo=Rust&color=orange) + + - To check: run `cargo --version`. + - To update: run `rustup update`. +- Make sure the `wasm32-wasip2` compiler target is installed. + - To check: run `rustup target list --installed` and check that `wasm32-wasip2` is on the list. + - To install: run `rustup target add wasm32-wasip2`. +- Make sure you are building in `release` mode. Spin manifests refer to your Wasm file by a path, and the default path corresponds to `release` builds. + - To build manually: run `cargo build --release --target wasm32-wasip2`. + - If you're using `spin build` and the templates, this should be set up correctly for you. +- Make sure that the `source` field in the component manifest match the path and name of the Wasm file in `target/wasm32-wasip2/release`. These could get out of sync if you renamed the Rust package in its `Cargo.toml`. + +## Manually Creating New Projects With Cargo + +The recommended way of creating new Spin projects is by starting from a template. +This section shows how to manually create a new project with Cargo. + +When creating a new Spin project with Cargo, you should use the `--lib` flag: + + + +```bash +$ cargo init --lib +``` + +A `Cargo.toml` with standard Spin dependencies looks like this: + + + +```toml +[package] +name = "your-app" +version = "0.1.0" +edition = "2021" + +[lib] +# Required to have a `cdylib` (dynamic library) to produce a Wasm module. +crate-type = [ "cdylib" ] + +[dependencies] +# Useful crate to handle errors. +anyhow = "1" +# The Spin SDK. +spin-sdk = "6.0" +``` + +## Read the Rust Spin SDK Documentation + +Although you learned a lot by following the concepts and samples shown here, you can dive even deeper and read the [Rust Spin SDK documentation](https://docs.rs/spin-sdk/latest/spin_sdk/index.html). diff --git a/content/v4/see-what-people-have-built-with-spin.md b/content/v4/see-what-people-have-built-with-spin.md new file mode 100644 index 00000000..d155e1a1 --- /dev/null +++ b/content/v4/see-what-people-have-built-with-spin.md @@ -0,0 +1,81 @@ +title = "Built With Spin" +template = "main" +date = "2023-11-04T00:00:01Z" +[extra] +url = "https://github.com/spinframework/spin-docs/blob/main/content/v4/see-what-people-have-built-with-spin.md" + +--- +- [AI-powered bookmarking app with Python and WebAssembly](#ai-powered-bookmarking-app-with-python-and-webassembly) +- [Like Button](#like-button) +- [Social App](#social-app) +- [Static Content Server](#static-content-server) +- [Bots](#bots) +- [URL Shortener and QR Code Generator](#url-shortener-and-qr-code-generator) +- [Content Management System (CMS)](#content-management-system-cms) +- [The Finicky Whiskers Game](#the-finicky-whiskers-game) +- [Accessing External APIs](#accessing-external-apis) +- [SQLite Storage Using Javascript](#sqlite-storage-using-javascript) +- [Next Steps](#next-steps) + +Spin can be used to build many different types of applications. The following blog articles show how different applications are built with Spin. + +## AI-powered bookmarking app with Python and WebAssembly + +[How to build an AI-powered bookmarking app with Python and WebAssembly](https://dev.to/fermyon/part-1-how-to-build-an-ai-powered-bookmarking-app-with-python-and-webassembly-2c5b). + +## Like Button + +[How I Built a Like Button for My Blog with Spin](https://www.fermyon.com/blog/how-i-built-a-like-button-for-my-blog-with-spin) + +## Social App + +The 'Building a Social App with Spin' series covers the process and decision-making of building an authenticated, dynamic, database-backed application and API, right the way from downloading Spin and setting up the project to deploying the finished application to the cloud. + +* [Part 1: Project Setup and first API](https://www.fermyon.com/blog/building-a-social-app-with-spin-1) +* [Part 2: Vue.js App and Token Verification](https://www.fermyon.com/blog/building-a-social-app-with-spin-2) +* [Part 3: Post API and Spin SDK Bindings](https://www.fermyon.com/blog/building-a-social-app-with-spin-3) +* [Part 3.5: Go Postgres Usage](https://www.fermyon.com/blog/building-a-social-app-with-spin-3-5) +* [Part 4: Key-Value Storage and Fermyon Cloud](https://www.fermyon.com/blog/building-a-social-app-with-spin-4) + +## Static Content Server + +[Serving Static Content via WebAssembly](https://www.fermyon.com/blog/serving-static-content-via-webassembly) + +## Bots + +[Bots With Spin and Fermyon Cloud](https://www.fermyon.com/blog/bots-with-spin-and-fermyon-cloud) + +## URL Shortener and QR Code Generator + +[Shortlink and QR Code Generator](https://www.fermyon.com/blog/component-reuse) + +## Content Management System (CMS) + +[Build Your Own Content Management System (CMS) From a Template](https://www.fermyon.com/blog/build-you-own-cms-from-a-template) + +## The Finicky Whiskers Game + +The 'Finicky Whiskers' series covers the journey of creating a game with an architecture designed to [re-think microservices](https://www.fermyon.com/blog/rethinking-microservices) and showcase how WebAssembly modules can be started, executed, and shut down in the blink of an eye. + +> Finicky Whiskers was built to be the world's most adorable manual load generator, so it's not necessarily an model of how to use Spin most efficiently! But the series may be useful for decomposing an application into Spin microservices, and as a look at how Spin and containers can work together. As a bonus, if you are interested, you can play the game [here](https://finickywhiskers.com/index.html). + +* [Part 1: The World's Most Adorable Manual Load Generator](https://www.fermyon.com/blog/finicky-whiskers-part-1-intro) +* [Part 2: Serving the HTML, CSS, and Static Assets](https://www.fermyon.com/blog/finicky-whiskers-part-2-fileserver) +* [Part 3: The Microservices](https://www.fermyon.com/blog/finicky-whiskers-part-3-microservices) +* [Part 4: Spin, Containers, Nomad, and Infrastructure](https://www.fermyon.com/blog/finicky-whiskers-part-4-infrastructure) + +## Accessing External APIs + +Chances are whatever you are building will want to talk to other endpoints on the web. If so, Spin's HTTP library can help. The following article explains how to access external APIs from within Spin applications. + +- [Accessing external APIs](https://www.fermyon.com/blog/spin-rest-apis) + +## SQLite Storage Using Javascript + +Check out our [NoOps and Serverless Are the Perfect Pair](https://www.fermyon.com/blog/noops-and-serverless-are-the-perfect-pair) blog article that provides an SQLite database example (using Javascript). + +## Next Steps + +- Try [running your application locally](running-apps) +- Learn about how to [configure your application at runtime](dynamic-configuration) +- Look up details in the [application manifest reference](manifest-reference) diff --git a/content/v4/serverless-ai-api-guide.md b/content/v4/serverless-ai-api-guide.md new file mode 100644 index 00000000..a4c69518 --- /dev/null +++ b/content/v4/serverless-ai-api-guide.md @@ -0,0 +1,259 @@ +title = "Serverless AI API" +template = "main" +date = "2023-11-04T00:00:01Z" +enable_shortcodes = true +[extra] +url = "https://github.com/spinframework/spin-docs/blob/main/content/v4/serverless-ai-api-guide.md" + +--- +- [Using Serverless AI From Applications](#using-serverless-ai-from-applications) + - [Configuration](#configuration) + - [File Structure](#file-structure) +- [Serverless AI Interface](#serverless-ai-interface) +- [Troubleshooting](#troubleshooting) + - [Error "Local LLM operations are not supported in this version of Spin"](#error-local-llm-operations-are-not-supported-in-this-version-of-spin) + +The nature of AI and LLM workloads on already trained models lends itself very naturally to a serverless-style architecture. As a framework for building and deploying serverless applications, Spin provides an interface for you to perform AI inference within Spin applications. + +## Using Serverless AI From Applications + +### Configuration + +By default, a given component of a Spin application will not have access to any Serverless AI models. Access must be provided explicitly via the Spin application's manifest (the `spin.toml` file). For example, an individual component in a Spin application could be given access to the llama2-chat model by adding the following `ai_models` configuration inside the specific `[component.(name)]` section: + + + +```toml +// -- snip -- + +[component.please-send-the-codes] +ai_models = ["codellama-instruct"] + +// -- snip -- +``` + +> Spin supports the models of the Llama architecture for inferencing and "all-minilm-l6-v2" for generating embeddings. + +### File Structure + +By default, the Spin framework will expect any already trained model files (which are configured as per the previous section) to be downloaded by the user and made available inside a `.spin/ai-models/` file path of a given application. +Within the `.spin/ai-models` directory, models of the same architecture (e.g. `llama`) must be grouped under a directory with the same name as the architecture. +Within an architecture directory, each individual model (e.g. `llama2-chat`, `codellama-instruct`) must be placed under a folder with the same name as the model. So for any given model, that files for the model are placed in the directory `.spin/ai-models//`. For example: + +```bash +code-generator-rs/.spin/ai-models/llama/codellama-instruct/safetensors +code-generator-rs/.spin/ai-models/llama/codellama-instruct/config.json +code-generator-rs/.spin/ai-models/llama/codellama-instruct/tokenizer.json +``` + +> For embeddings models, it is expected that both a `tokenizer.json` and a `model.safetensors` are located in the directory named after the model. For example, for the `foo-bar-baz` model, Spin will look in the `.spin/ai-models/foo-bar-baz` directory for `tokenizer.json` and a `model.safetensors`. + +## Serverless AI Interface + +The Spin SDK surfaces the Serverless AI interface to a variety of different languages. See the [Language Support Overview](./language-support-overview) to see if your specific language is supported. + +The set of operations is common across all supporting language SDKs: + +| Operation | Parameters | Returns | Behavior | +|:-----|:----------------|:-------|:----------------| +| `infer` | model`string`
prompt`string` | `string` | The `infer` is performed on a specific model.

The name of the model is the first parameter provided (i.e. `llama2-chat`, `codellama-instruct`, or other; passed in as a `string`).

The second parameter is a prompt; passed in as a `string`.
| +| `infer_with_options` | model`string`
prompt`string`
params`list` | `string` | The `infer_with_options` is performed on a specific model.

The name of the model is the first parameter provided (i.e. `llama2-chat`, `codellama-instruct`, or other; passed in as a `string`).

The second parameter is a prompt; passed in as a `string`.

The third parameter is a mix of float and unsigned integers relating to inferencing parameters in this order:

- `max-tokens` (unsigned 32 integer) Note: the backing implementation may return less tokens.
Default is 100

- `repeat-penalty` (float 32) The amount the model should avoid repeating tokens.
Default is 1.1

- `repeat-penalty-last-n-token-count` (unsigned 32 integer) The number of tokens the model should apply the repeat penalty to.
Default is 64

- `temperature` (float 32) The randomness with which the next token is selected.
Default is 0.8

- `top-k` (unsigned 32 integer) The number of possible next tokens the model will choose from.
Default is 40

- `top-p` (float 32) The probability total of next tokens the model will choose from.
Default is 0.9

The result from `infer_with_options` is a `string` | +| `generate-embeddings` | model`string`
prompt`list` | `string` | The `generate-embeddings` is performed on a specific model.

The name of the model is the first parameter provided (i.e. `all-minilm-l6-v2`, passed in as a `string`).

The second parameter is a prompt; passed in as a `list` of `string`s.

The result from `generate-embeddings` is a two-dimension array containing float32 type values only | + +The exact detail of calling these operations from your application depends on your language: + +{{ tabs "sdk-type" }} + +{{ startTab "Rust"}} + +> [**Want to go straight to the reference documentation?** Find it here.](https://docs.rs/spin-sdk/latest/spin_sdk/llm/index.html) + +To use Serverless AI functions, the `llm` module from the Spin SDK provides the methods. The following snippet is from the [Rust code generation example](https://github.com/fermyon/ai-examples/tree/main/code-generator-rs): + + + +```rust +use spin_sdk::{ + http::{IntoResponse, Request, Response}, + llm, +}; + +// -- snip -- + +async fn handle_code(req: Request) -> anyhow::Result { + // -- snip -- + + let result = llm::infer_with_options( + llm::InferencingModel::CodellamaInstruct, + &prompt, + llm::InferencingParams { + max_tokens: 400, + repeat_penalty: 1.1, + repeat_penalty_last_n_token_count: 64, + temperature: 0.8, + top_k: 40, + top_p: 0.9, + }, + )?; + + // -- snip -- +} + +``` + +**General Notes** + +The `infer_with_options` examples, operation: + +- The above example takes the model name `llm::InferencingModel::CodellamaInstruct` as input. From an interface point of view, the model name is technically an alias for a string (to maximize future compatibility as users want to support more and different types of models). +- The second parameter is a prompt (string) from whoever/whatever is making the request to the `handle_code()` function. +- A third, optional, parameter which is an interface allows you to specify parameters such as `max_tokens`, `repeat_penalty`, `repeat_penalty_last_n_token_count`, `temperature`, `top_k` and `top_p`. +- The return value (the `inferencing-result` record) contains a text field of type `string`. Ideally, this would be a `stream` that would allow streaming inferencing results back to the user, but alas streaming support is not yet ready for use so we leave that as a possible future backward incompatible change. + +{{ blockEnd }} + +{{ startTab "Typescript"}} + +> [**Want to go straight to the reference documentation?** Find it here.](https://spinframework.github.io/spin-js-sdk/) + +To use Serverless AI functions, [the `@spinframework/spin-llm` pacakge](https://spinframework.github.io/spin-js-sdk/modules/_spinframework_spin-llm.html) provides two methods: `infer` and `generateEmbeddings`. For example: + +```javascript +import { AutoRouter } from 'itty-router'; +import { inferencingModels, EmbeddingModels, infer, generateEmbeddings } from '@spinframework/spin-llm'; + +let router = AutoRouter(); + +router + .get("/", () => { + let embeddings = generateEmbeddings(EmbeddingModels.AllMiniLmL6V2, ["someString"]) + console.log(embeddings.embeddings) + let result = infer(InferencingModels.Llama2Chat, prompt) + + return new Response(result.text); + }) + +//@ts-ignore +addEventListener('fetch', async (event: FetchEvent) => { + event.respondWith(router.fetch(event.request)); +}); +``` + +**General Notes** + +`infer` operation: + +- It takes in the following arguments - model name, prompt and a optional third parameter for inferencing options. +- The model name is a string. There are enums for the inbuilt models (llama2-chat and codellama) in [`InferencingModels`](https://spinframework.github.io/spin-js-sdk/enums/_spinframework_spin-llm.InferencingModels.html). +- The optional third parameter which is an [InferencingOptions](https://spinframework.github.io/spin-js-sdk/interfaces/_spinframework_spin-llm.InferencingOptions.html) interface allows you to specify parameters such as `maxTokens`, `repeatPenalty`, `repeatPenaltyLastNTokenCount`, `temperature`, `topK`, `topP`. +- The return value is an [`InferenceResult`](https://spinframework.github.io/spin-js-sdk/interfaces/_spinframework_spin-llm.InferenceResult.html). + +`generateEmbeddings` operation: + +- It takes two arguments - model name and list of strings to generate the embeddings for. +- The model name is a string. There are enums for the inbuilt models (AllMiniLmL6V2) in [`EmbeddingModels`](https://spinframework.github.io/spin-js-sdk/enums/_spinframework_spin-llm.EmbeddingModels.html). +- The return value is an [`EmbeddingResult`](https://spinframework.github.io/spin-js-sdk/interfaces/_spinframework_spin-llm.EmbeddingResult.html) + +{{ blockEnd }} + +{{ startTab "Python"}} + +> [**Want to go straight to the reference documentation?** Find it here.](https://spinframework.github.io/spin-python-sdk/v3/llm.html) + +```python +from spin_sdk import http +from spin_sdk.http import Request, Response +from spin_sdk import llm + +class IncomingHandler(http.IncomingHandler): + def handle_request(self, request: Request) -> Response: + prompt="You are a stand up comedy writer. Tell me a joke." + result = llm.infer("llama2-chat", prompt) + return Response(200, + {"content-type": "application/json"}, + bytes(result.text, "utf-8")) +``` + +**General Notes** + +[`infer` operation](https://spinframework.github.io/spin-python-sdk/v3/llm.html#spin_sdk.llm.infer): + +- The model name is passed in as a string (as shown above; `"llama2-chat"`). +[`infer_with_options` operation](https://spinframework.github.io/spin-python-sdk/v3/llm.html#spin_sdk.llm.infer_with_options): + +- It takes in a model name, prompt text, and optionally a [parameter object](https://spinframework.github.io/spin-python-sdk/v3/llm.html#spin_sdk.llm.InferencingParams) to control the inferencing. + +{{ blockEnd }} + +{{ startTab "TinyGo"}} + +> [**Want to go straight to the reference documentation?** Find it here.](https://pkg.go.dev/github.com/spinframework/spin-go-sdk/v2@v2.2.1/llm) + +Serverless AI functions are available in the `github.com/spinframework/spin-go-sdk/v2/llm` package. See [Go Packages](https://pkg.go.dev/github.com/spinframework/spin-go-sdk/v2/llm) for reference documentation. For example: + +```go +package main + +import ( + "fmt" + "net/http" + + spinhttp "github.com/spinframework/spin-go-sdk/v2/http" + "github.com/spinframework/spin-go-sdk/v2/llm" +) + +func init() { + spinhttp.Handle(func(w http.ResponseWriter, r *http.Request) { + result, err := llm.Infer("llama2-chat", "What is a good prompt?", nil) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + fmt.Printf("Prompt tokens: %d\n", result.Usage.PromptTokenCount) + fmt.Printf("Generated tokens: %d\n", result.Usage.GeneratedTokenCount) + fmt.Fprintf(w, "%s\n", result.Text) + + embeddings, err := llm.GenerateEmbeddings("all-minilm-l6-v2", []string{"Hello world"}) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + fmt.Printf("Prompt Tokens: %d\n", embeddings.Usage.PromptTokenCount) + fmt.Printf("%v\n", embeddings.Embeddings) + }) +} +``` + +**General Notes** + +`infer` operation: + +- It takes in the following arguments - model name, prompt and an optional third parameter for inferencing options (pass `nil` if you don't want to specify it). +- The model name is a string. +- The params allows you to specify `MaxTokens`, `RepeatPenalty`, `RepeatPenaltyLastNTokenCount`, `Temperature`, `TopK`, `TopP`. +- It returns a result struct with a `Text` field that contains the answer and a `Usage` field that contains metadata about the operation. + +`generateEmbeddings` operation: + +- It takes two arguments - model name and list of strings to generate the embeddings for. +- The model name is a string: `all-minilm-l6-v2` +- It returns a result struct with an `Embeddings` field that contains the `[][]float32` embeddings and a `Usage` field that contains metadata about the operation. + +{{ blockEnd }} + +{{ blockEnd }} + +## Troubleshooting + +### Error "Local LLM operations are not supported in this version of Spin" + +If you see "Local LLM operations are not supported in this version of Spin", then your copy of Spin has been built without local LLM support. + +> The term "version" in the error message refers to how the software you are using built the Spin runtime, not to the numeric version of the runtime itself. + +Most Spin builds support local LLMs as described above. However, the models built into Spin do not build on some combinations of platforms (for example, there are known problems with the aarch64/musl combination). This may cause some environments that embed Spin to disable the local LLM feature altogether. (For examples, some versions of the `containerd-spin-shim` did this.) In such cases, you will see the error above. + +In such cases, you can: + +* See if there is another Spin build available for your platform. All current builds from the [Spin GitHub repository](https://github.com/spinframework/spin) or [Spin installer support](./install.md) support local LLMs. +* Use the [`cloud-gpu` plugin and runtime config option](./serverless-ai-hello-world.md#building-and-deploying-your-spin-application) to have LLM inferencing serviced in Fermyon Cloud instead of locally. diff --git a/content/v4/spin-application-structure.md b/content/v4/spin-application-structure.md new file mode 100644 index 00000000..ddf23728 --- /dev/null +++ b/content/v4/spin-application-structure.md @@ -0,0 +1,215 @@ +title = "Application Structure" +template = "main" +date = "2023-11-04T00:00:01Z" +enable_shortcodes = true +[extra] +url = "https://github.com/spinframework/spin-docs/blob/main/content/v4/spin-application-structure.md" +keywords = "structure" + +--- + +A Spin application can contain multiple components. If more than one component is built from source, you should consider how to organise the application project. + +> If you have multiple components, but all except one is downloaded from a remote source such as a URL, you don't need to worry about the additional structure recommended on this page here. For example, an application with one custom component and two fileserver components doesn't need to start from the `empty` template. + +The discussion on this page assumes that you know from the start that you are going to need to build multiple components. If you've already started a project, you may need to move some existing code around when you add your second built-from-source component. For information about this, see [this Spin Application Structure blog post](https://www.fermyon.com/blog/spin-application-structure) or [this video](https://www.youtube.com/watch?v=QQD-qodabSc). + +> The blog post and video show manifest changes in the Spin 1 format. In the Spin 2 manifest format, the changes are similar, affecting the component `source` and `build` sections. + +## Recommended Application Structure + +If we start with a blank canvas and use the `http-empty` template we will get a new Spin application: + + + +```console +$ spin new -t http-empty +Enter a name for your new application: myapp +Description: My application +``` + +The above command will provide an empty structure, as shown below: + + + +```console +└── myapp + └── spin.toml +``` + +To add new components to the application, we simply move into the `myapp` directory and begin to add components using the `spin add` subcommand: + + + +```console +$ spin add -t http-rust +Enter a name for your new component: first-http-rust-component +Description: The first of many new components +HTTP path: /first/... +$ spin add -t http-rust +Enter a name for your new component: second-http-rust-component +Description: The second of many new components +HTTP path: /second/... +``` + +After adding two new components, we can see the visual representation of our application. Notice the symmetry; there is no hierarchy or nesting of components: + + + +```console +. +β”œβ”€β”€ first-http-rust-component +β”‚ β”œβ”€β”€ Cargo.toml +β”‚ └── src +β”‚ └── lib.rs +β”œβ”€β”€ second-http-rust-component +β”‚ β”œβ”€β”€ Cargo.toml +β”‚ └── src +β”‚ └── lib.rs +└── spin.toml +``` + +To customize each of the two components, we can modify the `lib.rs` (Rust source code) of each component: + +```rust +use spin_sdk::http::{IntoResponse, Request, Response}; +use spin_sdk::http_service; + +/// A simple Spin HTTP component. +#[http_service] +async fn handle_first_http_rust_component(req: Request) -> anyhow::Result { + println!("Handling request to {:?}", req.headers().get("spin-full-url")); + Ok(Response::builder() + .status(200) + .header("content-type", "text/plain") + .body("Hello, First Component".to_string())?) +} +``` + +```rust +#[http_service] +async fn handle_second_http_rust_component(req: Request) -> anyhow::Result { + println!("Handling request to {:?}", req.headers().get("spin-full-url")); + Ok(Response::builder() + .status(200) + .header("content-type", "text/plain") + .body("Hello, Second Component".to_string())?) +} +``` + +As an additional example of adding more components, let's add a new static file server component: + + + +```console +$ spin add -t static-fileserver +Enter a name for your new component: assets +HTTP path: /static/... +Directory containing the files to serve: assets +``` + +After the static file server component is added, we create the `assets` directory (our local directory containing the files to serve) and then add some static content into the `assets` directory to be served: + + + +```console +$ mkdir assets +$ cp ~/Desktop/my-static-image.jpg assets +$ cp ~/Desktop/old.txt assets +$ cp ~/Desktop/new.txt assets +``` + +> The above commands are just an example that assumes you have the image (`my-static-image.jpg`) and two text files (`old.txt` & `new.txt`) on your Desktop. + +Why stop there? We can add even more functionality to our application. Let's now add a `redirect` component to redirect requests made to `/static/old.txt` and forward those through to `/static/new.txt`: + + + +```console +$ spin add -t redirect +Enter a name for your new component: additional-component-redirect +Redirect from: /static/old.txt +Redirect to: /static/new.txt +``` + +We now have 4 separate components scaffolded for us by Spin. Note the application manifest (the `spin.toml` file) is correctly configured based on our `spin add` commands: + + + +```toml +spin_manifest_version = 2 + +[application] +name = "myapp" +version = "0.1.0" + +[[trigger.http]] +route = "/first/..." +component = "first-http-rust-component" + +[component.first-http-rust-component] +source = "first-http-rust-component/target/wasm32-wasip2/release/first_http_rust_component.wasm" +allowed_outbound_hosts = [] +[component.first-http-rust-component.build] +command = "cargo build --target wasm32-wasip2 --release" +workdir = "first-http-rust-component" +watch = ["src/**/*.rs", "Cargo.toml"] + +[[trigger.http]] +route = "/second/..." +component = "second-http-rust-component" + +[component.second-http-rust-component] +source = "second-http-rust-component/target/wasm32-wasip2/release/second_http_rust_component.wasm" +allowed_outbound_hosts = [] +[component.second-http-rust-component.build] +command = "cargo build --target wasm32-wasip2 --release" +workdir = "second-http-rust-component" +watch = ["src/**/*.rs", "Cargo.toml"] + +[[trigger.http]] +route = "/static/..." +component = "assets" + +[component.assets] +source = { url = "https://github.com/spinframework/spin-fileserver/releases/download/v0.1.0/spin_static_fs.wasm", digest = "sha256:96c76d9af86420b39eb6cd7be5550e3cb5d4cc4de572ce0fd1f6a29471536cb4" } +files = [ { source = "assets", destination = "/" } ] + +[[trigger.http]] +component = "additional-component-redirect" +route = "/static/old.txt" + +[component.additional-component-redirect] +source = { url = "https://github.com/spinframework/spin-redirect/releases/download/v0.1.0/redirect.wasm", digest = "sha256:8bee959843f28fef2a02164f5840477db81d350877e1c22cb524f41363468e52" } +environment = { DESTINATION = "/static/new.txt" } +``` + +Also, note that the application's folder structure, scaffolded for us by Spin via the spin add commands, is symmetrical and shows no nesting of components: + + + +```console +β”œβ”€β”€ assets +β”‚ β”œβ”€β”€ my-static-image.jpg +β”‚ β”œβ”€β”€ new.txt +β”‚ └── old.txt +β”œβ”€β”€ first-http-rust-component +β”‚ β”œβ”€β”€ Cargo.toml +β”‚ └── src +β”‚ └── lib.rs +β”œβ”€β”€ second-http-rust-component +β”‚ β”œβ”€β”€ Cargo.toml +β”‚ └── src +β”‚ └── lib.rs +└── spin.toml +``` + +This is the recommended Spin application structure. + +## Next Steps + +- Learn about how to [build your Spin application code](build) +- Try [running your application locally](running-apps) +- Discover how Spin application authors [design and organise applications](see-what-people-have-built-with-spin) +- Learn about how to [configure your application at runtime](dynamic-configuration) +- Look up details in the [application manifest reference](manifest-reference) diff --git a/content/v4/sqlite-api-guide.md b/content/v4/sqlite-api-guide.md new file mode 100644 index 00000000..979d7fcd --- /dev/null +++ b/content/v4/sqlite-api-guide.md @@ -0,0 +1,298 @@ +title = "SQLite Storage" +template = "main" +date = "2023-11-04T00:00:01Z" +enable_shortcodes = true +[extra] +url = "https://github.com/spinframework/spin-docs/blob/main/content/v4/sqlite-api-guide.md" + +--- +- [Granting SQLite Database Permissions to Components](#granting-sqlite-database-permissions-to-components) +- [Using SQLite Storage From Applications](#using-sqlite-storage-from-applications) +- [Preparing an SQLite Database](#preparing-an-sqlite-database) +- [Custom SQLite Databases](#custom-sqlite-databases) + - [Granting Access to Custom SQLite Databases](#granting-access-to-custom-sqlite-databases) + +Spin provides an interface for you to persist data in an SQLite Database managed by Spin. This database allows Spin developers to persist relational data across application invocations. + +{{ details "Why do I need a Spin interface? Why can't I just use my own external database?" "You can absolutely still use your own external database either with the [MySQL or Postgres APIs](./rdbms-storage). However, if you're interested in quick, local relational storage without any infrastructure set-up then Spin's SQLite Database is a great option." }} + +## Granting SQLite Database Permissions to Components + +By default, a given component of an app will not have access to any SQLite Databases. Access must be granted specifically to each component via the component manifest. For example, a component could be given access to the default store using: + +```toml +[component.example] +sqlite_databases = ["default"] +``` + +> Note: To deploy your Database application to Fermyon Cloud using `spin cloud deploy`, see the [SQLite Database](https://developer.fermyon.com/cloud/noops-sql-db#accessing-private-beta) section in the documentation. It covers signing up for the private beta and setting up your Cloud database tables and initial data. + +## Using SQLite Storage From Applications + +The Spin SDK surfaces the Spin SQLite Database interface to your language. + +The set of operations is common across all SDKs: + +| Operation | Parameters | Returns | Behavior | +|------------|------------|---------|----------| +| `open` | name | connection | Open the database with the specified name. If `name` is the string "default", the default database is opened, provided that the component that was granted access in the component manifest from `spin.toml`. Otherwise, `name` must refer to a store defined and configured in a [runtime configuration file](./dynamic-configuration.md#sqlite-storage-runtime-configuration) supplied with the application.| +| `execute` | connection, sql, parameters | database records | Executes the SQL statement and returns the results of the statement. SELECT statements typically return records or scalars. INSERT, UPDATE, and DELETE statements typically return empty result sets, but may return values in some cases. The `execute` operation recognizes the [SQLite dialect of SQL](https://www.sqlite.org/lang.html). | +| `last-insert-rowid` | connection | integer | The SQLite rowid of the recent successful INSERT on the connection, or 0 if there has not yet been an INSERT on the connection. | +| `changes` | connection | integer | The number of rows modified, inserted or deleted by the most recently completed INSERT, UPDATE or DELETE statement on the connection. | + +The exact detail of calling these operations from your application depends on your language: + +{{ tabs "sdk-type" }} + +{{ startTab "Rust"}} + +Please note, we use `serde` in this Rust example, so please add `serde` as a dependency in your application's `Cargo.toml` file: + +```toml +[dependencies] +serde = {version = "1.0", features = ["derive"]} +serde_json = "1.0" +``` + +> [**Want to go straight to the reference documentation?** Find it here.](https://docs.rs/spin-sdk/latest/spin_sdk/sqlite/index.html) + +SQLite functions are available in the `spin_sdk::sqlite` module. The function names match the operations above. For example: + +```rust +use anyhow::Result; +use serde::Serialize; +use spin_sdk::{ + http::{FullBody, Request, Response, IntoResponse}, + http_service, + sqlite::{Connection, Value}, +}; + +#[http_service] +async fn handle_request(req: Request) -> Result { + let connection = Connection::open_default().await?; + + let execute_params = [ + Value::Text("Try out Spin SQLite".to_owned()), + Value::Text("Friday".to_owned()), + ]; + connection.execute( + "INSERT INTO todos (description, due) VALUES (?, ?)", + execute_params.as_slice(), + ).await?; + + let (columns, mut rows, result) = connection.execute( + "SELECT id, description, due FROM todos", + &[] + ).await?; + + let mut todos = vec![]; + while let Some(row) = rows.next().await? { + todos.push(ToDo { + id: row.get::("id").unwrap(), + description: row.get::<&str>("description").unwrap().to_owned(), + due: row.get::<&str>("due").unwrap().to_owned(), + }); + } + + let body = serde_json::to_vec(&todos)?; + Ok(Response::builder() + .status(200) + .header("content-type", "text/plain") + .body(FullBody::new(Bytes::from_owner(body)))?) +} + +// Helper for returning the query results as JSON +#[derive(Serialize)] +struct ToDo { + id: u32, + description: String, + due: String, +} +``` + +**General Notes** +* All functions are on the `spin_sdk::sqlite::Connection` type. +* Parameters are instances of the `Value` enum; you must wrap raw values in this type. +* The `execute` function returns a `QueryResult`. To iterate over the rows use the `rows()` function. This returns an iterator; use `collect()` if you want to load it all into a collection. +* The values in rows are instances of the `Value` enum. However, you can use `row.get(column_name)` to extract a specific column from a row. `get` casts the database value to the target Rust type. If the compiler can't infer the target type, write `row.get::<&str>(column_name)` (or whatever the desired type is). +* All functions wrap the return in `Result`, with the error type being `spin_sdk::sqlite::Error`. + +{{ blockEnd }} + +{{ startTab "Typescript"}} + +> [**Want to go straight to the reference documentation?** Find it here.](https://spinframework.github.io/spin-js-sdk/) + +To use SQLite functions, use [the `Sqlite.open` or `Sqlite.openDefault` function](https://spinframework.github.io/spin-js-sdk/modules/_spinframework_spin-sqlite.html) to obtain [a `SqliteConnection` object](https://spinframework.github.io/spin-js-sdk/interfaces/_spinframework_spin-sqlite.SqliteConnection.html). `SqliteConnection` provides the `execute` method as described above. For example: + +```javascript +import { AutoRouter } from 'itty-router'; +import { Sqlite } from '@spinframework/spin-sqlite'; + +let router = AutoRouter(); +router + .get("/", () => { + let conn = Sqlite.openDefault(); + let result = conn.execute("SELECT * FROM todos WHERE id > (?);", [1]); + + return new Response(JSON.stringify(result, null, 2)); + }) + +//@ts-ignore +addEventListener('fetch', async (event: FetchEvent) => { + event.respondWith(router.fetch(event.request)); +}); + +``` + +**General Notes** +* The `execute` function returns an object with `rows` and `columns` properties. `columns` is an array of strings representing column names. `rows` is an array of rows, each of which is an object containing Javascript values keyed using the column names. +* The `SqliteConnection` object doesn't surface the `close` function. + +{{ blockEnd }} + +{{ startTab "Python"}} + +> [**Want to go straight to the reference documentation?** Find it here.](https://spinframework.github.io/spin-python-sdk/v3/sqlite.html) + +To use SQLite functions, use the `sqlite` module in the Python SDK. The [`sqlite_open`](https://spinframework.github.io/spin-python-sdk/v3/sqlite.html#spin_sdk.sqlite.open) and [`sqlite_open_default`](https://spinframework.github.io/spin-python-sdk/v3/sqlite.html#spin_sdk.sqlite.open_default) functions return a [connection object](https://spinframework.github.io/spin-python-sdk/v3/wit/imports/sqlite.html#spin_sdk.wit.imports.sqlite.Connection). The connection object provides the [`execute` method](https://spinframework.github.io/spin-python-sdk/v3/wit/imports/sqlite.html#spin_sdk.wit.imports.sqlite.Connection.execute) as described above. For example: + +```python +from spin_sdk import http, sqlite +from spin_sdk.http import Request, Response +from spin_sdk.sqlite import ValueInteger + +class IncomingHandler(http.IncomingHandler): + def handle_request(self, request: Request) -> Response: + with sqlite.open_default() as db: + result = db.execute("SELECT * FROM todos WHERE id > (?);", [ValueInteger(1)]) + rows = result.rows + + return Response( + 200, + {"content-type": "text/plain"}, + bytes(str(rows), "utf-8") + ) +``` + +**General Notes** +* The `execute` method returns [a `QueryResult` object](https://spinframework.github.io/spin-python-sdk/v3/wit/imports/sqlite.html#spin_sdk.wit.imports.sqlite.QueryResult) with `rows` and `columns` methods. `columns` returns a list of strings representing column names. `rows` is an array of rows, each of which is an array of [`RowResult`](https://spinframework.github.io/spin-python-sdk/v3/wit/imports/sqlite.html#spin_sdk.wit.imports.sqlite.RowResult) in the same order as `columns`. +* The connection object doesn't surface the `close` function. +* Errors are surfaced as exceptions. + +{{ blockEnd }} + +{{ startTab "TinyGo"}} + +> [**Want to go straight to the reference documentation?** Find it here.](https://pkg.go.dev/github.com/spinframework/spin-go-sdk/v2@v2.2.1/sqlite) + +The Go SDK is implemented as a driver for the standard library's [database/sql](https://pkg.go.dev/database/sql) interface. + +```go +package main + +import ( + "encoding/json" + "net/http" + + spinhttp "github.com/spinframework/spin-go-sdk/v2/http" + "github.com/spinframework/spin-go-sdk/v2/sqlite" +) + +type Todo struct { + ID string + Description string + Due string +} + +func init() { + spinhttp.Handle(func(w http.ResponseWriter, r *http.Request) { + db := sqlite.Open("default") + defer db.Close() + + _, err := db.Exec("INSERT INTO todos (description, due) VALUES (?, ?)", "Try out Spin SQLite", "Friday") + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + rows, err := db.Query("SELECT id, description, due FROM todos") + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + var todos []*Todo + for rows.Next() { + var todo Todo + if err := rows.Scan(&todo.ID, &todo.Description, &todo.Due); err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + todos = append(todos, &todo) + } + json.NewEncoder(w).Encode(todos) + }) +} +``` + +**General Notes** + +A convenience function `sqlite.Open()` is provided to create a database connection. Because the `http.Handle` function is inside the `init()` function the Spin SQLite driver cannot be initialized the same way as other drivers using [sql.Open](https://pkg.go.dev/database/sql#Open). + +{{ blockEnd }} + +{{ blockEnd }} + +## Preparing an SQLite Database + +Although Spin provides SQLite as a built-in database, SQLite still needs you to create its tables. In most cases, the most convenient way to do this is to use the `spin up --sqlite` option to run whatever SQL statements you need before your application starts. This is typically used to create or alter tables, but can be used for whatever other maintenance or troubleshooting tasks you need. + +You can run a SQL script from a file using the `@filename` syntax: + + + +```bash +spin up --sqlite @migration.sql +``` + +Or you can pass SQL statements directly on the command line as a (quoted) string: + + + +```bash +spin up --sqlite "CREATE TABLE IF NOT EXISTS todos (id INTEGER PRIMARY KEY AUTOINCREMENT, description TEXT NOT NULL, due TEXT NOT NULL)" +``` + +As with runtime operations, this flag uses the [SQLite dialect of SQL](https://www.sqlite.org/lang.html). + +You can provide the `--sqlite` flag more than once; Spin runs the statements (or files) in the order you provide them, and waits for each to complete before running the next. + +> It's also possible to create tables from your Wasm components using the usual `execute` function. That can end up mingling your "hot path" application logic with database maintenance code; decide which approach is best based on your application's needs. + +## Custom SQLite Databases + +Spin defines a database named `"default"` and provides automatic backing storage. If you need to customize Spin with additional databases, or to change the backing storage for the default database, you can do so via the `--runtime-config-file` flag and the `runtime-config.toml` file. See [SQLite Database Runtime Configuration](./dynamic-configuration#sqlite-storage-runtime-configuration) for details. + +### Granting Access to Custom SQLite Databases + +As mentioned above, by default, a given component of an app will not have access to any SQLite Databases. Access must be granted specifically to each component via the component manifest, using the `component.sqlite_databases` field in the manifest. + +Components can be given access to different databases, and may be granted access to more than one database. For example: + +```toml +# c1 has no access to any databases +[component.example] +name = "c1" + +# c2 can use the default database, but no custom databases +[component.example] +name = "c2" +sqlite_databases = ["default"] + +# c3 can use the custom databases "marketing" and "sales", which must be +# defined in the runtime config file, but cannot use the default database +[component.example] +name = "c3" +sqlite_databases = ["marketing", "sales"] +``` diff --git a/content/v4/template-authoring.md b/content/v4/template-authoring.md new file mode 100644 index 00000000..03a22874 --- /dev/null +++ b/content/v4/template-authoring.md @@ -0,0 +1,149 @@ +title = "Creating Spin templates" +template = "main" +date = "2023-11-04T00:00:01Z" +[extra] +url = "https://github.com/spinframework/spin-docs/blob/main/content/v4/template-authoring.md" + +--- +- [Authoring the Content](#authoring-the-content) + - [Expression Syntax](#expression-syntax) +- [Authoring the Manifest](#authoring-the-manifest) +- [Supporting `spin add`](#supporting-spin-add) +- [Hosting Templates in Git](#hosting-templates-in-git) + +Spin templates allow a Spin developer to quickly create the skeleton of an +application or component, ready for the application logic to be filled in. + +A template consists of two directories, `content` and `metadata`. + +* The `content` directory contains all the files you'd like to be copied into + the Spin application directory, such as source code, the `spin.toml` file, + standard assets, precompiled modules, etc. These files can contain placeholders + so the user of the template can customize the end result. +* The `metadata` directory contains the files that control how the template is + instantiated. In this version of Spin, the only file in this directory + should be the _template manifest_. + +For examples of the directory contents, see the `templates` directory in the +[Spin GitHub repository](https://github.com/spinframework/spin). + +Templates must always be shared in a `templates` directory. This allows the +installer to locate them in repos that contain other content. + +## Authoring the Content + +Copy all the files that you want to be copied as part of the template into +the `content` directory. If you do nothing more, they will be copied +verbatim. Often, though, you'll want to allow the user to put their own +values in - for example, a project name, or an HTTP route. + +To do this, replace the text you want the user to be able to substitute +with an expression of the form `{{parameter-name}}`, where `parameter-name` +is an identifier of your choice. **You will need to add an entry to +the manifest matching this name** - see below. + +You can reuse a parameter in more than one place - it will be prompted only once and will get the same value in each place. + +You can also transform the user value by specifying a filter after a bar: +`{{parameter-name | filter-name}}`. This is particularly useful when you +want to conform to specific language conventions. The following filters +are supported: + +| Name | Effect | +|---------------|--------| +| `kebab_case` | Transforms input into kebab case, e.g. `My Application` to `my-application` | +| `snake_case` | Transforms input into snake case, e.g. `My Application` to `my_application` | +| `pascal_case` | Transforms input into Pascal case, e.g. `my application` to `MyApplication` | + +### Expression Syntax + +Content uses [the Liquid template language](https://shopify.github.io/liquid/). See the Liquid documentation for the available syntax and control tags. + +A common pitfall occurs because some entries in `spin.toml`, such as [component variable templates](variables), use the same double-brace syntax as Liquid does. If you want to generate a line such as `my-secret = "{{ secret }}"`, you must escape the double braces, for example using the Liquid [`raw` tag](https://shopify.github.io/liquid/tags/template/). If you don't do this, Liquid will look for a template parameter called `secret` instead! + +## Authoring the Manifest + +The template manifest is a TOML file. It must be named `spin-template.toml`: + + + +```toml +manifest_version = "1" +id = "my-application" +description = "An application" +tags = ["my-tag"] + +[parameters] +# Example parameter +project-name = { type = "string", prompt = "Project name" } +``` + +* `manifest_version` specifies the format this manifest follows. It must be `"1"`. +* `id` is however you want users to refer to your template in `spin new`. + It may contain letters, digits, hypens and underscores. +* `description` is optional. It is shown when displaying the template. +* `tags` is optional. These are used to enable discoverability via the Spin CLI. + For example, `spin new --tag my-tag` will prompt selection for a template containing `"my-tag"`. + +The `parameters` table is where you list the placeholders that you edited +into your content for the user to substitute. You should include an entry +for each parameter. The key is the parameter name, and the value a JSON +document that contains at minimum a `type` and `prompt`. `type` must +currently be `string`. `prompt` is displayed when prompting the user +for the value to substitute. + +The document may also have a `default`, which will be displayed to the user +and can be accepted by pressing Enter. It may also specify constraints +on what the user is allowed to enter. The following constraints are +supported: + +| Key | Value and usage | +|---------------|-----------------| +| `pattern` | A regular expression. The user input must match the regular expression to be accepted. | + +## Supporting `spin add` + +The `spin add` command lets users add your template as a new component in +an existing application. If you'd like to support this, you'll need to +add a few items to your metadata. + +* In the `metadata` directory, create a folder named `snippets`. In that + folder, create a file containing the (templated) manifest _just_ for the + component to be added. + * Don't include any application-level entries, just the component section. + * If your template contains component files, remember they will be copied + into a subdirectory, and make sure any paths reflect that. +* In the `spin-template.toml` file, add a table called `add_component`, with + the following entries: + +| Key | Value and usage | +|-----------------|-----------------| +| `snippets` | A subtable with an entry named `component`, whose value is the name of the file containing the component manifest template. (Don't include the `snippets` directory prefix - Spin knows to look in the `snippets` directory.) | +| `skip_files` | Optional array of content files that should _not_ be copied when running in "add component" mode. For example, if your template contains a `spin.toml` file, you should use this setting to exclude that, because you want to add a new entry to the existing file, not overwrite it. | +| `skip_parameters` | Optional array of parameters that Spin should _not_ prompt for when running in "add component" mode. | + +Here is an example `add_component` table from a HTTP template: + + + +```toml +[add_component] +skip_files = ["spin.toml"] +skip_parameters = ["shared-prefix"] +[add_component.snippets] +component = "component.txt" +``` + +> For examples from the Spin project, see `http-rust` and `static-fileserver`. + +## Hosting Templates in Git + +You can publish templates in a Git repo. The templates must be in the `/templates` +directory, with a subdirectory per template. + +When a user installs templates from your repo, by default Spin looks for a tag +to identify a compatible version of the templates. This tag is of the +form `spin/templates/vX.Y`, where X is the major version, and Y the minor +version, of the user's copy of Spin. For example, if the user is on +Spin 0.3.1, templates will be installed from `spin/templates/v0.3`. If this +tag does not exist, Spin installs templates from `HEAD`. diff --git a/content/v4/triggers.md b/content/v4/triggers.md new file mode 100644 index 00000000..4e160e12 --- /dev/null +++ b/content/v4/triggers.md @@ -0,0 +1,262 @@ +title = "Triggers" +template = "main" +date = "2023-11-04T00:00:01Z" +enable_shortcodes = true +[extra] +url = "https://github.com/spinframework/spin-docs/blob/main/content/v4/triggers.md" + +--- +- [Triggers and Components](#triggers-and-components) + - [Mapping a Trigger to a Named Component](#mapping-a-trigger-to-a-named-component) + - [Writing the Component Inside the Trigger](#writing-the-component-inside-the-trigger) + - [Choosing Between the Approaches](#choosing-between-the-approaches) + - [Setting Up Multiple Trigger Types](#setting-up-multiple-trigger-types) +- [Cron Trigger](#cron-trigger) + - [Cron Trigger Expressions](#cron-trigger-expressions) + - [Installing the Cron Trigger Plugin](#installing-the-cron-trigger-plugin) + - [Installing the Cron Trigger Template](#installing-the-cron-trigger-template) + - [Creating the Application](#creating-the-application) + - [Inspecting the Source Code](#inspecting-the-source-code) + - [Building and Running the Application](#building-and-running-the-application) + +A Spin _trigger_ maps an event, such as an HTTP request or a Redis pub-sub message, to a component that handles that event. + +An application can contain multiple triggers. + +In Spin 2.2 and earlier, all triggers must be of the same type. For example, an application can contain triggers for multiple HTTP routes, or for multiple Redis pub-sub channels, but not both. + +In Spin 2.3 and later, an application can contain triggers of different types. For example, a single application can serve HTTP on one or more routes, and at the same time subscribe to one or more Redis pub-sub channels. + +## Triggers and Components + +How events are specified depends on the type of trigger involved. For example, an [HTTP trigger](./http-trigger.md) is specified by the route it handles. A [Cron Trigger](#spin-cron-trigger) is specified by the schedule on which it runs. A [Redis trigger](./redis-trigger.md) is specified by the channel it monitors. A trigger always, however, has a `component` field, specifying the component that handles matching events. The `component` can be specified in two ways. + +### Mapping a Trigger to a Named Component + +An application manifest can define _named_ components in the `component` section. Each component is a WebAssembly component file (or reference) plus the supporting resources it needs, and metadata such as build information. The component name is written as part of the TOML `component` declaration. For example: + +```toml +[component.checkout] # The component's name is "checkout" +source = "target/wasm32-wasip2/release/checkout.wasm" +allowed_outbound_hosts = ["https://payment-processing.example.com"] +key_value_stores = ["default"] +[component.checkout.build] +command = "cargo build --target wasm32-wasip2 --release" +``` + +To map a trigger to a named component, specify its name in the trigger's `component` field: + +```toml +[[trigger.http]] +route = "/cart/checkout" +component = "checkout" +``` + +### Writing the Component Inside the Trigger + +Instead of writing the component in a separate section and referencing it by name, you can write it the same fields _inline_ in the trigger `component` field. For example: + +```toml +# Using inline table syntax +[[trigger.http]] +route = "/cart/..." +component = { source = "dist/cart.wasm" } + +# Nested table syntax +[[trigger.http]] +route = "/cart/..." +[trigger.http.component] +source = "target/wasm32-wasip2/release/checkout.wasm" +allowed_outbound_hosts = ["payment-processing.example.com"] +``` + +These behave in the same way: the inline table syntax is more compact for short declarations, but the nested table syntax is easier to read when there are many fields or the values are long. + +### Choosing Between the Approaches + +These ways of writing components achieve the same thing, so which should you choose? + +Named components have the following advantages: + +* Reuse. If you want two triggers to behave in the same way, they can refer to the same named component. Remember this means they are not just handled by the same Wasm file, but with the same settings. +* Named. If an error occurs, Spin can tell you the name of the component where the error happened. With inline components, Spin has to synthesize a name. This isn't a big deal in single-component apps, but makes diagnostics harder in larger apps. + +Inline components have the following advantages: + +* Compact, especially when using inline table syntax. +* One place to look. Both the trigger event and the handling details are always in the same piece of TOML. + +If you are not sure, or are not experienced, we recommend using named components at first, and adopting inline components as and when you find cases where you prefer them. + +### Setting Up Multiple Trigger Types + +In this section, we build an application that contains multiple triggers. + +Here is an example of creating an application with both HTTP and Redis triggers: + + + +```bash +# Start with an empty application +$ spin new -t http-empty multiple-trigger-example +Description: An application that handles both HTTP requests and Redis messages +# Change into to the application directory +$ cd multiple-trigger-example +# Add an HTTP trigger application +$ spin add -t http-rust rust-http-trigger-example +Description: A Rust HTTP example +HTTP path: /... +# Add a Redis trigger application +$ spin add -t redis-rust rust-redis-trigger-example +Description: A Rust redis example +Redis address: redis://localhost:6379 +Redis channel: one +``` + +The above `spin new` and `spin add` commands will scaffold a Spin manifest (`spin.toml` file) with the following triggers: + + + +```toml +spin_manifest_version = 2 + +[application] +name = "multiple-trigger-example" +version = "0.1.0" +authors = ["Your Name "] +description = "An application that handles both HTTP requests and Redis messages" + +[[trigger.http]] +route = "/..." +component = "rust-http-trigger-example" + +[component.rust-http-trigger-example] +source = "rust-http-trigger-example/target/wasm32-wasip2/release/rust_http_trigger_example.wasm" +allowed_outbound_hosts = [] +[component.rust-http-trigger-example.build] +command = "cargo build --target wasm32-wasip2 --release" +workdir = "rust-http-trigger-example" +watch = ["src/**/*.rs", "Cargo.toml"] + +[application.trigger.redis] +address = "redis://localhost:6379" + +[[trigger.redis]] +channel = "one" +component = "rust-redis-trigger-example" + +[component.rust-redis-trigger-example] +source = "rust-redis-trigger-example/target/wasm32-wasip2/release/rust_redis_trigger_example.wasm" +allowed_outbound_hosts = [] +[component.rust-redis-trigger-example.build] +command = "cargo build --target wasm32-wasip2 --release" +workdir = "rust-redis-trigger-example" +watch = ["src/**/*.rs", "Cargo.toml"] +``` + +## Cron Trigger + +Spin has experimental support for creating and running components on a schedule. Please note that there are only working Cron Trigger app samples written in [Rust](https://github.com/spinframework/spin-trigger-cron/tree/main/guest-rust) and [Python](https://github.com/spinframework/spin-trigger-cron/tree/main/guest-python) at present. + +> Please note: You can not `spin deploy` an application to Fermyon Cloud if it uses `cron` because non-HTTP triggers are not supported in Fermyon Cloud. + +Let's look at how the [experimental Cron trigger for Spin](https://github.com/spinframework/spin-trigger-cron) allows you to deploy an application that runs on a schedule. A Cron trigger maps a cron expression (a schedule) to a specific component. For example: + + + +```toml +[[trigger.cron]] +component = "hello-cron" +cron_expression = "1/2 * * * * *" +``` + +> Note: The 7th field (year) for the `cron_expression` is optional. + +### Cron Trigger Expressions + +The expression is based on the crontab (cron table) syntax whereby each line is made up of 7 fields that represent the time to execute. + +```bash +# β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ sec (0–59) +# | β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ min (0–59) +# | β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ hour (0–23) +# | β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ day of month (1–31) +# | β”‚ β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ month (1–12) +# | β”‚ β”‚ β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ day of week (0–6) +# | β”‚ β”‚ β”‚ β”‚ | β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€- year +# | β”‚ β”‚ β”‚ β”‚ | β”‚ +# | β”‚ β”‚ β”‚ β”‚ | β”‚ + 1/30 * * * * * * +``` + +> For more information about setting the schedule, please see the [Spin Cron Trigger repository](https://github.com/spinframework/spin-trigger-cron?tab=readme-ov-file#trigger-configuration). + +Let's look at a time-based workload inside a Rust application. + +### Installing the Cron Trigger Plugin + +First, we install the plugin: + +```bash +spin plugins install --url https://github.com/spinframework/spin-trigger-cron/releases/download/canary/trigger-cron.json +``` + +### Installing the Cron Trigger Template + +Then, we install the template: + +```bash +spin templates install --git https://github.com/spinframework/spin-trigger-cron +``` + +### Creating the Application + +With the plugin and template installed, we create a new application: + +```bash +spin new -t cron-rust hello_cron --accept-defaults +``` + +### Inspecting the Source Code + +The Rust source code for this application is as follows: + +```Rust +use spin_cron_sdk::{cron_component, Error, Metadata}; +use spin_sdk::variables; + +#[cron_component] +async fn handle_cron_event(metadata: Metadata) -> Result<(), Error> { + let key = variables::get("something").unwrap_or_default(); + println!( + "[{}] Hello this is me running every {}", + metadata.timestamp, key + ); + Ok(()) +} +``` + +### Building and Running the Application + +We can immediately run this pre-written (template) application and observe the time-driven execution: + +```bash +cd hello_cron +spin build --up + +Building component hello-cron with `cargo build --target wasm32-wasip2 --release` + +... + +Finished building all Spin components +[1715640447] Hello from a cron component +[1715640449] Hello from a cron component +[1715640451] Hello from a cron component +[1715640453] Hello from a cron component +[1715640455] Hello from a cron component +[1715640457] Hello from a cron component +``` + +As we can see from the above output, our application is now running and executing the function every two seconds without the need for any incoming requests or any intervention from users or other machines. + +If you would like to learn more about using the Spin Cron Trigger, please check out [the Spin Cron Trigger blog post](https://www.fermyon.com/blog/spin-cron-trigger) and the [Spin Cron Trigger GitHub repository](https://github.com/spinframework/spin-trigger-cron). diff --git a/content/v4/troubleshooting-application-dev.md b/content/v4/troubleshooting-application-dev.md new file mode 100644 index 00000000..ea995c9f --- /dev/null +++ b/content/v4/troubleshooting-application-dev.md @@ -0,0 +1,54 @@ +title = "Troubleshooting Application Development" +template = "main" +date = "2023-11-04T00:00:01Z" +enable_shortcodes = true +[extra] +url = "https://github.com/spinframework/spin-docs/blob/main/content/v4/troubleshooting-application-dev.md" + +--- + +## Spin Doctor + +The `spin doctor` command detects problems that could stop your application building and running, and can help to fix them. These include problems like invalid manifests, missing Wasm files, and missing tools. + +To troubleshoot using `spin doctor`, run the command: + + + +```bash +$ spin doctor +``` + +> If you're not in the application directory, use the `-f` flag to tell the doctor which application to check + +Spin performs a series of checks on your application. If it finds a problem, it prints a description and, if possible, offers to fix it. Here's an example where a stray keystroke has messed up the version field in the application manifest: + + + +```bash +$ spin doctor +πŸ“Ÿ The Spin Doctor is in. +🩺 Checking spin.toml... + +❗ Diagnosis: Manifest 'spin_manifest_version' must be "1", not "11" +🩹 The Spin Doctor can help! Would you like to: +> Set manifest version to "1" + Do nothing + See more details about the recommended changes +``` + +If `spin doctor` detects a problem it can fix, you can choose to accept the fix, skip it to fix manually later, or see more details before choosing. If `spin doctor` can't fix the problem, it displays the problem so you can make your own decision about how to fix it. + +> `spin doctor` is in an early stage of development, and there are many potential problems it doesn't yet check for. Please [raise an issue](https://github.com/spinframework/spin/issues/new?template=suggestion.md) if you have a problem you think `spin doctor` should check for. + +## Viewing Spin Debug Logs + +If you need to follow what Spin is doing internally, set the RUST_LOG environment variable for detailed logs, before running `spin up`: + + + +```bash +$ export RUST_LOG=spin=trace +``` + +> The variable is `RUST_LOG` no matter what language your application is written in, because this is setting the log level for Spin itself. diff --git a/content/v4/upgrade.md b/content/v4/upgrade.md new file mode 100644 index 00000000..9647e181 --- /dev/null +++ b/content/v4/upgrade.md @@ -0,0 +1,92 @@ +title = "Upgrade Spin" +template = "main" +date = "2023-11-04T00:00:01Z" +enable_shortcodes = true +[extra] +url = "https://github.com/spinframework/spin-docs/blob/main/content/v4/upgrade.md" + +--- +- [Are You on the Latest Version?](#are-you-on-the-latest-version) +- [Upgrading Using the Installer](#upgrading-using-the-installer) +- [Upgrading Using Homebrew](#upgrading-using-homebrew) +- [Upgrading Using `cargo`](#upgrading-using-cargo) +- [Upgrading by Building From Source](#upgrading-by-building-from-source) +- [If You're Not Sure](#if-youre-not-sure) +- [Troubleshooting](#troubleshooting) + - [Not Seeing the Latest Version?](#not-seeing-the-latest-version) + +Spin can be installed in many ways, and therefore the upgrade procedure can differ between users. Here are a few suggested ways to upgrade Spin to the latest version. + +## Are You on the Latest Version? + +The best way to know if you're on the latest version of Spin is to run `spin --version`: + + + +```bash +$ spin --version +``` + +You can compare the output from the above command with the [latest release release](https://github.com/spinframework/spin/releases/latest) listed in the Spin GitHub repository (which is also shown in the image below): + +![spin version image](https://img.shields.io/github/v/release/fermyon/spin) + +## Upgrading Using the Installer + +If you originally followed the documentation's [installer script method](./install#installing-spin), please revisit to reinstall. + +> The installer downloads to the current directory, which is probably not where you're running it from day-to-day. After downloading, move the downloaded Spin binary to the same place as you moved it before, for example `/usr/local/bin/spin`. You can use `which spin` (Linux and MacOS) or `where spin` (Windows) to remind yourself where that was! + +## Upgrading Using Homebrew + +If you originally installed Spin via [Homebrew](https://brew.sh/), you should use Homebrew to upgrade it: + + + +```bash +$ brew update +$ brew upgrade spinframework/tap/spin +``` + +## Upgrading Using `cargo` + +If you originally [installed Spin via `cargo install`](./install#using-cargo-to-install-spin), please revisit to reinstall. + +## Upgrading by Building From Source + +If you followed the documentation's [install from source method](./install#building-spin-from-source) please revisit to reinstall. + +## If You're Not Sure + +If you can't remember how you originally installed Spin, run `which spin` (Linux or MacOS) or `where spin` (Windows) to see where your current install is, and look it up on the following chart: + +| Current location is... | Install / upgrade method | +|----------------------------------|--------------------------------------------| +| `~/.cargo/bin/spin` | You used `cargo install`. Re-run the instructions for using Cargo. | +| `C:/Users/.../.cargo/bin/spin` | You used `cargo install`. Re-run the instructions for using Cargo. | +| `/opt/homebrew/Cellar/...` | You used Homebrew. [See above.](#homebrew) | +| `/usr/local/Cellar/...` | You used Homebrew. [See above.](#homebrew) | +| `/home/linuxbrew/.linuxbrew/...` | You used Homebrew. [See above.](#homebrew) | +| `.../target/release/spin` | You built from source. Re-run the instructions for building from source. | +| Anywhere else | You used the install script. Re-run it, then move the `spin` file to replace your current one. | + +## Troubleshooting + +If you have upgraded Spin and don't see the newer version, please consider the following. + +### Not Seeing the Latest Version? + +It may be possible that you have installed Spin **using more than one** of the above methods. In this case, the Spin executable that runs is the one that is listed first in your `PATH` system variable. + +If you have upgraded Spin yet still see the old version using `spin --version` this can be due to the order of precedence in your `PATH`. Try echoing your path to the screen and checking to see whether the location of your intended Spin executable is listed before or after other pre-existing installation paths: + +```bash +echo $PATH +/Users/my_user/.cargo/bin:/usr/local/bin +``` + +> Paths are separated by the `:` (colon) on Linux and MacOS, and `;` (semi-colon) on Windows. + +In the above case, the [Cargo install method](./install#using-cargo-to-install-spin)'s installation will take precedence over the [installer script method](./install#installing-spin)'s installation. + +In this case, you can either remove the Cargo installation of Spin using `cargo uninstall spin-cli` or update your system path to prioritize the Spin binary path that you prefer. diff --git a/content/v4/variables.md b/content/v4/variables.md new file mode 100644 index 00000000..ccc9ab67 --- /dev/null +++ b/content/v4/variables.md @@ -0,0 +1,341 @@ +title = "Application Variables" +template = "main" +date = "2023-11-04T00:00:01Z" +enable_shortcodes = true +[extra] +url = "https://github.com/spinframework/spin-docs/blob/main/content/v4/variables.md" + +--- +- [Adding Variables to Your Applications](#adding-variables-to-your-applications) +- [Using Variables From Applications](#using-variables-from-applications) +- [Providing Variable Values](#providing-variable-values) + - [Providing Variable Values from a Secrets Store](#providing-variable-values-from-a-secrets-store) + - [Providing Variable Values from the Environment](#providing-variable-values-from-the-environment) + - [Providing Variable Values on the Command Line](#providing-variable-values-on-the-command-line) +- [Troubleshooting](#troubleshooting) + +Spin supports dynamic application variables. Instead of being static, their values can be updated without modifying the application, creating a simpler experience for rotating secrets, updating API endpoints, and more. + +These variables are defined in a Spin application manifest (in the `[variables]` section), and their values can be set or overridden at runtime by an [application variables provider](./dynamic-configuration.md#application-variables-runtime-configuration), or the `--variable` flag to `spin up`. When running Spin locally, the variables provider can be [Hashicorp Vault](./dynamic-configuration.md#vault-application-variable-provider) for secrets, [Azure Key Vault](https://azure.microsoft.com/en-us/products/key-vault), or host environment variables. [See below](#setting-variable-values) for more information. + +## Adding Variables to Your Applications + +Variables are added to an application under the top-level `[variables]` section of an application manifest (`spin.toml`). Each entry must either have a default value or be marked as `required = true`. β€œRequired” entries must be [provided](./dynamic-configuration#application-variables-runtime-configuration) with a value. + +For example, consider an application which makes an outbound API call using a bearer token. To configure this using variables, you would: +* Add a `[variables]` section to the application manifest +* Add a `token` entry for the bearer token. Since there is no reasonable default value for this secret, set the variable as required with `required = true`. +* Add an `api_uri` variable. The URL _is_ known, but is useful to override, for example for A/B testing. So you can give this variable a default value with `default = "http://my-api.com"`. +The resulting application manifest looks like this: + + + +```toml +[variables] +api_token = { required = true } +api_uri = { default = "http://my-api.com" } +``` + +Variables are surfaced to a specific component by adding a `[component.(name).variables]` section to the component and referencing them within it. The `[component.(name).variables]` section contains a mapping of component variables and values. Entries can be static (like `api_version` below) or reference an updatable application variable (like `token` below) using [mustache](https://mustache.github.io/)-inspired string templates. Only components that explicitly use variables in their configuration section will get access to them. This enables only exposing variables (such as secrets) to the desired components of an application. + + + +```toml +[component.(name).variables] +token = "\{{ api_token }}" +api_uri = "\{{ api_uri }}" +api_version = "v1" +``` + +When a component variable references an application variable, its value will dynamically update as the application variable changes. For example, if the `api_token` variable is provided using the [Spin Vault provider](./dynamic-configuration.md#vault-application-variable-provider), it can be updated by changing the value in HashiCorp Vault. The next time the component gets the value of `token`, the latest value of `api_token` will be returned by the provider. See the [next section](#using-variables-from-applications) to learn how to use Spin's configuration SDKs to get configuration variables within applications. + +Variables can also be used in other sections of the application manifest that benefit from runtime configuration. In these cases, the variables are substituted at application load time rather than dynamically updated while the application is running. For example, the `allowed_outbound_hosts` can be dynamically configured using variables as follows: + + + +```toml +[component.(name)] +allowed_outbound_hosts = [ "\{{ api_uri }}" ] +``` + + +All in all, an application manifest with `api_token` and `api_uri` variables and a component that uses them would look similar to the following: + + + +```toml +spin_manifest_version = 2 + +[application] +name = "api-consumer" +version = "0.1.0" +description = "A Spin app that makes API requests" + +[variables] +api_token = { required = true } +api_uri = { default = "http://my-api.com" } + +[[trigger.http]] +route = "/..." +component = "api-consumer" + +[component.api-consumer] +source = "app.wasm" +[component.api-consumer.variables] +token = "\{{ api_token }}" +api_uri = "\{{ api_uri }}" +api_version = "v1" +``` + +## Using Variables From Applications + +The Spin SDK surfaces the Spin configuration interface to your language. The [interface](https://github.com/spinframework/spin/blob/main/wit/deps/spin-variables%403.0.0/variables.wit) consists of one operation: + +| Operation | Parameters | Returns | Behavior | +|------------|--------------------|---------------------|----------| +| `get` | Variable name | Variable value | Gets the value of the variable from the configured provider | + +To illustrate the variables API, each of the following examples makes a request to some API with a bearer token. The API URI, version, and token are all passed as application variables. The application manifest associated with the examples would look similar to the one described [in the previous section](#adding-variables-to-your-applications). + +The exact details of calling the config SDK from a Spin application depends on the language: + +{{ tabs "sdk-type" }} + +{{ startTab "Rust"}} + +> [**Want to go straight to the reference documentation?** Find it here.](https://docs.rs/spin-sdk/latest/spin_sdk/variables/index.html) + +The interface is available in the `spin_sdk::variables` module and is named `get`. + +```rust +use spin_sdk::{ + http::{EmptyBody, IntoResponse, Method, Request, Response}, + http_service, variables, +}; + +#[http_service] +async fn handle_api_call_with_token(_req: Request) -> anyhow::Result { + let token = variables::get("token").await?; + let api_uri = variables::get("api_uri").await?; + let version = variables::get("version").await?; + let versioned_api_uri = format!("{}/{}", api_uri, version); + let request = Request::builder() + .method(Method::Get) + .uri(versioned_api_uri) + .header("Authorization", format!("Bearer {}", token)) + .body(EmptyBody::new())?; + let response: Response = spin_sdk::http::send(request).await?; + // Do something with the response ... + Ok(Response::builder() + .status(200) + .body(EmptyBody::new())?) +} +``` + +{{ blockEnd }} + +{{ startTab "TypeScript"}} + +> [**Want to go straight to the reference documentation?** Find it here.](https://spinframework.github.io/spin-js-sdk/modules/_spinframework_spin-variables.html) + +```ts +import { AutoRouter } from 'itty-router'; +import { Variables } from '@fermyon/spin-sdk'; + +let router = AutoRouter(); + +router + .get("/", () => { + let token = Variables.get("token") + let apiUri = Variables.get("api_uri") + let version = Variables.get("version") + let versionedAPIUri = `${apiUri}/${version}` + let response = await fetch( + versionedAPIUri, + { + headers: { + 'Authorization': 'Bearer ' + token + } + } + ); + + return new Response("Used an API"); + }) + +//@ts-ignore +addEventListener('fetch', async (event: FetchEvent) => { + event.respondWith(router.fetch(event.request)); +}); + +``` + +{{ blockEnd }} + +{{ startTab "Python"}} + +> [**Want to go straight to the reference documentation?** Find it here.](https://spinframework.github.io/spin-python-sdk/v3/variables.html) + +The `variables` module has a function called `get`(https://spinframework.github.io/spin-python-sdk/v3/variables.html#spin_sdk.variables.get). + +```py +from spin_sdk.http import IncomingHandler, Request, Response, send +from spin_sdk import variables + +class IncomingHandler(IncomingHandler): + def handle_request(self, request: Request) -> Response: + token = variables.get("token") + api_uri = variables.get("api_uri") + version = variables.get("version") + versioned_api_uri = f"{api_uri}/{version}" + headers = { + "Authorization": f"Bearer {token}" + } + response = send(Request("GET", versioned_api_uri, headers, None)) + # Do something with the response ... + return Response( + 200, + {"content-type": "text/plain"}, + bytes("Used an API", "utf-8") + ) +``` + +{{ blockEnd }} + +{{ startTab "TinyGo"}} + +> [**Want to go straight to the reference documentation?** Find it here.](https://pkg.go.dev/github.com/spinframework/spin-go-sdk/v2@v2.2.1/variables) + +The function is available in the `github.com/spinframework/spin-go-sdk/v2/variables` package and is named `Get`. See [Go package](https://pkg.go.dev/github.com/spinframework/spin-go-sdk/v2/variables) for reference documentation. + +```go +import ( + "bytes" + "fmt" + "net/http" + + spinhttp "github.com/spinframework/spin-go-sdk/v2/http" + "github.com/spinframework/spin-go-sdk/v2/variables" +) + +func init() { + spinhttp.Handle(func(w http.ResponseWriter, r *http.Request) { + token, err := variables.Get("token") + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + apiUri, err := variables.Get("api_uri") + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + version, err := variables.Get("version") + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + versionedApiUri := fmt.Sprintf("%s/%s", apiUri, version) + + request, err := http.NewRequest("GET", versionedApiUri, bytes.NewBuffer([]byte(""))) + request.Header.Add("Authorization", "Bearer "+token) + response, err := spinhttp.Send(request) + // Do something with the response ... + w.Header().Set("Content-Type", "text/plain") + fmt.Fprintln(w, "Used an API") + }) +} +``` + +{{ blockEnd }} + +{{ blockEnd }} + +To build and run the application, we issue the following commands: + + + +```bash +# Build the application +$ spin build +# Run the application, setting the values of the API token and URI via the environment variable provider +# using the `SPIN_VARIABLE` prefix (upper case is necessary as shown here) +$ SPIN_VARIABLE_API_TOKEN="your-token-here" SPIN_VARIABLE_API_URI="http://my-best-api.com" spin up +``` + +Assuming you have configured you application to use an API, to test the application, simply query +the app endpoint: + + + +```bash +$ curl -i localhost:3000 +HTTP/1.1 200 OK +content-type: text/plain +content-length: 11 +date: Wed, 31 Jul 2024 22:03:35 GMT + +Used an API +``` + +## Providing Variable Values + +When you run an application, you must provide values for all required variables, and you may provide values for non-required variables (where you want to override the default). You can do this using a provider such as a secrets store, or you can set variables directly on the `spin up` command line. + +### Providing Variable Values from a Secrets Store + +You can provide variable values from a secrets store. The Spin CLI supports [Hashicorp Vault](./dynamic-configuration.md#vault-application-variable-provider) and [Azure Key Vault](https://azure.microsoft.com/en-us/products/key-vault). If you want to do this, you must set up the store in the runtime configuration file, and reference that file using `--runtime-config-file` on the command line. + +For information about configuring application variables providers, refer to the [runtime configuration documentation](./dynamic-configuration.md#application-variables-runtime-configuration). + +### Providing Variable Values from the Environment + +You can provide variable values using the operating system environment. For each variable you want to set, you must specify an environment variable named `SPIN_VARIABLE_` followed by a "shouty capitalised" form of the variable name - that is, all uppercase, with underscores for separators. For example, to set the `database_username` variable: + +```console +$ export SPIN_VARIABLE_DATABASE_USERNAME=bob_the_admin +$ spin up +``` + +### Providing Variable Values on the Command Line + +You can provide variable values via the `spin up --variable` command line flag. This flag can be repeated, and supports several variants: + +* `=`: sets the `` variable to the given value +* `=@`: sets the `` variable to the contents of the given text file +* `@.json`: sets multiple variables - each `"": ""` pair in the given JSON file sets the `` variable to the corresponding value +* `@.toml`: sets multiple variables - each ` = ""` pair in the given TOML file sets the `` variable to the corresponding value + +For example, suppose `secrets.json` is as follows: + +```json +{ + "sierra_madre": "no spoilers", + "eleven_herbs_and_spices": "you will never learn them from us" +} +``` + +Then `spin up --variable @secrets.json` will set the `sierra_madre` and `eleven_herbs_and_spices` variables. (Similarly with TOML files.) + +For the `@.json` and `@.toml` variants, the file content must be string keys to string values. Numeric or object/table values are not allowed. + +## Troubleshooting + +**"No provider resolved" error** + +If you run into the following error, you've most likely not set the variable, either through the environment variable provider using the `SPIN_VARIABLE_` prefix or through another provider. + +```console +Handler returned an error: Error::Provider("no provider resolved required variable \"YOUR_VARIABLE\"") +``` + +See [Dynamic Application Configuration](./dynamic-configuration#application-variables-runtime-configuration) for information on setting variable values via environment variables, or configuring secure variable providers. + +**"No variable" error** + +If you run into the following error, you've most likely not configured the component section in the `spin.toml` to have access to the variable specified. + +```console +Handler returned an error: Error::Undefined("no variable for \"\".\"your-variable\"") +``` + +To fix this, edit the `spin.toml` and add to the `[component..variables]` table a line such as ` = "\{{ app-variable }}".` See [above](#adding-variables-to-your-applications) for more information. \ No newline at end of file diff --git a/content/v4/writing-apps.md b/content/v4/writing-apps.md new file mode 100644 index 00000000..a0bde37a --- /dev/null +++ b/content/v4/writing-apps.md @@ -0,0 +1,537 @@ +title = "Writing Spin Applications" +template = "main" +date = "2023-11-04T00:00:01Z" +enable_shortcodes = true +[extra] +url = "https://github.com/spinframework/spin-docs/blob/main/content/v4/writing-apps.md" + +--- +- [Writing an Application Manifest](#writing-an-application-manifest) + - [The `trigger` Fields](#the-trigger-fields) + - [The Component Name](#the-component-name) + - [The Component `source`](#the-component-source) +- [Writing a Component Wasm Module](#writing-a-component-wasm-module) +- [Creating an Application From a Template](#creating-an-application-from-a-template) +- [Adding a New Component to an Application](#adding-a-new-component-to-an-application) +- [Including Files with Components](#including-files-with-components) +- [Adding Environment Variables to Components](#adding-environment-variables-to-components) +- [Granting Networking Permissions to Components](#granting-networking-permissions-to-components) +- [Granting Storage Permissions to Components](#granting-storage-permissions-to-components) +- [Example Manifests](#example-manifests) + - [Including a Directory of Files](#including-a-directory-of-files) + - [Using Public Components](#using-public-components) + - [Customizing the Executor](#customizing-the-executor) + - [Setting the Redis Channel to Monitor](#setting-the-redis-channel-to-monitor) +- [Using Component Dependencies](#using-component-dependencies) + - [Declaring Component Dependencies](#declaring-component-dependencies) + - [Specifying Dependencies](#specifying-dependencies) + - [Dependencies from a Registry](#dependencies-from-a-registry) + - [Dependencies from a Local Component](#dependencies-from-a-local-component) + - [Dependencies from a Component in the Application](#dependencies-from-a-component-in-the-application) + - [Dependencies from a URL](#dependencies-from-a-url) + - [Mapping All Imports from a Package](#mapping-all-imports-from-a-package) + - [Dependency Permissions](#dependency-permissions) +- [Next Steps](#next-steps) + +A Spin application consists of a set of WebAssembly (Wasm) _components_, and a _manifest_ that lists those components with some data about when and how to run them. This page describes how to write components, manifests, and hence applications. + +## Writing an Application Manifest + +> You won't normally create a manifest by hand, but will instead [create](#creating-an-application-from-a-template) or [update](#adding-a-new-component-to-an-application) one from a template. Skip to those sections if that's what you want to do. But it's important to know what's inside a manifest for when you need to update component capabilities or troubleshoot a problem. + +A Spin _application manifest_ is a [TOML](https://toml.io/) file that contains: + +* Identification information about the application +* What events the application should run in response to (the _triggers_) +* The Wasm modules of the application and their associated settings (the _components_) +* Optionally, configuration variables that the user can set when running the application + +By convention, the Spin manifest file is usually called `spin.toml`. + +This example is the manifest for a simple HTTP application with a single trigger executed when the `/hello` endpoint is accessed: + + + +```toml +spin_manifest_version = 2 + +# General identification information +[application] +name = "spin-hello-world" +version = "1.0.0" +description = "A simple application that returns hello world." + +# The application's sole trigger. This application responds to HTTP requests +# on the path "/hello", and handles them using the "hello" component. +[[trigger.http]] +route = "/hello" +component = "hello" + +# The "hello" component +[component.hello] +description = "A simple component that returns hello world." +# The Wasm module to run for the component +source = "target/wasm32-wasip2/release/helloworld.wasm" +# How to build the Wasm module from source +[component.hello.build] +command = "cargo build --target wasm32-wasip2 --release" +``` + +You can look up the various fields in the [Manifest Reference](manifest-reference), but let's look at the key fields in more detail. + +### The `trigger` Fields + +An application contains one or more _triggers_. Each trigger specifies a type, an event of that type that the application responds to, and a component to handle that event. + +The description above might sound a bit abstract. Let's clarify it with a concrete example. The most common trigger type is `http`, for which events are distinguished by the route. So an HTTP trigger might look like this: + +```toml +# double square brackets because there could be multiple 'trigger.http' tables +[[trigger.http]] # the type is part of the TOML "section" +route = "/hello" # the route (event) that this trigger responds to +component = "greeter" # the component to handle the trigger +``` + +Put the type (`http`), event (`route = "/hello"`), and component (`greeter`) together, and this is saying "when the application gets an HTTP request to `/hello`, respond to it using the `greeter` component. + +For more details about `http` triggers, see the [HTTP trigger](http-trigger) documentation. + +Another trigger type is `redis`, which is triggered by Redis pub-sub messages. For the `redis` type, the trigger event is specified by a pub-sub channel, e.g. `channel = "alerts"`. See the [Redis trigger](redis-trigger) documentation for more information. + +Multiple triggers may refer to the same component. For example, you could have another trigger on `/dober-dan` which also invokes the `greeter` component. + +Some triggers have additional application-level configuration options. For example, the Redis trigger allows you to provide an `address` field, which tells Spin the default server for components that do not specify servers. See the [Redis trigger](redis-trigger) documentation for more details. + +### The Component Name + +Component names are identifier strings. While not significant in themselves, they must be unique within the application. Triggers use names to refer to components, and Spin uses the name in logging and error messages to tell you which component a message applies to. + +Each component is a TOML table, and the name is written as part of the table header. For example: + + + +```toml +[component.greeter] +``` + +### The Component `source` + +Each component has a `source` field which tells Spin where to load the Wasm module from. + +For components that you are working on locally, set it to the file path to the compiled Wasm module file. This must be a relative path from the directory containing `spin.toml`. + + + +```toml +[component.shopping-cart] +source = "dist/cart.wasm" +``` + +For components that are published on the Web, provide a `url` field containing the URL of the Wasm module, and a `digest` field indicating the expected SHA256 hash of the module contents. The digest must be prefixed with the string `sha256:`. Spin uses the digest to check that the module is what you were expecting, and won't load the module if the content doesn't match. + + + +```toml +[component.asset-server] +source = { url = "https://github.com/spinframework/spin-fileserver/releases/download/v0.0.1/spin_static_fs.wasm", digest = "sha256:650376c33a0756b1a52cad7ca670f1126391b79050df0321407da9c741d32375" } +``` + +Multiple components can have the same source. An example is a document archive, where one component might serve user interface assets (CSS, images, etc.) on one route, while another serves the documents themselves on another route - both using the same file server module, but with different settings. + +## Writing a Component Wasm Module + +> You won't normally create a component by hand, but will instead [create](#creating-an-application-from-a-template) or [add](#adding-a-new-component-to-an-application) one from a template. Skip to those sections if that's what you want to do. But it's important to know what a component looks like so you can understand where to write your own code. + +At the Wasm level, a Spin component is a Wasm component or module that exports a handler for the application trigger. At the developer level, a Spin component is a library or program that implements your event handling logic, and uses Spin interfaces, libraries, or tools to associate that with the events handled by Spin. + +See the Language Guides section for how to do this in your preferred language. As an example, this is a component written in the Rust language. The `hello_world` function uses an attribute `#[http_service]` to identify the function as handling a Spin HTTP event. The function takes a `Request` and returns a `anyhow::Result`. + +```rust +use spin_sdk::http::{FullBody, IntoResponse, Request, Response}; +use spin_sdk::http_service; + +/// A simple Spin HTTP component. +#[http_service] +async fn hello_world(req: Request) -> anyhow::Result { + println!("Handling request to {:?}", req.headers().get("spin-full-url")); + Ok(Response::builder() + .status(200) + .header("content-type", "text/plain") + .body("Hello, Spin".to_string())?) +}​​ +``` + +## Creating an Application From a Template + +If you've installed the Spin templates for your preferred language, you can use them to get started without having to write a manifest or the boilerplate code yourself. To do this, run the `spin new` command, and choose the template that matches the type of application you want to create, and the language you want to use. + +{{ tabs "sdk-type" }} + +{{ startTab "Rust"}} + +Choose the `http-rust` template to create a new HTTP application, or `redis-rust` to create a new Redis application. + + + +```bash +$ spin new +Pick a template to start your application with: + http-c (HTTP request handler using C and the Zig toolchain) + http-csharp (HTTP request handler using C# (EXPERIMENTAL)) + http-go (HTTP request handler using (Tiny)Go) + http-grain (HTTP request handler using Grain) +> http-rust (HTTP request handler using Rust) + http-swift (HTTP request handler using SwiftWasm) + http-zig (HTTP request handler using Zig) + redis-go (Redis message handler using (Tiny)Go) + redis-rust (Redis message handler using Rust) + +Enter a name for your new application: hello_rust +Project description: My first Rust Spin application +HTTP path: /... +``` + +{{ blockEnd }} + +{{ startTab "TypeScript"}} + +Choose the `http-ts` or `http-js` template to create a new HTTP application, according to whether you want to use TypeScript or JavaScript. + +> The JavaScript development kit doesn't yet support Redis applications. + + + +```bash +$ spin new +Pick a template to start your application with: + http-js (HTTP request handler using Javascript) +> http-ts (HTTP request handler using Typescript) +Enter a name for your new application: hello_typescript +Project description: My first TypeScript Spin application +HTTP path: /... +``` + +{{ blockEnd }} + +{{ startTab "Python"}} + +Choose the `http-py` template to create a new HTTP application. + +> The Python development kit doesn't yet support Redis applications. + + + +```bash +$ spin new +Pick a template to start your application with: +> http-py (HTTP request handler using Python) +Enter a name for your new application: hello_python +Description: My first Python Spin application +HTTP path: /... +``` + +{{ blockEnd }} + +{{ startTab "TinyGo"}} + +Choose the `http-go` template to create a HTTP application, or `redis-go` to create a Redis application. + + + +```bash +$ spin new +Pick a template to start your application with: + http-c (HTTP request handler using C and the Zig toolchain) + http-empty (HTTP application with no components) +> http-go (HTTP request handler using (Tiny)Go) + http-grain (HTTP request handler using Grain) + http-php (HTTP request handler using PHP) + http-rust (HTTP request handler using Rust) +Enter a name for your new application: hello_go +Description: My first Go Spin application +HTTP path: /... +``` + +{{ blockEnd }} + +{{ blockEnd }} + +All of these templates create a manifest containing a single component, and the source code for a minimal "hello world" component. + +## Adding a New Component to an Application + +To add a new component to an existing application using a template, run the `spin add` command. This works in a very similar way to `spin new`, except that it expects the `spin.toml` file to already exist, and adds the details for the new component to that `spin.toml`. + +> Please take a look at the [Spin application structure](spin-application-structure) documentation, which explains how to achieve the recommended application structure (through the use of Spin templates via the `spin new` and `spin add` commands). + +## Including Files with Components + +You can include files with a component. This means that: + +1. The Wasm module will be able to read those files at runtime. +1. When you distribute or deploy the application, those files will be bundled with it so that the component can still access them. + +To do this, use the `files` field in the component manifest: + + + +```toml +[component.asset-server] +files = [ "images/**/*.jpg", { source = "styles/dist", destination = "/styles" } ] +``` + +The `files` field is an array listing the files, patterns and directories you want to include. Each element of the array can be: + +* A glob pattern, such as `images/**/*.jpg`, or single file path. In this case, the file or files that match the pattern are available to the Wasm code, at the same paths as they are in your file system. For example, if the glob pattern matches `images/photos/lake.jpg`, the Wasm module can access it using the path `images/photos/lake.jpg`. Glob patterns are relative to the directory containing `spin.toml`, and must be within that directory. +* A mapping from a `source` file or directory to a `destination` location, such as `{ source = "styles/dist", destination = "/styles" }`. In this case, the file, or the entire contents of the source directory, are available to the Wasm code at the destination location. In this example, if you have a file named `styles/dist/list/exciting.css`, the Wasm module can access it using the path `/styles/list/exciting.css`. Source locations are relative to the directory containing `spin.toml`; destination locations must be absolute. + +If your files list would match some files or directories that you _don't_ want included, you can use the `exclude_files` field to omit them. + +> By default, Spin takes a snapshot of your included files, and components access that snapshot. This ensures that when you test your application, you are checking it with the set of files it will be deployed with. However, it also means that your component does not pick up changes to the files while it is running. When testing a Web site, you might want to be able to edit a HTML or CSS file, and see the changes reflected without restarting Spin. You can tell Spin to give components access to the original, "live" files by passing the `--direct-mounts` flag to `spin up`. + +> Each component can access _only_ the files included via its `files` section. It does not have access to files included by other components, to your source code, or to the compiled `.wasm` file (unless you add those to the `files` section). + +## Adding Environment Variables to Components + +Environment variables can be provided to components via the Spin application manifest. + +To do this, use the `environment` field in the component manifest: + + + +```toml +[component.pet-info] +environment = { PET = "CAT", FOOD = "WATERMELON" } +``` + +The field accepts a map of environment variable key/value pairs. They are mapped inside the component at runtime. + +The environment variables can then be accessed inside the component. For example, in Rust: + +```rust +use spin_sdk::http::{IntoResponse, Request, Response}; +use spin_sdk::http_service; + +#[http_service] +async fn handle_hello_rust(req: Request) -> anyhow::Result { + let response = format!("My {} likes to eat {}", std::env::var("PET")?, std::env::var("FOOD")?); + Ok(Response::builder() + .status(200) + .header("content-type", "text/plain") + .body(response)?) +} +``` + +## Granting Networking Permissions to Components + +By default, Spin components are not allowed to make outgoing network requests. This follows the general Wasm rule that modules must be explicitly granted capabilities, which is important to sandboxing. + +To grant a component permission to make outbound requests to a particular address, use the `allowed_outbound_hosts` field in the component manifest. Each entry must comprise a host name or IP address _and_ a port. For example: + + + +```toml +[component.talkative] +allowed_outbound_hosts = ["redis://redis.example.com:6379", "https://api.example.com:8080"] +``` + +If a port is specified, the component can make requests only to that port. If no port is specified, the component can make requests only to the default port for the scheme (e.g. port 443 for the `https` scheme, port 5432 for the `postgres` scheme). If you need to allow requests to _any_ port, use the wildcard `*` (e.g. `mysql://db.example.com:*`). + +The Wasm module can send network traffic _only_ to the hosts specified in these two fields. Requests to other hosts (or other ports) will fail with an error. + +{{ details "This feels like extra work! Why do I have to list the hosts?" "This comes from the Wasm principle of deny by default: the user of a component, rather than the component itself, should decide what resource it's allowed to access. But this isn't just an abstract principle: it's critical to being able to trust third party components. For example, suppose you add `bad-boy-brians-totally-legitimate-file-server.wasm` to your application. Unless you unwisely grant it network permissions, you can be _provably certain_ that it doesn't access your Postgres database or send information to evildoers." }} + +For development-time convenience, you can also pass the string `"*://*:*"` in the `allowed_outbound_hosts` collection. This allows the component to make network requests to _any_ host and on any port. However, once you've determined which hosts your code needs, you should remove this string, and list the hosts instead. Other Spin implementations may restrict host access, and may disallow components that ask to connect to anything and everything! + +## Granting Storage Permissions to Components + +By default, Spin components are not allowed to access Spin's storage services. This follows the general Wasm rule that modules must be explicitly granted capabilities, which is important to sandboxing. To grant a component permission to use a Spin-provided store, use the `key_value_stores` field in the component manifest: + + + +```toml +[component.squirrel] +key_value_stores = [ "default" ] +``` + +See [the key-value API guide](kv-store-api-guide) for more information. + +## Example Manifests + +This section shows some examples of Spin component manifests. + +### Including a Directory of Files + +This example shows a Spin HTTP component that includes all the files in `static/` under the application directory, made available to the Wasm module at the `/` path. + + + +```toml +[[trigger.http]] +route = "/static/..." +component = "fileserver" + +[component.fileserver] +source = "modules/spin_static_fs.wasm" +files = [ { source = "static/", destination = "/" } ] +``` + +### Using Public Components + +This is similar to the file server component above, but gets the Wasm module from a public release instead of a local copy. Notice that the files are still drawn from a local path. This is a way in which you can use off-the-shelf component logic with your own application data. + + + +```toml +[[trigger.http]] +route = "/static/..." +component = "fileserver" + +[component.fileserver] +source = { url = "https://github.com/spinframework/spin-fileserver/releases/download/v0.0.1/spin_static_fs.wasm", digest = "sha256:650376c33a0756b1a52cad7ca670f1126391b79050df0321407da9c741d32375" } +files = [ { source = "static/", destination = "/" } ] +``` + +### Customizing the Executor + +This example shows an HTTP component whose Wasm module, instead of using the default Spin HTTP interface, uses the CGI-like WAGI (WebAssembly Gateway Interface) protocol. Spin can't work out the application model from the component, so the manifest needs to tell Spin to use WAGI instead of its default mode. It does this via the `executor` field. This is specific to the HTTP trigger so it goes under `trigger.http`. + +In addition, this module does not provide its WAGI functionality via the default `_start` entry point, but via a custom entry point named `serve-wagi`. The `executor` table needs to tell Spin this via the `entrypoint` field. Finally, this component needs to run the WAGI entry point with a specific set of command line arguments, which are expressed in the WAGI executor's `argv` field. + + + +```toml +[[trigger.http]] +route = "/..." +component = "env" +executor = { type = "wagi", argv = "test ${SCRIPT_NAME} ${ARGS} done", entrypoint = "serve-wagi" } + +[component.env] +source = "modules/env_wagi.wasm" +files = [ "content/**/*" , "templates/*", "scripts/*", "config/*"] +``` + +### Setting the Redis Channel to Monitor + +The example shows a Redis component. The manifest sets the `channel` field to say which channel the component should monitor. In this case, the component is invoked for new messages on the `messages` channel. + + + +```toml +[[trigger.redis]] +channel = "messages" +component = "echo-message" + +[component.echo-message] +source = "spinredis.wasm" +``` + +## Using Component Dependencies + +Few of us write applications without relying on libraries. Traditionally, those libraries have had to come from the language ecosystem - e.g. `npm` for JavaScript, `pip` for Python, etc. - and you can still work this way in Spin. However, the WebAssembly Component Model means that you can also depend on other WebAssembly components. The process of combining your application component with the Wasm components it depends on is called _composition_, and Spin supports this natively. + +> Spin's composition is limited to "plug" style scenarios where each of your component's imports is satisfied independently, and where the dependency component does not need to be further composed with any other components. The analogy is plugging each of your imports into a socket provided by a dependency. If you need to construct a more complex composition, you must use a dedicated tool such as [`wac`](https://github.com/bytecodealliance/wac) as part of your build. See the [Component Model book](https://component-model.bytecodealliance.org/creating-and-consuming/composing.html) for details and examples. + +To use composition through Spin, your component must import a [WIT (Wasm Interface Type) interface](https://component-model.bytecodealliance.org/design/wit.html), and the dependency must export the same WIT interface. The details of working with WIT interfaces is language-specific, and is beyond the scope of the Spin documentation. You can learn more from the [language guides in the Component Model book](https://component-model.bytecodealliance.org/language-support.html). This section focuses on describing the dependency composition support in Spin. + +### Declaring Component Dependencies + +To declare a component dependency, create a `[component.(name).dependencies]` table in your Spin manifest, and list all the WIT interfaces you import (other than the ones that Spin itself satisfies), together with the packages that you would like to use to satisfy those imports. + +For example, suppose your component imports a WIT interface named `security:http/malice` for detecting malicious HTTP requests. This interface might be defined by a vendor or standards body, and might have multiple implementations. Suppose Bargain Security, Inc. provides an HTTP inspection package which includes an implementation of this interface, and that they publish this in the `registry.example.com` registry as `bargains:inspection@2.0.0`. You can then set up your dependency as follows: + +```toml +[component.my-app.dependencies] +"security:http/malice" = { package = "bargains:inspection", version = "2.0.0", registry = "packages.example.com" } +``` + +During loading, Spin will download the package from the registry, locate its `security:http/malice` export, and wire up your imports to that export so that when your component calls a function in the WIT interface, that call is dispatched to the Bargain Security package. + +> Your Wasm component depends _only_ on the WIT interface. If, inexplicably, you become dissatisfied with Bargain Security, Inc., then you can switch to a different vendor by changing the package reference in the dependency mapping (and, of course, re-testing with the new implementation). + +### Specifying Dependencies + +Spin supports four sources for dependencies. + +#### Dependencies from a Registry + +To use a dependency from a registry, specify the following fields: + +| Field | Required? | Description | Example | +|------------|-------------|----------------------------------------------------------------------------------------------|---------| +| `version` | Required | A [semantic versioning constraint](https://semver.org/) for the package version to use. | `">= 1.1.0"` | +| `package` | Optional | The name of the package to use. If omitted, this defaults to the package name of the imported interface. | `"bargains:inspection"` | +| `registry` | Optional | The registry that hosts the package. If omitted, this defaults to your system default registry. | `"registry.example.com"` | +| `export` | Optional | The name of the export in the package. If omitted, this defaults to the name of the import. | `"more-secure:checking-it-out/web"` | + +If you don't need any of the optional fields, you can provide the version constraint as a plain string instead of writing out the table: + +```toml +# Use the `security:http` package in the default registry +"security:http/malice" = "2.0.0" +``` + +#### Dependencies from a Local Component + +To use a dependency from a component on your file system, specify the following fields: + +| Field | Required? | Description | Example | +|------------|-------------|----------------------------------------------------------------------------------------------|---------| +| `path` | Required | The path to the Wasm file containing the component. | `"../validation/request-checker.wasm"` | +| `export` | Optional | The name of the export in the package. If omitted, this defaults to the name of the import. | `"more-secure:checking-it-out/web"` | + +#### Dependencies from a Component in the Application + +You can use a component in your application as a dependency by specifying the following fields: + +| Field | Required? | Description | Example | +|------------|-------------|----------------------------------------------------------------------------------------------|---------| +| `component` | Required | The name (ID) of the component. | `"request-checker"` | +| `export` | Optional | The name of the export in the package. If omitted, this defaults to the name of the import. | `"more-secure:checking-it-out/web"` | + +Components referenced as dependencies may not have any Spin runtime configuration, such as `files`, `allowed_outbound_hosts`, `variables`, etc., because these are determined by the 'main' component. They may have `build` sections. + +#### Dependencies from a URL + +To use a dependency from an HTTP URL, such as a GitHub release, specify the following fields: + +| Field | Required? | Description | Example | +|------------|-------------|----------------------------------------------------------------------------------------------|---------| +| `url` | Required | The URL of the Wasm file containing the component. | `"https://example.com/downloads/request-checker.wasm"` | +| `digest` | Required | The SHA256 digest of the Wasm file. This is required for integrity checking. | `"sha256:650376c33a0756b1a52cad7ca670f1126391b79050df0321407da9c741d32375"` | +| `export` | Optional | The name of the export in the package. If omitted, this defaults to the name of the import. | `"more-secure:checking-it-out/web"` | + +### Mapping All Imports from a Package + +If you are importing several interfaces from the same WIT package, and want them all satisfied by the same Wasm package, you can omit the interface from the dependency name. For example, suppose you import the `malice`, `tomfoolery`, and `shenanigans` WIT interfaces from the `security:http` package, and that your Bargain Security package exports all three of them. You can write: + +```toml +[component.my-app.dependencies] +"security:http" = { package = "bargains:inspection", version = "2.0.0", registry = "packages.example.com" } +``` + +and Spin will map all of your `security:http` imports to the matching exports from the package. + +### Dependency Permissions + +By default, dependencies do not have access to Spin resources that require permission to be given in the manifest - network hosts, key-value stores, SQLite databases, variables, etc. + +If a component has a dependency which requires resource access, you can grant it by setting the `dependencies_inherit_configuration` flag in the Spin component manifest: + +```toml +[component.my-app] +dependencies_inherit_configuration = true +``` + +This grants _all_ dependencies access to _all_ resources listed in the Spin component manifest. You should therefore set this only if you trust _all_ dependencies. + +> Spin does not currently support inheritance on a dependency-by-dependency or feature-by-feature basis. + +## Next Steps + +- Learn about [Spin application structure](spin-application-structure) +- Learn about how to [build your Spin application code](build) +- Try [running your application locally](running-apps) +- Discover how Spin application authors [design and organise applications](see-what-people-have-built-with-spin) +- Learn about how to [configure your application at runtime](dynamic-configuration) +- Look up details in the [application manifest reference](manifest-reference) diff --git a/templates/main.hbs b/templates/main.hbs index f0ebf752..860ab268 100644 --- a/templates/main.hbs +++ b/templates/main.hbs @@ -7,10 +7,12 @@ {{! This adds the sidebar }} + + + + Managing Plugins + Managing Templates + + + + Configuring spin.toml + Command Line + + + + Internal Data & Caching + +
+
+
+ + Creating Spin Plugins + Creating Spin Templates + Spin Improvement Proposals + Custom Triggers + +
+ + Contributing to Spin + Contributing Documentation + + + + \ No newline at end of file diff --git a/templates/sitemap.hbs b/templates/sitemap.hbs index a2a3e811..b6d66c73 100644 --- a/templates/sitemap.hbs +++ b/templates/sitemap.hbs @@ -21,8 +21,18 @@ For date/time format, see https://www.w3.org/TR/NOTE-datetime weekly 0.5 {{else}} - daily - 1 + {{#if (active_project uri "/spin/v2" )}} + weekly + 0.5 + {{else}} + {{#if (active_project uri "/spin/v3" )}} + weekly + 0.5 + {{else}} + daily + 1 + {{/if}} + {{/if}} {{/if}} {{/each}}