-
Notifications
You must be signed in to change notification settings - Fork 23
Expand file tree
/
Copy pathmod.rs
More file actions
333 lines (302 loc) · 11.8 KB
/
mod.rs
File metadata and controls
333 lines (302 loc) · 11.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
//! Plugin system for extending host capabilities.
//!
//! This module provides the plugin framework that allows the wasmcloud host
//! to support different WASI interfaces and capabilities. Plugins implement
//! specific functionality that components can use through standard interfaces.
//!
//! # Plugin Architecture
//!
//! Plugins are Rust types that implement the [`HostPlugin`] trait. They:
//! - Declare which WIT interfaces they provide via [`HostPlugin::world`]
//! - Bind to components that need their capabilities via [`HostPlugin::bind_component`]
//! - Can participate in workload lifecycle events
//! - Are automatically linked into the wasmtime runtime
//!
//! # Built-in Plugins
//!
//! The crate provides several built-in plugins for common WASI interfaces:
//! - [`wasi_http`] - HTTP server capabilities (`wasi:http/incoming-handler`)
//! - [`wasi_config`] - Runtime configuration (`wasi:config/store`)
//! - [`wasi_blobstore`] - Object storage (`wasi:blobstore`)
//! - [`wasi_keyvalue`] - Key-value storage (`wasi:keyvalue`)
//! - [`wasi_logging`] - Structured logging (`wasi:logging`)
use std::future::Future;
use std::path::PathBuf;
use std::{collections::HashMap, path::Path};
use crate::engine::workload::WorkloadItem;
use crate::{
engine::workload::{ResolvedWorkload, UnresolvedWorkload, WorkloadComponent},
wit::WitWorld,
};
#[cfg(feature = "wasi-config")]
pub mod wasi_config;
#[cfg(feature = "wasi-blobstore")]
pub mod wasi_blobstore;
#[cfg(feature = "wasi-keyvalue")]
pub mod wasi_keyvalue;
#[cfg(feature = "wasi-logging")]
pub mod wasi_logging;
#[cfg(all(feature = "wasmcloud-postgres", not(doctest)))]
pub mod wasmcloud_postgres;
pub mod wasmcloud_messaging;
#[cfg(all(feature = "wasi-webgpu", not(target_os = "windows")))]
pub mod wasi_webgpu;
/// The [`HostPlugin`] trait provides an interface for implementing built-in plugins for the host.
/// A plugin is primarily responsible for implementing a specific [`WitWorld`] as a collection of
/// imports and exports that will be directly linked to the workload's [`wasmtime::component::Linker`].
///
/// For example, the runtime doesn't implement `wasi:keyvalue`, but it's a key capability for many component
/// applications. This crate provides a [`wasi_keyvalue::WasiKeyvalue`] built-in that persists key-value data
/// in-memory and implements the component imports of `wasi:keyvalue` atomics, batch and store.
///
/// You can supply your own [`HostPlugin`] implementations to the [`crate::host::HostBuilder::with_plugin`] function.
#[async_trait::async_trait]
pub trait HostPlugin: std::any::Any + Send + Sync + 'static {
/// Returns the unique identifier for this plugin.
///
/// This ID must be unique across all plugins registered with a host.
/// It's used to retrieve plugin instances and avoid conflicts.
///
/// # Returns
/// A static string slice containing the plugin's unique identifier.
fn id(&self) -> &'static str;
/// Returns the WIT interfaces that this plugin provides.
///
/// The returned `WitWorld` contains the imports and exports that this plugin
/// implements. The plugin's `bind_component` method will only be called if
/// a workload requires one of these interfaces.
///
/// # Returns
/// A `WitWorld` containing the plugin's imports and exports.
fn world(&self) -> WitWorld;
/// Returns whether this plugin supports handling multiple named instances
/// of the same namespace:package interface.
///
/// When a workload declares multiple host interfaces with the same
/// namespace:package but different names (e.g., two `wasi:keyvalue` entries
/// named "cache" and "sessions"), the plugin must be able to distinguish
/// and route to each named backend.
///
/// The default is `false`, which causes binding to fail if the workload
/// requires named multiplexing. Plugins that implement multiplexing
/// should override this to return `true`.
fn supports_named_instances(&self) -> bool {
false
}
/// Called when the plugin is started during host initialization.
///
/// This method allows plugins to perform any necessary setup before
/// accepting workloads. The default implementation does nothing.
///
/// # Returns
/// Ok if the plugin started successfully.
///
/// # Errors
/// Returns an error if the plugin fails to initialize, which will
/// prevent the host from starting.
async fn start(&self) -> anyhow::Result<()> {
Ok(())
}
/// Called when a workload is binding to this plugin.
///
/// This method is invoked when a workload is in the process of being bound to the plugin,
/// allowing the plugin to perform any necessary setup or validation before the binding is finalized.
/// The default implementation does nothing.
///
/// # Arguments
/// * `workload` - The unresolved workload that is being bound.
/// * `interfaces` - The set of WIT interfaces that the workload requires from this plugin.
///
/// # Returns
/// Ok if the binding preparation succeeded.
///
/// # Errors
/// Returns an error if the plugin cannot support the requested binding.
async fn on_workload_bind(
&self,
_workload: &UnresolvedWorkload,
_interfaces: std::collections::HashSet<crate::wit::WitInterface>,
) -> anyhow::Result<()> {
Ok(())
}
/// Called when a [`WorkloadComponent`] or [`WorkloadService`] is being bound to this plugin.
///
/// This method is called when a workload requires interfaces that this
/// plugin provides. The plugin should configure the component's linker
/// with the necessary implementations.
///
/// # Arguments
/// * `component` - The workload component to bind to this plugin
/// * `interfaces` - The specific WIT interfaces the component requires
///
/// # Returns
/// Ok if binding succeeded.
///
/// # Errors
/// Returns an error if the plugin cannot bind to the component.
async fn on_workload_item_bind<'a>(
&self,
_item: &mut WorkloadItem<'a>,
_interfaces: std::collections::HashSet<crate::wit::WitInterface>,
) -> anyhow::Result<()> {
Ok(())
}
/// Called when a workload has been fully resolved and is ready for use.
///
/// This optional callback allows plugins to perform actions after a workload
/// has been successfully bound and resolved. The default implementation
/// does nothing.
///
/// # Arguments
/// * `workload` - The fully resolved workload
/// * `component_id` - The ID of the specific component within the workload
///
/// # Returns
/// Ok if the callback completed successfully.
///
/// # Errors
/// Returns an error if the plugin fails to handle the resolved workload.
async fn on_workload_resolved(
&self,
_workload: &ResolvedWorkload,
_component_id: &str,
) -> anyhow::Result<()> {
Ok(())
}
/// Called when a workload is being stopped or unbound from this plugin.
///
/// This method allows plugins to clean up any resources associated with
/// the workload. This can be called during binding failures (before resolution)
/// or during normal workload shutdown (after resolution).
///
/// The default implementation does nothing.
///
/// # Arguments
/// * `workload_id` - The ID of the workload being unbound
/// * `interfaces` - The interfaces that were bound
///
/// # Returns
/// Ok if unbinding succeeded.
///
/// # Errors
/// Returns an error if cleanup fails.
async fn on_workload_unbind(
&self,
_workload_id: &str,
_interfaces: std::collections::HashSet<crate::wit::WitInterface>,
) -> anyhow::Result<()> {
Ok(())
}
/// Called when the plugin is being stopped during host shutdown.
///
/// This method allows plugins to perform cleanup before the host stops.
/// The default implementation does nothing.
///
/// # Returns
/// Ok if the plugin stopped successfully.
///
/// # Errors
/// Returns an error if cleanup fails (errors are logged but don't prevent shutdown).
async fn stop(&self) -> anyhow::Result<()> {
Ok(())
}
}
/// A tracker for workloads and their components, allowing storage of associated
/// data.
/// The tracker maintains a mapping of workload IDs to their data and
/// components, as well as a mapping of component IDs to their parent workload
/// IDs.
pub struct WorkloadTracker<T, Y> {
pub workloads: HashMap<String, WorkloadTrackerItem<T, Y>>,
pub components: HashMap<String, String>,
}
#[derive(Default)]
pub struct WorkloadTrackerItem<T, Y> {
pub workload_data: Option<T>,
pub components: HashMap<String, Y>,
}
impl<T, Y> Default for WorkloadTracker<T, Y> {
fn default() -> Self {
Self {
workloads: HashMap::new(),
components: HashMap::new(),
}
}
}
// TODO(lxf): remove once plugins have migrated to use this.
#[allow(dead_code)]
impl<T, Y> WorkloadTracker<T, Y> {
pub fn add_unresolved_workload(&mut self, workload: &UnresolvedWorkload, data: T) {
self.workloads.insert(
workload.id().to_string(),
WorkloadTrackerItem {
workload_data: Some(data),
components: HashMap::new(),
},
);
}
pub async fn remove_workload(&mut self, workload_id: &str) {
if let Some(item) = self.workloads.remove(workload_id) {
for component_id in item.components.keys() {
self.components.remove(component_id);
}
}
}
pub async fn remove_workload_with_cleanup<
FutW: Future<Output = ()>,
FutC: Future<Output = ()>,
>(
&mut self,
workload_id: &str,
workload_cleanup: impl FnOnce(Option<T>) -> FutW,
component_cleanup: impl Fn(Y) -> FutC,
) {
if let Some(item) = self.workloads.remove(workload_id) {
for (component_id, component_data) in item.components {
component_cleanup(component_data).await;
self.components.remove(&component_id);
}
workload_cleanup(item.workload_data).await;
}
}
pub fn add_component(&mut self, workload_component: &WorkloadComponent, data: Y) {
let component_id = workload_component.id();
let workload_id = workload_component.workload_id();
let item = self
.workloads
.entry(workload_id.to_string())
.or_insert_with(|| WorkloadTrackerItem {
workload_data: None,
components: HashMap::new(),
});
item.components.insert(component_id.to_string(), data);
self.components
.insert(component_id.to_string(), workload_id.to_string());
}
pub fn get_workload_data(&self, workload_id: &str) -> Option<&T> {
let item = self.workloads.get(workload_id)?;
item.workload_data.as_ref()
}
pub fn get_component_data(&self, component_id: &str) -> Option<&Y> {
let workload_id = self.components.get(component_id)?;
let item = self.workloads.get(workload_id)?;
item.components.get(component_id)
}
}
/// Locks an untrusted path to be within the given root directory.
pub(crate) fn lock_root(root: impl AsRef<Path>, untrusted: &str) -> Result<PathBuf, &'static str> {
let path = Path::new(untrusted);
// Reject absolute paths
if path.is_absolute() {
return Err("absolute paths not allowed");
}
// Reject paths with parent references
for component in path.components() {
match component {
std::path::Component::ParentDir => return Err("path traversal not allowed"),
std::path::Component::Prefix(_) => return Err("windows prefixes not allowed"),
_ => {}
}
}
Ok(root.as_ref().join(path))
}