From 355587234ea79a24dc09b641002e4b69663ab3c5 Mon Sep 17 00:00:00 2001 From: kjuulh Date: Mon, 2 Dec 2024 23:12:37 +0100 Subject: [PATCH] feat: allow process from external code Signed-off-by: kjuulh --- crates/churn/src/agent/plugins.rs | 83 ++++++++++++++++++++++++++++--- crates/churn/wit/world.wit | 8 +++ 2 files changed, 85 insertions(+), 6 deletions(-) diff --git a/crates/churn/src/agent/plugins.rs b/crates/churn/src/agent/plugins.rs index 6a3651a..b33fbe2 100644 --- a/crates/churn/src/agent/plugins.rs +++ b/crates/churn/src/agent/plugins.rs @@ -1,7 +1,7 @@ use anyhow::Context; +use component::churn_tasks::process::HostProcess; use futures::StreamExt; -use std::path::PathBuf; -use std::sync::{Arc, RwLock}; +use std::sync::Arc; use tokio::io::AsyncWriteExt; use tokio::sync::Mutex; use wasmtime::component::*; @@ -10,10 +10,40 @@ use wasmtime_wasi::{DirPerms, FilePerms, WasiCtx, WasiCtxBuilder, WasiView}; wasmtime::component::bindgen!({ path: "wit/world.wit", - world: "churn", - async: true + //world: "churn", + async: true, + with: { + "component:churn-tasks/process/process": CustomProcess + } }); +#[derive(Default)] +pub struct CustomProcess {} +impl CustomProcess { + pub fn run(&self, args: Vec) -> String { + tracing::info!("calling function"); + + match args.split_first() { + Some((item, rest)) => { + let mut cmd = std::process::Command::new(item); + match cmd.args(rest).output() { + Ok(output) => std::str::from_utf8(&output.stdout) + .expect("to be able to parse utf8") + .to_string(), + Err(e) => { + tracing::error!("command failed with output: {e}"); + e.to_string() + } + } + } + None => { + tracing::warn!("failed to call function because it is empty"); + panic!("failed to call function because it is empty") + } + } + } +} + #[derive(Clone)] pub struct PluginStore { inner: Arc>, @@ -53,6 +83,12 @@ impl InnerPluginStore { // Add the command world (aka WASI CLI) to the linker wasmtime_wasi::add_to_linker_async(&mut linker).context("Failed to link command world")?; + + component::churn_tasks::process::add_to_linker( + &mut linker, + |state: &mut ServerWasiView| state, + )?; + let wasi_view = ServerWasiView::new(); let store = Store::new(&engine, wasi_view); @@ -130,7 +166,8 @@ impl InnerPluginStore { ); let instance = Churn::instantiate_async(&mut self.store, &component, &self.linker) .await - .context("Failed to instantiate the example world")?; + .context("Failed to instantiate the example world") + .unwrap(); Ok(instance) } @@ -139,6 +176,7 @@ impl InnerPluginStore { struct ServerWasiView { table: ResourceTable, ctx: WasiCtx, + processes: ResourceTable, } impl ServerWasiView { @@ -153,7 +191,11 @@ impl ServerWasiView { .expect("to be able to open root") .build(); - Self { table, ctx } + Self { + table, + ctx, + processes: ResourceTable::default(), + } } } @@ -166,3 +208,32 @@ impl WasiView for ServerWasiView { &mut self.ctx } } + +impl component::churn_tasks::process::Host for ServerWasiView {} + +#[async_trait::async_trait] +impl HostProcess for ServerWasiView { + async fn new( + &mut self, + ) -> wasmtime::component::Resource { + self.processes.push(CustomProcess::default()).unwrap() + } + + async fn run_process( + &mut self, + self_: wasmtime::component::Resource, + inputs: wasmtime::component::__internal::Vec, + ) -> String { + let process = self.processes.get(&self_).unwrap(); + process.run(inputs) + } + + async fn drop( + &mut self, + rep: wasmtime::component::Resource, + ) -> wasmtime::Result<()> { + self.processes.delete(rep)?; + + Ok(()) + } +} diff --git a/crates/churn/wit/world.wit b/crates/churn/wit/world.wit index ed11b38..86cf72a 100644 --- a/crates/churn/wit/world.wit +++ b/crates/churn/wit/world.wit @@ -1,5 +1,12 @@ package component:churn-tasks@0.1.0; +interface process { + resource process { + constructor(); + run-process: func(inputs: list) -> string; + } +} + interface task { id: func() -> string; should-run: func() -> bool; @@ -8,4 +15,5 @@ interface task { world churn { export task; + import process; }