3 releases
| 0.4.2 | Mar 17, 2026 |
|---|---|
| 0.4.1 | Mar 9, 2026 |
| 0.4.0 | Mar 5, 2026 |
#702 in Debugging
205KB
4K
SLoC
Drasi Host SDK
The Drasi Host SDK provides the host-side counterpart to the Drasi Plugin SDK. While the Plugin SDK helps authors build cdylib plugins, the Host SDK helps the server load, validate, and interact with them at runtime.
Overview
When the Drasi Server is built with the dynamic-plugins feature, it uses this crate to:
- Discover plugin shared libraries (
.so/.dylib/.dll) in a directory - Validate plugin metadata (SDK version, target triple) before initialization
- Initialize plugins by calling their
drasi_plugin_init()entry point - Wire callbacks for log routing and lifecycle event capture
- Wrap FFI vtables in proxy types that implement standard DrasiLib traits (
Source,Reaction,BootstrapProvider,SourcePluginDescriptor, etc.)
The result is that the rest of the server code works with normal Rust trait objects — the FFI boundary is completely hidden behind the proxies.
Architecture
┌─────────────────────────────────────────────────────────┐
│ Host (drasi-server) │
│ │
│ PluginLoader ──► LoadedPlugin │
│ ├── SourcePluginProxy │
│ ├── ReactionPluginProxy │
│ └── BootstrapPluginProxy │
│ │ │
│ create_source() │
│ │ │
│ SourceProxy ─── impl Source │
│ │
│ StateStoreVtableBuilder ──► StateStoreVtable ─────┐ │
│ IdentityProviderVtableBuilder ──► IdentityVtable ─┤ │
│ CallbackContext ──► log/lifecycle callbacks ───────┤ │
│ │ │
│ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ FFI boundary ─ ─ ─ ─ ─ ─ ─ ─ ─│─ │
│ ▼ │
│ Plugin (.so / .dylib / .dll) │
│ FfiStateStoreProxy ── impl StateStoreProvider │
│ FfiIdentityProviderProxy ── impl IdentityProvider │
│ FfiTracingLayer ── forwards logs to host │
└─────────────────────────────────────────────────────────┘
Data flow
- Host → Plugin: The host passes
StateStoreVtable,IdentityProviderVtable, and callback function pointers into the plugin viaFfiRuntimeContext. The plugin wraps these in proxy types from the Plugin SDK. - Plugin → Host: The plugin returns
SourceVtable,ReactionVtable, andBootstrapProviderVtablestructs. The Host SDK wraps these in proxy types that implement DrasiLib traits.
Modules
| Module | Description |
|---|---|
loader |
PluginLoader and PluginLoaderConfig — discovers and loads plugins from a directory |
callbacks |
CallbackContext and InstanceCallbackContext — routes plugin logs and lifecycle events into DrasiLib registries |
proxies::source |
SourceProxy (wraps SourceVtable → impl Source) and SourcePluginProxy (wraps SourcePluginVtable → impl SourcePluginDescriptor) |
proxies::reaction |
ReactionProxy (wraps ReactionVtable → impl Reaction) and ReactionPluginProxy (wraps ReactionPluginVtable → impl ReactionPluginDescriptor) |
proxies::bootstrap_provider |
BootstrapProviderProxy (wraps BootstrapProviderVtable → impl BootstrapProvider) and BootstrapPluginProxy (wraps BootstrapPluginVtable → impl BootstrapPluginDescriptor) |
proxies::change_receiver |
ChangeReceiverProxy and BootstrapReceiverProxy — proxy types for data channel receivers passed to plugins |
state_store_bridge |
StateStoreVtableBuilder — wraps a host Arc<dyn StateStoreProvider> into a StateStoreVtable for plugin consumption |
identity_bridge |
IdentityProviderVtableBuilder — wraps a host Arc<dyn IdentityProvider> into an IdentityProviderVtable for plugin consumption |
Usage
Loading plugins
use drasi_host_sdk::{PluginLoader, PluginLoaderConfig};
let config = PluginLoaderConfig {
plugin_dir: PathBuf::from("./plugins"),
file_patterns: vec![
"libdrasi_source_*".to_string(),
"libdrasi_reaction_*".to_string(),
"libdrasi_bootstrap_*".to_string(),
],
};
let loader = PluginLoader::new(config);
let plugins = loader.load_all(
log_ctx, // *mut c_void — host callback context
log_callback, // LogCallbackFn
lifecycle_ctx, // *mut c_void — host callback context
lifecycle_callback, // LifecycleCallbackFn
)?;
for plugin in plugins {
// Each LoadedPlugin contains factory proxies
for source_factory in plugin.source_plugins {
// source_factory implements SourcePluginDescriptor
println!("Loaded source plugin: {}", source_factory.kind());
}
}
Creating component instances
// SourcePluginProxy implements SourcePluginDescriptor
let source: Box<dyn Source> = source_factory
.create_source("my-source-1", &config_json, true)
.await?;
// The returned SourceProxy implements Source — use it normally
source.start().await?;
let status = source.status().await;
Injecting host services
The host can inject a StateStoreProvider and IdentityProvider into plugins:
use drasi_host_sdk::{StateStoreVtableBuilder, IdentityProviderVtableBuilder};
// Build FFI vtables from host-side trait objects
let state_store_vtable = StateStoreVtableBuilder::build(my_state_store.clone());
let identity_vtable = IdentityProviderVtableBuilder::build(my_identity_provider.clone());
// These vtables are passed to plugins via FfiRuntimeContext during initialization.
// The plugin wraps them in FfiStateStoreProxy / FfiIdentityProviderProxy.
Callback wiring
use drasi_host_sdk::CallbackContext;
let ctx = Arc::new(CallbackContext {
instance_id: "my-instance".to_string(),
runtime_handle: tokio::runtime::Handle::current(),
log_registry: log_registry.clone(),
source_event_history: source_events.clone(),
reaction_event_history: reaction_events.clone(),
});
// Pass as raw pointer to plugin loader
let raw_ctx = CallbackContext::into_raw(ctx);
Plugins route all log and tracing events through the FFI log callback. The CallbackContext dispatches these into the correct DrasiLib ComponentLogRegistry keyed by instance and component ID.
OCI Registry and Signature Verification
The OciRegistryClient supports downloading plugins from OCI registries and optional cosign signature verification via CosignVerifier:
- Use
OciRegistryClient::with_verifier(config, verifier)to enable signature verification on download VerificationConfigallows configuring trusted identities (issuer + subject pattern)download_plugin()returnsDownloadResultcontaining the plugin path and an optionalVerificationResultPluginMetadatanow includesgit_commitandbuild_timestampfields for build provenance
Plugin Load Sequence
- Library open —
libloading::Library::new(path)loads the.so/.dylib/.dll - Metadata validation — resolves
drasi_plugin_metadata()symbol, checks SDK version (major.minor match) and target triple - Initialization — calls
drasi_plugin_init()which returns anFfiPluginRegistrationcontaining vtable arrays and callback setters - Callback wiring — calls
set_log_callbackandset_lifecycle_callbackwith host context pointers - Proxy extraction — wraps each
SourcePluginVtable,ReactionPluginVtable, andBootstrapPluginVtablein their corresponding proxy types - Library retention — the
Arc<Library>is stored in each proxy to keep the shared library loaded for as long as any proxy is alive
Integration Tests
The host-sdk includes integration tests that load real cdylib plugins and exercise the full pipeline:
# Prerequisites: build the test plugins as cdylib shared libraries
make build-dynamic-plugins
# Run the integration tests
cargo test -p drasi-host-sdk --test integration_test
The tests cover:
- Plugin discovery and loading
- Metadata validation (SDK version, target triple)
- Source/Reaction/Bootstrap factory invocation
- Trait method dispatch through FFI (start, stop, status, subscribe, etc.)
- Log and lifecycle callback routing
- State store and identity provider injection
- Error handling and panic safety
License
Licensed under the Apache License, Version 2.0.
Dependencies
~31–44MB
~654K SLoC