1mod config;
6pub mod describe;
7mod error;
8mod path_safety;
9mod resolve;
10mod runner;
11mod store;
12mod verify;
13
14pub use config::{DynSecretsStore, ExecConfig, RuntimePolicy, SecretsStore, VerifyPolicy};
15pub use error::{ExecError, RunnerError};
16pub use store::{ToolInfo, ToolStore};
17
18use greentic_types::TenantCtx;
19use serde_json::{Value, json};
20
21use crate::runner::Runner;
22
23#[derive(Clone, Debug)]
24pub struct ExecRequest {
25 pub component: String,
26 pub action: String,
27 pub args: Value,
28 pub tenant: Option<TenantCtx>,
29}
30
31pub fn exec(req: ExecRequest, cfg: &ExecConfig) -> Result<Value, ExecError> {
36 let resolved = resolve::resolve(&req.component, &cfg.store)
37 .map_err(|err| ExecError::resolve(&req.component, err))?;
38
39 let verified = verify::verify(&req.component, resolved, &cfg.security)
40 .map_err(|err| ExecError::verification(&req.component, err))?;
41
42 let runner = runner::DefaultRunner::new(&cfg.runtime)
43 .map_err(|err| ExecError::runner(&req.component, err))?;
44
45 let result = runner.run(
46 &req,
47 &verified,
48 runner::ExecutionContext {
49 runtime: &cfg.runtime,
50 http_enabled: cfg.http_enabled,
51 secrets_store: cfg.secrets_store.clone(),
52 },
53 );
54
55 let value = match result {
56 Ok(v) => v,
57 Err(RunnerError::ActionNotFound { .. }) => {
58 return Err(ExecError::not_found(
59 req.component.clone(),
60 req.action.clone(),
61 ));
62 }
63 Err(RunnerError::ToolTransient { component, message }) => {
64 return Err(ExecError::tool_error(
65 component,
66 req.action.clone(),
67 "transient",
68 json!({ "message": message }),
69 ));
70 }
71 Err(RunnerError::Internal(message)) => {
72 return Err(ExecError::runner(
73 &req.component,
74 RunnerError::Internal(message),
75 ));
76 }
77 Err(err) => return Err(ExecError::runner(&req.component, err)),
78 };
79
80 if let Some(code) = value
81 .get("error")
82 .and_then(|error| error.get("code"))
83 .and_then(Value::as_str)
84 .map(str::to_owned)
85 {
86 if code == "iface-error.not-found" {
87 return Err(ExecError::not_found(req.component, req.action));
88 } else {
89 return Err(ExecError::tool_error(
90 req.component,
91 req.action,
92 code,
93 value,
94 ));
95 }
96 }
97
98 Ok(value)
99}
100
101#[cfg(test)]
102mod tests {
103 use super::*;
104 use crate::config::{RuntimePolicy, VerifyPolicy};
105 use crate::error::RunnerError;
106 use crate::store::ToolStore;
107 use serde_json::json;
108 use std::collections::HashMap;
109 use std::path::PathBuf;
110
111 use crate::verify::VerifiedArtifact;
112
113 #[derive(Default)]
114 struct MockRunner;
115
116 impl runner::Runner for MockRunner {
117 fn run(
118 &self,
119 request: &ExecRequest,
120 artifact: &VerifiedArtifact,
121 _ctx: runner::ExecutionContext<'_>,
122 ) -> Result<Value, RunnerError> {
123 let mut payload = request.args.clone();
124 if let Value::Object(map) = &mut payload {
125 map.insert(
126 "component_digest".to_string(),
127 Value::String(artifact.resolved.digest.clone()),
128 );
129 }
130 Ok(payload)
131 }
132 }
133
134 #[test]
135 fn local_resolve_and_verify_success() {
136 let tempdir = tempfile::tempdir().expect("tempdir");
137 let wasm_path = tempdir.path().join("echo.component.wasm");
138 std::fs::write(&wasm_path, b"fake wasm contents").expect("write");
139
140 let digest = crate::resolve::resolve(
141 "echo.component",
142 &ToolStore::LocalDir(PathBuf::from(tempdir.path())),
143 )
144 .expect("resolve")
145 .digest;
146
147 let mut required = HashMap::new();
148 required.insert("echo.component".to_string(), digest.clone());
149
150 let cfg = ExecConfig {
151 store: ToolStore::LocalDir(PathBuf::from(tempdir.path())),
152 security: VerifyPolicy {
153 allow_unverified: false,
154 required_digests: required,
155 trusted_signers: Vec::new(),
156 },
157 runtime: RuntimePolicy::default(),
158 http_enabled: false,
159 secrets_store: None,
160 };
161
162 let req = ExecRequest {
163 component: "echo.component".into(),
164 action: "noop".into(),
165 args: json!({"message": "hello"}),
166 tenant: None,
167 };
168
169 let resolved =
171 crate::resolve::resolve(&req.component, &cfg.store).expect("resolve second time");
172 let verified =
173 crate::verify::verify(&req.component, resolved, &cfg.security).expect("verify");
174 let result = MockRunner
175 .run(
176 &req,
177 &verified,
178 runner::ExecutionContext {
179 runtime: &cfg.runtime,
180 http_enabled: cfg.http_enabled,
181 secrets_store: cfg.secrets_store.clone(),
182 },
183 )
184 .expect("run");
185
186 assert_eq!(
187 result.get("component_digest").and_then(Value::as_str),
188 Some(digest.as_str())
189 );
190 }
191}