kafka provider template

This commit is contained in:
2024-11-14 21:53:26 +01:00
parent 354d60eaec
commit b5027e0ca2
11 changed files with 779 additions and 0 deletions

View File

@@ -0,0 +1,6 @@
# Rust build artifacts
target/
# Wash build artifacts
build/

View File

@@ -0,0 +1,387 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "ahash"
version = "0.8.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011"
dependencies = [
"cfg-if",
"once_cell",
"version_check",
"zerocopy",
]
[[package]]
name = "anyhow"
version = "1.0.93"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c95c10ba0b00a02636238b814946408b1322d5ac4760326e6fb8ec956d85775"
[[package]]
name = "bitflags"
version = "2.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de"
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "echo-messaging"
version = "0.1.0"
dependencies = [
"wit-bindgen",
]
[[package]]
name = "equivalent"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
[[package]]
name = "hashbrown"
version = "0.14.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
dependencies = [
"ahash",
]
[[package]]
name = "hashbrown"
version = "0.15.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3a9bfc1af68b1726ea47d3d5109de126281def866b33970e10fbab11b5dafab3"
[[package]]
name = "heck"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
[[package]]
name = "id-arena"
version = "2.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "25a2bc672d1148e28034f176e01fffebb08b35768468cc954630da77a1449005"
[[package]]
name = "indexmap"
version = "2.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da"
dependencies = [
"equivalent",
"hashbrown 0.15.1",
"serde",
]
[[package]]
name = "itoa"
version = "1.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b"
[[package]]
name = "leb128"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67"
[[package]]
name = "log"
version = "0.4.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24"
[[package]]
name = "memchr"
version = "2.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
[[package]]
name = "once_cell"
version = "1.20.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775"
[[package]]
name = "prettyplease"
version = "0.2.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "64d1ec885c64d0457d564db4ec299b2dae3f9c02808b8ad9c3a089c591b18033"
dependencies = [
"proc-macro2",
"syn",
]
[[package]]
name = "proc-macro2"
version = "1.0.89"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af"
dependencies = [
"proc-macro2",
]
[[package]]
name = "ryu"
version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f"
[[package]]
name = "semver"
version = "1.0.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b"
[[package]]
name = "serde"
version = "1.0.215"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6513c1ad0b11a9376da888e3e0baa0077f1aed55c17f50e7b2397136129fb88f"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.215"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "serde_json"
version = "1.0.132"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d726bfaff4b320266d395898905d0eba0345aae23b54aee3a737e260fd46db03"
dependencies = [
"itoa",
"memchr",
"ryu",
"serde",
]
[[package]]
name = "smallvec"
version = "1.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67"
[[package]]
name = "spdx"
version = "0.10.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "47317bbaf63785b53861e1ae2d11b80d6b624211d42cb20efcd210ee6f8a14bc"
dependencies = [
"smallvec",
]
[[package]]
name = "syn"
version = "2.0.87"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "25aa4ce346d03a6dcd68dd8b4010bcb74e54e62c90c573f394c46eae99aba32d"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "unicode-ident"
version = "1.0.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe"
[[package]]
name = "unicode-xid"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
[[package]]
name = "version_check"
version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
[[package]]
name = "wasm-encoder"
version = "0.217.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b88b0814c9a2b323a9b46c687e726996c255ac8b64aa237dd11c81ed4854760"
dependencies = [
"leb128",
"wasmparser",
]
[[package]]
name = "wasm-metadata"
version = "0.217.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "65a146bf9a60e9264f0548a2599aa9656dba9a641eff9ab88299dc2a637e483c"
dependencies = [
"anyhow",
"indexmap",
"serde",
"serde_derive",
"serde_json",
"spdx",
"wasm-encoder",
"wasmparser",
]
[[package]]
name = "wasmparser"
version = "0.217.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ca917a21307d3adf2b9857b94dd05ebf8496bdcff4437a9b9fb3899d3e6c74e7"
dependencies = [
"ahash",
"bitflags",
"hashbrown 0.14.5",
"indexmap",
"semver",
]
[[package]]
name = "wit-bindgen"
version = "0.32.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0eb9327b2afd6af02ab39f8fbde6bfc7d369d14bc8c8688311d3defcda3952bd"
dependencies = [
"wit-bindgen-rt",
"wit-bindgen-rust-macro",
]
[[package]]
name = "wit-bindgen-core"
version = "0.32.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc9cfd3f1b4e29e9a90fe04157764f24ae396cfb8530dae5753de140e73f9e56"
dependencies = [
"anyhow",
"heck",
"wit-parser",
]
[[package]]
name = "wit-bindgen-rt"
version = "0.32.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ca6f307148acf7199e492fd3781cc7b79f8f3eda003c0ac3aa8079449601ccb"
dependencies = [
"bitflags",
]
[[package]]
name = "wit-bindgen-rust"
version = "0.32.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf075ae0c89dc391f7d710d70c69bfd018c029c74a54f7ddfd0266dccc8ff0c5"
dependencies = [
"anyhow",
"heck",
"indexmap",
"prettyplease",
"syn",
"wasm-metadata",
"wit-bindgen-core",
"wit-component",
]
[[package]]
name = "wit-bindgen-rust-macro"
version = "0.32.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2ab28d36e4d326bd43d483512348874d4fffa378d8dc1da6dd6521afe2ec4f6"
dependencies = [
"anyhow",
"prettyplease",
"proc-macro2",
"quote",
"syn",
"wit-bindgen-core",
"wit-bindgen-rust",
]
[[package]]
name = "wit-component"
version = "0.217.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d7117809905e49db716d81e794f79590c052bf2fdbbcda1731ca0fb28f6f3ddf"
dependencies = [
"anyhow",
"bitflags",
"indexmap",
"log",
"serde",
"serde_derive",
"serde_json",
"wasm-encoder",
"wasm-metadata",
"wasmparser",
"wit-parser",
]
[[package]]
name = "wit-parser"
version = "0.217.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fb893dcd6d370cfdf19a0d9adfcd403efb8e544e1a0ea3a8b81a21fe392eaa78"
dependencies = [
"anyhow",
"id-arena",
"indexmap",
"log",
"semver",
"serde",
"serde_derive",
"serde_json",
"unicode-xid",
"wasmparser",
]
[[package]]
name = "zerocopy"
version = "0.7.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0"
dependencies = [
"zerocopy-derive",
]
[[package]]
name = "zerocopy-derive"
version = "0.7.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e"
dependencies = [
"proc-macro2",
"quote",
"syn",
]

