Skip to content

Commit c5b3760

Browse files
committedMay 24, 2024
feat(example): add blank slate rust provider
Signed-off-by: Brooks Townsend <brooksmtownsend@gmail.com>
1 parent da313ab commit c5b3760

24 files changed

+621
-0
lines changed
 
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Cargo.lock
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
[package]
2+
name = "wasmcloud-example-blank-slate"
3+
version = "0.1.0"
4+
edition = "2021"
5+
description = """
6+
A blank slate capability provider built for quick implementation of custom capabilities.
7+
"""
8+
9+
[workspace]
10+
11+
[badges.maintenance]
12+
status = "actively-developed"
13+
14+
[dependencies]
15+
anyhow = "1.0.82"
16+
async-nats = "0.33.0"
17+
serde = { version = "1.0.197" , features = ["derive"] }
18+
serde_json = "1.0.115"
19+
tokio = { version = "1.37.0", features = [ "full" ] }
20+
tracing = "0.1"
21+
wasmcloud-provider-sdk = "0.5.0"
22+
wit-bindgen-wrpc = "0.3.7"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
# Blank Slate Capability Provider
2+
3+
This capability provider is a blank slate for creating providers with custom capabilities. It uses the [wasmcloud-provider-sdk](https://crates.io/crates/wasmcloud-provider-sdk) and implements the [Provider](https://docs.rs/wasmcloud-provider-sdk/0.5.0/wasmcloud_provider_sdk/trait.Provider.html) trait with an example handler that will persist the links that target the provider (target links) and links where the provider is the source and targets a component (source links).
4+
5+
The purpose of this example is to provide comprehensive comments on the usage of our wasmCloud provider SDK, from serving RPC exports to invoking component imports. The code is informative to read through and provides a base for extending wasmCloud with custom capabilities.
6+
7+
## Building
8+
9+
Prerequisites:
10+
11+
1. [Rust toolchain](https://www.rust-lang.org/tools/install)
12+
1. [wash](https://wasmcloud.com/docs/installation)
13+
14+
You can build this capability provider by running `wash build`. You can build the included test component with `wash build -p ./component`.
15+
16+
## Running to test
17+
18+
Prerequisites:
19+
20+
1. [Rust toolchain](https://www.rust-lang.org/tools/install)
21+
1. [nats-server](https://github.com/nats-io/nats-server)
22+
1. [nats-cli](https://github.com/nats-io/natscli)
23+
24+
You can run this capability provider as a binary by passing a simple base64 encoded [HostData](https://docs.rs/wasmcloud-core/0.6.0/wasmcloud_core/host/struct.HostData.html) struct, in order to do basic testing. For example:
25+
26+
```bash
27+
nats-server -js &
28+
echo '{"lattice_rpc_url": "0.0.0.0:4222", "lattice_rpc_prefix": "default", "provider_key": "blank-slate", "config": {"foo": "bar"}, "env_values": {}, "link_definitions": [], "otel_config": {"enable_observability": false}}' | base64 | cargo run
29+
```
30+
31+
And in another terminal, you can request the health of the provider using the NATS CLI
32+
33+
```bash
34+
nats req "wasmbus.rpc.default.blank-slate.default.health '{}'
35+
```
36+
37+
Additionally, you can invoke the provider directly which will send test data to each linked component
38+
39+
```bash
40+
wash call blank-slate wasmcloud:example/system-info.call
41+
```
42+
43+
## Running as an application
44+
45+
You can deploy this provider, along with a [prebuilt component](./component/) for testing, by deploying the [wadm.yaml](./wadm.yaml) application.
46+
47+
```bash
48+
# Launch wasmCloud in the background
49+
wash up -d
50+
# Deploy the application
51+
wash app deploy ./wadm.yaml
52+
```
53+
54+
## Customizing
55+
56+
Customizing this provider to meet your needs of a custom capability takes just a few steps.
57+
58+
1. Update the [wit/world.wit](./wit/world.wit) to include the data types and functions that model your custom capability. You can use the example as a base and the [component model WIT reference](https://component-model.bytecodealliance.org/design/wit.html) as a guide for types and keywords.
59+
1. Implement any provider `export`s in [src/provider.rs](./src/provider.rs) inside of the `impl Handler {}` block.
60+
1. Use the methods inside of the `impl Provider {}` block to handle invoking components. For inspiration, take a look at our other capability providers that implement various capabilities like HTTP, Messaging, Key-Value in the [crates/provider-\*](../../../../crates/) folder.
61+
62+
Have any questions? Please feel free to [file an issue](https://github.com/wasmCloud/wasmCloud/issues/new/choose) and/or join us on the [wasmCloud slack](https://slack.wasmcloud.com)!
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# Rust build artifacts
2+
Cargo.lock
3+
4+
# Wash build artifacts
5+
build/
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
[package]
2+
name = "blank-slate-test-component"
3+
edition = "2021"
4+
version = "0.1.0"
5+
6+
[workspace]
7+
8+
[lib]
9+
crate-type = ["cdylib"]
10+
11+
[dependencies]
12+
wit-bindgen = { version = "0.24", features = ["default"] }
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# Blank slate test component
2+
3+
This component is meant to test the [blank slate capability provider](../) by an implementation of the interface on the component.
4+
5+
## Build
6+
7+
Use `wash build` to build this component. A prebuilt component is included for easy deployment of the provider.
8+
9+
## Deploy
10+
11+
Use the [wadm.yaml](../wadm.yaml) in the parent directory to deploy this component alongside the provider.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
wit_bindgen::generate!();
2+
3+
use crate::exports::wasmcloud::example::process_data::Data;
4+
use crate::exports::wasmcloud::example::process_data::Guest;
5+
use crate::wasi::logging::logging::*;
6+
use crate::wasmcloud::example::system_info::Kind;
7+
8+
struct BlankSlateComponent;
9+
10+
impl Guest for BlankSlateComponent {
11+
fn process(data: Data) -> String {
12+
log(Level::Info, "", &format!("Data received: {:?}", data));
13+
// Request OS and architecture information
14+
let os = crate::wasmcloud::example::system_info::request_info(Kind::Os);
15+
let arch = crate::wasmcloud::example::system_info::request_info(Kind::Arch);
16+
format!("Provider is running on {os}-{arch}").to_string()
17+
}
18+
}
19+
20+
export!(BlankSlateComponent);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
name = "Blank slate test component"
2+
language = "rust"
3+
type = "component"
4+
5+
[component]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
[blankslate]
2+
path = "../../wit"
3+
sha256 = "3af8ebbb348273a8a541440c4fbad2c739d03257903ab172d6722c68a694f86c"
4+
sha512 = "2d83b11fd3ac592c4ed87a05feefae1d26803b626f5a425a109202196b6d951bfb407cb4f97c6209891ef70aeb3b86b8b8338c880d46fc1976fa7a9b5ca7201b"
5+
6+
[logging]
7+
url = "https://github.com/WebAssembly/wasi-logging/archive/main.tar.gz"
8+
sha256 = "9676b482485bb0fd2751a390374c1108865a096b7037f4b5dbe524f066bfb06e"
9+
sha512 = "30a621a6d48a0175e8047c062e618523a85f69c45a7c31918da2b888f7527fce1aca67fa132552222725d0f6cdcaed95be7f16c28488d9468c0fad00cb7450b9"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
blankslate = "../../wit"
2+
logging = "https://github.com/WebAssembly/wasi-logging/archive/main.tar.gz"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
package wasmcloud:example;
2+
3+
// This interface is generic and includes a function to process some
4+
// data, returning a string result.
5+
// We'll use this to send structured data to a component for processing.
6+
interface process-data {
7+
record data {
8+
name: string,
9+
count: u32,
10+
}
11+
12+
// Send structured data to the component for processing
13+
process: func(data: data) -> string;
14+
}
15+
16+
// While processing data, sometimes a component may need to request
17+
// information about the system it's running on. The component isn't
18+
// allowed to access this information directly, so it can request it
19+
// from the provider.
20+
interface system-info {
21+
enum kind {
22+
OS,
23+
ARCH,
24+
}
25+
26+
// Request information about the system the provider is running on
27+
request-info: func(kind: kind) -> string;
28+
29+
// Example export to call from the provider for testing
30+
call: func() -> string;
31+
}
32+
33+
// The `world` defines all of the imports and exports our provider can use / must implement.
34+
world provider {
35+
// Providers `import` functions that it can call on a component
36+
import process-data;
37+
// Providers `export` functions that a component can call
38+
export system-info;
39+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/// WASI Logging is a logging API intended to let users emit log messages with
2+
/// simple priority levels and context values.
3+
interface logging {
4+
/// A log level, describing a kind of message.
5+
enum level {
6+
/// Describes messages about the values of variables and the flow of
7+
/// control within a program.
8+
trace,
9+
10+
/// Describes messages likely to be of interest to someone debugging a
11+
/// program.
12+
debug,
13+
14+
/// Describes messages likely to be of interest to someone monitoring a
15+
/// program.
16+
info,
17+
18+
/// Describes messages indicating hazardous situations.
19+
warn,
20+
21+
/// Describes messages indicating serious errors.
22+
error,
23+
24+
/// Describes messages indicating fatal errors.
25+
critical,
26+
}
27+
28+
/// Emit a log message.
29+
///
30+
/// A log message has a `level` describing what kind of message is being
31+
/// sent, a context, which is an uninterpreted string meant to help
32+
/// consumers group similar messages, and a string containing the message
33+
/// text.
34+
log: func(level: level, context: string, message: string);
35+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package wasi:logging;
2+
3+
world imports {
4+
import logging;
5+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package wasmcloud:blankslate;
2+
3+
world component {
4+
// Import logging for processing data
5+
import wasi:logging/logging;
6+
7+
// Notice here the component has the inverse direction for importing/exporting interfaces
8+
// compared to the provider. This allows us to compose the component with the provider, each
9+
// import linking up to an export.
10+
import wasmcloud:example/system-info;
11+
export wasmcloud:example/process-data;
12+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
[template]
2+
3+
raw = [
4+
"*.par.gz",
5+
"*.par",
6+
]
7+
exclude = [
8+
"target/",
9+
"keys/",
10+
"build/",
11+
"*.lock",
12+
]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
use std::collections::HashMap;
2+
3+
use serde::{Deserialize, Serialize};
4+
5+
/// Configuration for this provider, which is passed to the provider from the host.
6+
#[derive(Debug, Default, Clone, Serialize, Deserialize, PartialEq)]
7+
pub struct ProviderConfig {
8+
values: HashMap<String, String>,
9+
}
10+
11+
impl From<&HashMap<String, String>> for ProviderConfig {
12+
/// Construct configuration struct from the passed config values.
13+
///
14+
/// For this example, we just store the values directly for any later reference.
15+
/// You can use this as a base to create your own strongly typed configuration struct.
16+
fn from(values: &HashMap<String, String>) -> ProviderConfig {
17+
ProviderConfig {
18+
values: values.clone(),
19+
}
20+
}
21+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
//! A blank slate provider that's meant to inform developers how to build a capability provider.
2+
//! The implementation in `./provider.rs` uses the `wasmcloud-provider-sdk` to provide a scaffold
3+
//! for building a capability provider with a custom interface. Take note of the documentation
4+
//! comments in the code to understand how to build a capability provider.
5+
6+
mod config;
7+
mod provider;
8+
9+
use provider::BlankSlateProvider;
10+
11+
/// Capability providers are native executables, so the entrypoint is the same as any other Rust
12+
/// binary, `main()`. Typically the `main` function is kept simple and the provider logic is
13+
/// implemented in a separate module. Head to the `provider.rs` file to see the implementation of
14+
/// the `BlankSlateProvider`.
15+
#[tokio::main]
16+
async fn main() -> anyhow::Result<()> {
17+
BlankSlateProvider::run().await?;
18+
eprintln!("Blank slate provider exiting");
19+
Ok(())
20+
}

0 commit comments

Comments
 (0)
Please sign in to comment.