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