View File

@@ -0,0 +1,12 @@
[package]
name = "echo-messaging"
edition = "2021"
version = "0.1.0"
[workspace]
[lib]
crate-type = ["cdylib"]
[dependencies]
wit-bindgen = "0.32"

View File

@@ -0,0 +1,207 @@
# Kafka Capability Provider
> [!WARNING]
> ⚠️ **THIS PROVIDER IS CURRENTLY EXPERIMENTAL** ⚠️
This capability provider is an implementation of the `wasmcloud:messaging` contract.
It exposes publish and subscribe functionality to components to operate on Kafka topics when connecting to a Kafka-compatible API. At the time of writing, this provider was tested and works well with [Apache Kafka][kafka] and [Redpanda][redpanda].
[kafka]: https://kafka.apache.org/
[redpanda]: https://redpanda.com/
## Named Config Settings
| Property | Description |
|-----------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `hosts` | A comma-separated list of bootstrap server hosts. For example, `HOSTS=127.0.0.1:9092,127.0.0.1:9093`. A single value is accepted as well, and the default value is the Kafka default of `127.0.0.1:9092`. This will be used for both the consumer and producer connections |
| `topic` | The Kafka topic you wish to consume. Any messages on this topic will be forwarded to this component for processing |
| `consumer_group` | Consumer group to use when consuming messages |
| `consumer_partitions` | Comma delimited list of partitions to use when subscribing to the topic specified by the link. |
| `producer_partitions` | Comma delimited list of partitions to use when handling `publish` calls from components (unrelated to the subscription topic) |
> [!WARNING]
> While `hosts` *can* be provided as named configuration, it *should* be provided as a secret, since
> bootstrap server hosts may be considered or contain sensitive information.
>
> While both named config and secrets are currently allowed, in a future version sensitive fields *must* be supplied via secrets.
## Secrets
| Property | Description |
|-----------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `hosts` | A comma-separated list of bootstrap server hosts. For example, `HOSTS=127.0.0.1:9092,127.0.0.1:9093`. A single value is accepted as well, and the default value is the Kafka default of `127.0.0.1:9092`. This will be used for both the consumer and producer connections |
|
## Limitations
This capability provider only implements the very basic Kafka functionality of producing to a topic and consuming a topic.
Because of this, advanced Kafka users may find that this is implemented without specific optimizations or options and we welcome any additions to this client.
Additionally, running multiple copies of this provider across different hosts was not tested during development, and it's possible that multiple instances of this provider will cause unexpected behavior like duplicate message delivery.
This provider also hard-codes a return topic (`<topic>.reply`) which is passed along to all actors it invokes.
## Testing
To test this provider, do the following:
### 1. Start Kafka
Start a [Kafka][kafka] instance using [docker][docker] (for example, [`bitnami/kafka`][dockerhub-bitnami/kafka]):
```console
docker run --rm \
-p 9092:9092 \
-e KAFKA_CFG_NODE_ID=0 \
-e KAFKA_CFG_PROCESS_ROLES=controller,broker \
-e KAFKA_CFG_ADVERTISED_LISTENERS=PLAINTEXT://localhost:9092 \
-e KAFKA_CFG_LISTENERS=PLAINTEXT://0.0.0.0:9092,CONTROLLER://0.0.0.0:9093 \
-e KAFKA_CFG_LISTENER_SECURITY_PROTOCOL_MAP=CONTROLLER:PLAINTEXT,PLAINTEXT:PLAINTEXT \
-e KAFKA_CFG_CONTROLLER_QUORUM_VOTERS=0@localhost:9093 \
-e KAFKA_CFG_CONTROLLER_LISTENER_NAMES=CONTROLLER \
--name messaging-test-kafka \
bitnami/kafka:3.6.1
```
After kafka finishes starting up, start a consumer that listens on the topic we're going to be creating later:
```console
docker exec -it \
messaging-test-kafka \
kafka-console-consumer.sh \
--consumer.config /opt/bitnami/kafka/config/consumer.properties \
--bootstrap-server 127.0.0.1:9092 \
--topic wasmcloud.echo \
--from-beginning
```
This command won't return, but will listen continuously for any new published messages on the topic.
[dockerhub-bitnami/kafka]: https://hub.docker.com/r/bitnami/kafka
### 2. Build the provider
You can build this provider with standard Rust tooling:
```console
cargo build
```
To be able to load the provider into a wasmcloud host, we must build a [compressed Provider ARchive (PAR)][wasmcloud-docs-par] with [`wash`][wash]:
```console
wash par create \
--vendor example \
--compress \
--name messaging-kafka \
--destination provider.par.gz \
--binary ../target/debug/messaging_kafka
```
You should now have a file named `provider.par.gz` in the current folder.
### 3. Start a new wasmcloud host
Use [`wash`][wash] to start a new [wasmcloud][wasmcloud] host:
```console
wash up
```
### 4. Deploy an architecture declaratively with `wadm`
Using [`wadm`][wadm] we can easily create a declarative deployment, using configuration similar to the [`messaging-kafka-demo.wadm.yaml` WADM manifest](./messaging-kafka-demo.wadm.yaml).
```yaml
---
apiVersion: core.oam.dev/v1beta1
kind: Application
metadata:
name: messaging-kafka-demo
annotations:
version: v0.0.1
description: |
Echo demo in Rust, using the WebAssembly Component Model and WebAssembly Interfaces Types (WIT), along with
the Kafka messaging provider.
spec:
components:
- name: echo
type: component
properties:
# NOTE: make sure to `wash build` the echo messaging example!
image: file://../../examples/rust/components/echo-messaging/build/echo_messaging_s.wasm
traits:
# Govern the spread/scheduling of the component
- type: spreadscaler
properties:
instances: 1
- type: link
properties:
target: nats
namespace: wasmcloud
package: messaging
interfaces: [consumer]
target_config:
- name: simple-subscription
properties:
topic: wasmcloud.echo
# Add a capability provider that implements `wasmcloud:messaging` using NATS
- name: nats
type: capability
properties:
image: ghcr.io/wasmcloud/messaging-nats:0.23.1
```
Then, we must set up the named config that we're expecting to see (`simple-subscription`):
```console
wash config put simple-subscription topic=wasmcloud.echo
```
To deploy the architecture above to your wasmcloud lattice:
```console
wash app deploy wadm.yaml
```
> [!NOTE]
>
> If you ever need to to remove (and possibly redeploy) your application:
>
> ```console
> wash app delete messaging-kafka-demo v0.0.1
> ```
### 5. Send a message on `<topic>`, see it echoed on `<topic>.reply`
Since the `echo-messaging` component returns any message it receives, and this provider adds a `reply_to` of `<topic>.reply`, we can test that our component is working by sending a message over the kafka topic we created earlier `wasmcloud.echo`, and seeing the messaged surfaced on `wasmcloud.echo.reply`.
To do this, make sure you have a consumer open for the `wasmcloud.echo.reply` topic:
```console
docker exec -it \
messaging-test-kafka \
kafka-console-consumer.sh \
--consumer.config /opt/bitnami/kafka/config/consumer.properties \
--bootstrap-server 127.0.0.1:9092 \
--topic wasmcloud.echo.reply \
--from-beginning
```
Then, you should be able to send a message using the kafka container (note that this comamnd will not return, but will instead produce a prompt):
```console
docker exec -it \
messaging-test-kafka \
kafka-console-producer.sh \
--bootstrap-server 127.0.0.1:9092 \
--topic wasmcloud.echo
```
Messages you send via the producer will be echoed first in the original consumer (`wasmcloud.echo`) and _also_ echoed in `wasmcloud.echo.reply`, which is the work of the `echo-messaging` component and the default functionality of this provider (supplying a generated `reply_to` topic).
[docker]: https://docs.docker.com
[wash]: https://github.com/wasmCloud/wasmCloud/tree/main/crates/wash-cli
[wadm]: https://github.com/wasmCloud/wadm

