Browse Source
* Implement wasi-runtime-config * Avoid clone the WasiRuntimeConfig every timepull/8970/head
Xinzhao Xu
4 months ago
committed by
GitHub
13 changed files with 352 additions and 26 deletions
@ -0,0 +1,8 @@ |
|||
use test_programs::config::wasi::config::runtime; |
|||
|
|||
fn main() { |
|||
let v = runtime::get("hello").unwrap().unwrap(); |
|||
assert_eq!(v, "world"); |
|||
let config = runtime::get_all().unwrap(); |
|||
assert_eq!(config, [("hello".to_string(), "world".to_string())]) |
|||
} |
@ -0,0 +1,20 @@ |
|||
[package] |
|||
name = "wasmtime-wasi-runtime-config" |
|||
version.workspace = true |
|||
authors.workspace = true |
|||
edition.workspace = true |
|||
repository = "https://github.com/bytecodealliance/wasmtime" |
|||
license = "Apache-2.0 WITH LLVM-exception" |
|||
description = "Wasmtime implementation of the wasi-runtime-config API" |
|||
|
|||
[lints] |
|||
workspace = true |
|||
|
|||
[dependencies] |
|||
anyhow = { workspace = true } |
|||
wasmtime = { workspace = true, features = ["runtime", "component-model"] } |
|||
|
|||
[dev-dependencies] |
|||
test-programs-artifacts = { workspace = true } |
|||
wasmtime-wasi = { workspace = true } |
|||
tokio = { workspace = true, features = ["macros"] } |
@ -0,0 +1,140 @@ |
|||
//! # Wasmtime's [wasi-runtime-config] Implementation
|
|||
//!
|
|||
//! This crate provides a Wasmtime host implementation of the [wasi-runtime-config]
|
|||
//! API. With this crate, the runtime can run components that call APIs in
|
|||
//! [wasi-runtime-config] and provide configuration variables for the component.
|
|||
//!
|
|||
//! # Examples
|
|||
//!
|
|||
//! The usage of this crate is very similar to other WASI API implementations
|
|||
//! such as [wasi:cli] and [wasi:http].
|
|||
//!
|
|||
//! A common scenario is getting runtime-passed configurations in a [wasi:cli]
|
|||
//! component. A standalone example of doing all this looks like:
|
|||
//!
|
|||
//! ```
|
|||
//! use wasmtime::{
|
|||
//! component::{Linker, ResourceTable},
|
|||
//! Config, Engine, Result, Store,
|
|||
//! };
|
|||
//! use wasmtime_wasi::{WasiCtx, WasiCtxBuilder, WasiView};
|
|||
//! use wasmtime_wasi_runtime_config::{WasiRuntimeConfig, WasiRuntimeConfigVariables};
|
|||
//!
|
|||
//! #[tokio::main]
|
|||
//! async fn main() -> Result<()> {
|
|||
//! let mut config = Config::new();
|
|||
//! config.async_support(true);
|
|||
//! let engine = Engine::new(&config)?;
|
|||
//!
|
|||
//! let mut store = Store::new(&engine, Ctx {
|
|||
//! table: ResourceTable::new(),
|
|||
//! wasi_ctx: WasiCtxBuilder::new().build(),
|
|||
//! wasi_runtime_config_vars: WasiRuntimeConfigVariables::from_iter(vec![
|
|||
//! ("config_key1", "value1"),
|
|||
//! ("config_key2", "value2"),
|
|||
//! ]),
|
|||
//! });
|
|||
//!
|
|||
//! let mut linker = Linker::<Ctx>::new(&engine);
|
|||
//! wasmtime_wasi::add_to_linker_async(&mut linker)?;
|
|||
//! // add `wasi-runtime-config` world's interfaces to the linker
|
|||
//! wasmtime_wasi_runtime_config::add_to_linker(&mut linker, |h: &mut Ctx| {
|
|||
//! WasiRuntimeConfig::from(&h.wasi_runtime_config_vars)
|
|||
//! })?;
|
|||
//!
|
|||
//! // ... use `linker` to instantiate within `store` ...
|
|||
//!
|
|||
//! Ok(())
|
|||
//! }
|
|||
//!
|
|||
//! struct Ctx {
|
|||
//! table: ResourceTable,
|
|||
//! wasi_ctx: WasiCtx,
|
|||
//! wasi_runtime_config_vars: WasiRuntimeConfigVariables,
|
|||
//! }
|
|||
//!
|
|||
//! impl WasiView for Ctx {
|
|||
//! fn table(&mut self) -> &mut ResourceTable { &mut self.table }
|
|||
//! fn ctx(&mut self) -> &mut WasiCtx { &mut self.wasi_ctx }
|
|||
//! }
|
|||
//! ```
|
|||
//!
|
|||
//! [wasi-runtime-config]: https://github.com/WebAssembly/wasi-runtime-config
|
|||
//! [wasi:cli]: https://docs.rs/wasmtime-wasi/latest
|
|||
//! [wasi:http]: https://docs.rs/wasmtime-wasi-http/latest
|
|||
|
|||
#![deny(missing_docs)] |
|||
|
|||
use anyhow::Result; |
|||
use std::collections::HashMap; |
|||
|
|||
mod gen_ { |
|||
wasmtime::component::bindgen!({ |
|||
path: "wit", |
|||
world: "wasi:config/imports", |
|||
trappable_imports: true, |
|||
}); |
|||
} |
|||
use self::gen_::wasi::config::runtime as generated; |
|||
|
|||
/// Capture the state necessary for use in the `wasi-runtime-config` API implementation.
|
|||
#[derive(Default)] |
|||
pub struct WasiRuntimeConfigVariables(HashMap<String, String>); |
|||
|
|||
impl<S: Into<String>> FromIterator<(S, S)> for WasiRuntimeConfigVariables { |
|||
fn from_iter<I: IntoIterator<Item = (S, S)>>(iter: I) -> Self { |
|||
Self( |
|||
iter.into_iter() |
|||
.map(|(k, v)| (k.into(), v.into())) |
|||
.collect(), |
|||
) |
|||
} |
|||
} |
|||
|
|||
impl WasiRuntimeConfigVariables { |
|||
/// Create a new runtime configuration.
|
|||
pub fn new() -> Self { |
|||
Default::default() |
|||
} |
|||
|
|||
/// Insert a key-value pair into the configuration map.
|
|||
pub fn insert(&mut self, key: impl Into<String>, value: impl Into<String>) -> &mut Self { |
|||
self.0.insert(key.into(), value.into()); |
|||
self |
|||
} |
|||
} |
|||
|
|||
/// A wrapper capturing the needed internal `wasi-runtime-config` state.
|
|||
pub struct WasiRuntimeConfig<'a> { |
|||
vars: &'a WasiRuntimeConfigVariables, |
|||
} |
|||
|
|||
impl<'a> From<&'a WasiRuntimeConfigVariables> for WasiRuntimeConfig<'a> { |
|||
fn from(vars: &'a WasiRuntimeConfigVariables) -> Self { |
|||
Self { vars } |
|||
} |
|||
} |
|||
|
|||
impl generated::Host for WasiRuntimeConfig<'_> { |
|||
fn get(&mut self, key: String) -> Result<Result<Option<String>, generated::ConfigError>> { |
|||
Ok(Ok(self.vars.0.get(&key).map(|s| s.to_owned()))) |
|||
} |
|||
|
|||
fn get_all(&mut self) -> Result<Result<Vec<(String, String)>, generated::ConfigError>> { |
|||
Ok(Ok(self |
|||
.vars |
|||
.0 |
|||
.iter() |
|||
.map(|(k, v)| (k.to_string(), v.to_string())) |
|||
.collect())) |
|||
} |
|||
} |
|||
|
|||
/// Add all the `wasi-runtime-config` world's interfaces to a [`wasmtime::component::Linker`].
|
|||
pub fn add_to_linker<T>( |
|||
l: &mut wasmtime::component::Linker<T>, |
|||
f: impl Fn(&mut T) -> WasiRuntimeConfig<'_> + Send + Sync + Copy + 'static, |
|||
) -> Result<()> { |
|||
generated::add_to_linker_get_host(l, f)?; |
|||
Ok(()) |
|||
} |
@ -0,0 +1,69 @@ |
|||
use anyhow::{anyhow, Result}; |
|||
use test_programs_artifacts::{foreach_runtime_config, RUNTIME_CONFIG_GET_COMPONENT}; |
|||
use wasmtime::{ |
|||
component::{Component, Linker, ResourceTable}, |
|||
Store, |
|||
}; |
|||
use wasmtime_wasi::{add_to_linker_async, bindings::Command, WasiCtx, WasiCtxBuilder, WasiView}; |
|||
use wasmtime_wasi_runtime_config::{WasiRuntimeConfig, WasiRuntimeConfigVariables}; |
|||
|
|||
struct Ctx { |
|||
table: ResourceTable, |
|||
wasi_ctx: WasiCtx, |
|||
wasi_runtime_config_vars: WasiRuntimeConfigVariables, |
|||
} |
|||
|
|||
impl WasiView for Ctx { |
|||
fn table(&mut self) -> &mut ResourceTable { |
|||
&mut self.table |
|||
} |
|||
|
|||
fn ctx(&mut self) -> &mut WasiCtx { |
|||
&mut self.wasi_ctx |
|||
} |
|||
} |
|||
|
|||
async fn run_wasi(path: &str, ctx: Ctx) -> Result<()> { |
|||
let engine = test_programs_artifacts::engine(|config| { |
|||
config.async_support(true); |
|||
}); |
|||
let mut store = Store::new(&engine, ctx); |
|||
let component = Component::from_file(&engine, path)?; |
|||
|
|||
let mut linker = Linker::new(&engine); |
|||
add_to_linker_async(&mut linker)?; |
|||
wasmtime_wasi_runtime_config::add_to_linker(&mut linker, |h: &mut Ctx| { |
|||
WasiRuntimeConfig::from(&h.wasi_runtime_config_vars) |
|||
})?; |
|||
|
|||
let command = Command::instantiate_async(&mut store, &component, &linker).await?; |
|||
command |
|||
.wasi_cli_run() |
|||
.call_run(&mut store) |
|||
.await? |
|||
.map_err(|()| anyhow!("command returned with failing exit status")) |
|||
} |
|||
|
|||
macro_rules! assert_test_exists { |
|||
($name:ident) => { |
|||
#[allow(unused_imports)] |
|||
use self::$name as _; |
|||
}; |
|||
} |
|||
|
|||
foreach_runtime_config!(assert_test_exists); |
|||
|
|||
#[tokio::test(flavor = "multi_thread")] |
|||
async fn runtime_config_get() -> Result<()> { |
|||
run_wasi( |
|||
RUNTIME_CONFIG_GET_COMPONENT, |
|||
Ctx { |
|||
table: ResourceTable::new(), |
|||
wasi_ctx: WasiCtxBuilder::new().build(), |
|||
wasi_runtime_config_vars: WasiRuntimeConfigVariables::from_iter(vec![( |
|||
"hello", "world", |
|||
)]), |
|||
}, |
|||
) |
|||
.await |
|||
} |
@ -0,0 +1,25 @@ |
|||
interface runtime { |
|||
/// An error type that encapsulates the different errors that can occur fetching config |
|||
variant config-error { |
|||
/// This indicates an error from an "upstream" config source. |
|||
/// As this could be almost _anything_ (such as Vault, Kubernetes ConfigMaps, KeyValue buckets, etc), |
|||
/// the error message is a string. |
|||
upstream(string), |
|||
/// This indicates an error from an I/O operation. |
|||
/// As this could be almost _anything_ (such as a file read, network connection, etc), |
|||
/// the error message is a string. |
|||
/// Depending on how this ends up being consumed, |
|||
/// we may consider moving this to use the `wasi:io/error` type instead. |
|||
/// For simplicity right now in supporting multiple implementations, it is being left as a string. |
|||
io(string), |
|||
} |
|||
|
|||
/// Gets a single opaque config value set at the given key if it exists |
|||
get: func( |
|||
/// A string key to fetch |
|||
key: string |
|||
) -> result<option<string>, config-error>; |
|||
|
|||
/// Gets a list of all set config data |
|||
get-all: func() -> result<list<tuple<string, string>>, config-error>; |
|||
} |
@ -0,0 +1,6 @@ |
|||
package wasi:config@0.2.0-draft; |
|||
|
|||
world imports { |
|||
/// The runtime interface for config |
|||
import runtime; |
|||
} |
@ -0,0 +1,6 @@ |
|||
// We actually don't use this; it's just to let bindgen! find the corresponding world in wit/deps. |
|||
package wasmtime:wasi; |
|||
|
|||
world bindings { |
|||
include wasi:config/imports@0.2.0-draft; |
|||
} |
Loading…
Reference in new issue