Skip to main content

wash_runtime/plugin/
wasi_config.rs

1//! Runtime configuration plugin for WebAssembly components.
2//!
3//! This plugin implements the `wasi:config/runtime@0.2.0-draft` interface,
4//! providing components with access to configuration data and environment
5//! variables at runtime. It allows components to retrieve configuration
6//! values without requiring them to be compiled into the component.
7//!
8//! # Features
9//!
10//! - Access to environment variables
11//! - Configuration key-value pairs
12//! - Runtime configuration updates
13//! - Component isolation of configuration data
14//!
15//! # Usage
16//!
17//! Components can use this plugin through the standard WASI config interface
18//! to retrieve configuration values that are set by the host environment.
19
20use std::{
21    collections::{HashMap, HashSet},
22    sync::Arc,
23};
24use tokio::sync::RwLock;
25
26use crate::{
27    engine::{ctx::Ctx, workload::WorkloadComponent},
28    plugin::HostPlugin,
29    wit::{WitInterface, WitWorld},
30};
31
32mod bindings {
33    wasmtime::component::bindgen!({
34        world: "config",
35        trappable_imports: true,
36        async: true,
37    });
38}
39
40use bindings::wasi::config::runtime::{ConfigError, Host};
41
42const RUNTIME_CONFIG_ID: &str = "runtime-config";
43
44type ConfigMap = HashMap<Arc<str>, HashMap<String, String>>;
45
46/// Runtime configuration plugin that provides access to configuration data.
47///
48/// This plugin implements the WASI config interface, allowing components to
49/// retrieve configuration values and environment variables at runtime. Each
50/// component gets isolated access to its own configuration scope.
51#[derive(Clone, Default)]
52pub struct RuntimeConfig {
53    /// A map of configuration from component id to key-value pairs
54    config: Arc<RwLock<ConfigMap>>,
55}
56
57impl Host for Ctx {
58    async fn get(&mut self, key: String) -> anyhow::Result<Result<Option<String>, ConfigError>> {
59        let Some(plugin) = self.get_plugin::<RuntimeConfig>(RUNTIME_CONFIG_ID) else {
60            return Ok(Ok(None));
61        };
62        let config_guard = plugin.config.read().await;
63        config_guard
64            .get(&*self.component_id)
65            .and_then(|map| map.get(&key).cloned())
66            .map_or(Ok(Ok(None)), |v| Ok(Ok(Some(v))))
67    }
68
69    async fn get_all(&mut self) -> anyhow::Result<Result<Vec<(String, String)>, ConfigError>> {
70        let Some(plugin) = self.get_plugin::<RuntimeConfig>(RUNTIME_CONFIG_ID) else {
71            return Ok(Ok(vec![]));
72        };
73        let config_guard = plugin.config.read().await;
74        let entries = config_guard
75            .get(&*self.component_id)
76            .map(|map| map.iter().map(|(k, v)| (k.clone(), v.clone())).collect())
77            .unwrap_or_default();
78        Ok(Ok(entries))
79    }
80}
81
82#[async_trait::async_trait]
83impl HostPlugin for RuntimeConfig {
84    fn id(&self) -> &'static str {
85        RUNTIME_CONFIG_ID
86    }
87
88    fn world(&self) -> WitWorld {
89        WitWorld {
90            imports: HashSet::from([WitInterface::from("wasi:config/runtime@0.2.0-draft")]),
91            exports: HashSet::new(),
92        }
93    }
94    async fn on_component_bind(
95        &self,
96        component_handle: &mut WorkloadComponent,
97        interfaces: std::collections::HashSet<crate::wit::WitInterface>,
98    ) -> anyhow::Result<()> {
99        // Find the "wasi:config/runtime" interface, if present
100        let Some(interface) = interfaces.iter().find(|i| {
101            i.namespace == "wasi" && i.package == "config" && i.interfaces.contains("runtime")
102        }) else {
103            // Log a warning if the requested interfaces are not wasi:config/runtime
104            tracing::warn!(
105                "RuntimeConfig plugin requested for non-wasi:config/runtime interface(s): {:?}",
106                interfaces
107            );
108            return Ok(());
109        };
110
111        // Add `wasi:config/runtime` to the workload's linker
112        bindings::wasi::config::runtime::add_to_linker(component_handle.linker(), |ctx| ctx)?;
113
114        // Store the configuration for lookups later
115        self.config
116            .write()
117            .await
118            .insert(Arc::from(component_handle.id()), interface.config.clone());
119
120        Ok(())
121    }
122}