View File

@@ -0,0 +1,47 @@
---
apiVersion: core.oam.dev/v1beta1
kind: Application
metadata:
name: messaging-kafka-demo
annotations:
version: v0.0.1
description: |
Echo demo in Rust, using the WebAssembly Component Model and WebAssembly Interfaces Types (WIT), along with
the Kafka messaging provider.
spec:
components:
- name: echo
type: component
properties:
image: file://./build/echo_messaging_s.wasm
traits:
# Govern the spread/scheduling of the component
- type: spreadscaler
properties:
instances: 1
- type: link
properties:
target: kafka
namespace: wasmcloud
package: messaging
interfaces: [consumer]
target_config:
- name: simple-subscription
properties:
topic: wasmcloud.echo
# consumer_group: "your-group-name-here"
# consumer_partitions: "0,1,2,3"
# producer_partitions: "0,1,2,3"
# Add a capability provider that implements `wasmcloud:messaging` using KAFKA
- name: kafka
type: capability
properties:
# To use the locally built version of this provider,
#
# - build it with (`cargo build`)
# - create a provider archive (`wash par create`)
# - uncomment the line below
#
# image: file://../../messaging-kafka-provider.par.gz
image: ghcr.io/wasmcloud/messaging-kafka:0.5.1

View File

@@ -0,0 +1,19 @@
wit_bindgen::generate!({ generate_all });
use exports::wasmcloud::messaging::handler::Guest;
use wasmcloud::messaging::*;
struct Echo;
impl Guest for Echo {
fn handle_message(msg: types::BrokerMessage) -> Result<(), String> {
consumer::publish(&types::BrokerMessage {
subject: [msg.subject, String::from(".reply")].join(""),
reply_to: None,
body: msg.body,
})
}
}
export!(Echo);

View File

@@ -0,0 +1,21 @@
# This file is automatically generated.
# It is not intended for manual editing.
version = 1
[[packages]]
name = "wasi:logging"
registry = "wasi.dev"
[[packages.versions]]
requirement = "=0.1.0-draft"
version = "0.1.0-draft"
digest = "sha256:09621a45b12b0a9cddc798517f778aac0e5ae4bd234077b3d70758d6cf625580"
[[packages]]
name = "wasmcloud:messaging"
registry = "wasmcloud.com"
[[packages.versions]]
requirement = "=0.2.0"
version = "0.2.0"
digest = "sha256:bd2182f0a304b9a54a6b363f2f655422c8c0f00a03073c0195f1614a92dfdc7b"

View File

@@ -0,0 +1,7 @@
name = "echo-messaging"
language = "rust"
type = "component"
[component]
wit_world = "hello"
wasm_target = "wasm32-wasip2"

View File

@@ -0,0 +1,36 @@
package wasi:logging@0.1.0-draft;
/// WASI Logging is a logging API intended to let users emit log messages with
/// simple priority levels and context values.
interface logging {
/// A log level, describing a kind of message.
enum level {
/// Describes messages about the values of variables and the flow of
/// control within a program.
trace,
/// Describes messages likely to be of interest to someone debugging a
/// program.
debug,
/// Describes messages likely to be of interest to someone monitoring a
/// program.
info,
/// Describes messages indicating hazardous situations.
warn,
/// Describes messages indicating serious errors.
error,
/// Describes messages indicating fatal errors.
critical,
}
/// Emit a log message.
///
/// A log message has a `level` describing what kind of message is being
/// sent, a context, which is an uninterpreted string meant to help
/// consumers group similar messages, and a string containing the message
/// text.
log: func(level: level, context: string, message: string);
}
world imports {
import logging;
}

View File

@@ -0,0 +1,29 @@
package wasmcloud:messaging@0.2.0;
/// Types common to message broker interactions
interface types {
/// A message sent to or received from a broker
record broker-message {
subject: string,
body: list<u8>,
reply-to: option<string>,
}
}
interface handler {
use types.{broker-message};
/// Callback handled to invoke a function when a message is received from a subscription
handle-message: func(msg: broker-message) -> result<_, string>;
}
interface consumer {
use types.{broker-message};
/// Perform a request operation on a subject
request: func(subject: string, body: list<u8>, timeout-ms: u32) -> result<broker-message, string>;
/// Publish a message to a subject without awaiting a response
publish: func(msg: broker-message) -> result<_, string>;
}

View File

@@ -0,0 +1,8 @@
package wasmcloud:hello;
world hello {
import wasi:logging/logging@0.1.0-draft;
import wasmcloud:messaging/consumer@0.2.0;
export wasmcloud:messaging/handler@0.2.0;
}