1use anyhow::Error;
2use extism::{convert::Json, host_fn};
3use extism::{Manifest, PluginBuilder, Wasm, PTR};
4use fluentci_common::common;
5use fluentci_ext::devbox::Devbox as DevboxExt;
6use fluentci_ext::devenv::Devenv as DevenvExt;
7use fluentci_ext::envhub::Envhub as EnvhubExt;
8use fluentci_ext::flox::Flox as FloxExt;
9use fluentci_ext::git::Git as GitExt;
10use fluentci_ext::hermit::Hermit as HermitExt;
11use fluentci_ext::http::Http as HttpExt;
12use fluentci_ext::mise::Mise as MiseExt;
13use fluentci_ext::nix::Nix as NixExt;
14use fluentci_ext::pixi::Pixi as PixiExt;
15use fluentci_ext::pkgx::Pkgx as PkgxExt;
16use fluentci_ext::runner::Runner as RunnerExt;
17use fluentci_ext::Extension;
18use fluentci_secrets::Provider;
19use fluentci_types::cache::Cache;
20use fluentci_types::file::File;
21use fluentci_types::Module;
22use std::sync::Arc;
23
24use crate::{
25 cache::*, devbox::*, devenv::*, directory::*, envhub::*, file::*, flox::*, git::*, hermit::*,
26 http::*, mise::*, nix::*, pipeline::*, pixi::*, pkgx::*, state::State,
27};
28
29host_fn!(pub set_runner(user_data: State; runner: String) {
30 let state = user_data.get()?;
31 let mut state = state.lock().unwrap();
32 state.runner = runner;
33 Ok(())
34});
35
36host_fn!(pub with_exec(user_data: State; args: Json<Vec<String>>) {
37 let state = user_data.get()?;
38 let state = state.lock().unwrap();
39 let graph = state.graph.clone();
40 let runner = state.runner.clone();
41 let runner: Arc<Box<dyn Extension + Send + Sync>> = match runner.as_str() {
42 "nix" => Arc::new(Box::new(NixExt::default())),
43 "devbox" => Arc::new(Box::new(DevboxExt::default())),
44 "devenv" => Arc::new(Box::new(DevenvExt::default())),
45 "envhub" => Arc::new(Box::new(EnvhubExt::default())),
46 "flox" => Arc::new(Box::new(FloxExt::default())),
47 "git" => Arc::new(Box::new(GitExt::default())),
48 "http" => Arc::new(Box::new(HttpExt::default())),
49 "mise" => Arc::new(Box::new(MiseExt::default())),
50 "runner" => Arc::new(Box::new(RunnerExt::default())),
51 "pixi" => Arc::new(Box::new(PixiExt::default())),
52 "pkgx" => Arc::new(Box::new(PkgxExt::default())),
53 "hermit" => Arc::new(Box::new(HermitExt::default())),
54 _ => Arc::new(Box::new(RunnerExt::default())),
55 };
56 common::with_exec(graph, args.into_inner(), runner)?;
57 Ok(())
58});
59
60host_fn!(pub with_workdir(user_data: State; path: String) {
61 let state = user_data.get()?;
62 let state = state.lock().unwrap();
63 let graph = state.graph.clone();
64 let runner = state.runner.clone();
65 let runner: Arc<Box<dyn Extension + Send + Sync>> = match runner.as_str() {
66 "nix" => Arc::new(Box::new(NixExt::default())),
67 "devbox" => Arc::new(Box::new(DevboxExt::default())),
68 "devenv" => Arc::new(Box::new(DevenvExt::default())),
69 "envhub" => Arc::new(Box::new(EnvhubExt::default())),
70 "flox" => Arc::new(Box::new(FloxExt::default())),
71 "git" => Arc::new(Box::new(GitExt::default())),
72 "http" => Arc::new(Box::new(HttpExt::default())),
73 "mise" => Arc::new(Box::new(MiseExt::default())),
74 "runner" => Arc::new(Box::new(RunnerExt::default())),
75 "pixi" => Arc::new(Box::new(PixiExt::default())),
76 "pkgx" => Arc::new(Box::new(PkgxExt::default())),
77 "hermit" => Arc::new(Box::new(HermitExt::default())),
78 _ => Arc::new(Box::new(RunnerExt::default())),
79 };
80 common::with_workdir(graph, path, runner)?;
81 Ok(())
82});
83
84host_fn!(pub with_cache(user_data: State; cache: Json<Cache>) {
85 let state = user_data.get()?;
86 let state = state.lock().unwrap();
87 let graph = state.graph.clone();
88 let cache = cache.into_inner();
89 common::with_cache(graph, cache.id, cache.path)?;
90 Ok(())
91});
92
93host_fn!(pub with_file(user_data: State; file: Json<File>) {
94 let state = user_data.get()?;
95 let state = state.lock().unwrap();
96 let graph = state.graph.clone();
97 let file = file.into_inner();
98 common::with_file(graph, file.id, file.path)?;
99 Ok(())
100});
101
102host_fn!(pub with_service(user_data: State; service_id: String) {
103 let state = user_data.get()?;
104 let state = state.lock().unwrap();
105 let graph = state.graph.clone();
106 common::with_service(graph, service_id)?;
107 Ok(())
108});
109
110host_fn!(pub stdout(user_data: State;) -> String {
111 let state = user_data.get()?;
112 let state = state.lock().unwrap();
113 let graph = state.graph.clone();
114 let rx = state.rx.clone();
115 common::stdout(graph, rx)
116});
117
118host_fn!(pub stderr(user_data: State;) -> String {
119 let state = user_data.get()?;
120 let state = state.lock().unwrap();
121 let graph = state.graph.clone();
122 let rx = state.rx.clone();
123 common::stderr(graph, rx)
124});
125
126host_fn!(pub zip(user_data: State; path: String) -> Json<File> {
127 let state = user_data.get()?;
128 let state = state.lock().unwrap();
129 let graph = state.graph.clone();
130 match common::zip(graph, path) {
131 Ok(file) => Ok(Json(File::from(file))),
132 Err(e) => Err(e),
133 }
134});
135
136host_fn!(pub tar_czvf(user_data: State; path: String) -> Json<File> {
137 let state = user_data.get()?;
138 let state = state.lock().unwrap();
139 let graph = state.graph.clone();
140 match common::tar_czvf(graph, path) {
141 Ok(file) => Ok(Json(File::from(file))),
142 Err(e) => Err(e),
143 }
144});
145
146host_fn!(pub get_env(user_data: State; key: String) -> String {
147 Ok(std::env::var(key).unwrap_or_default())
148});
149
150host_fn!(pub set_envs(user_data: State; env: Json<Vec<(String, String)>>) {
151 for (key, value) in env.into_inner() {
152 std::env::set_var(key, value);
153 }
154 Ok(())
155});
156
157host_fn!(pub remove_env(user_data: State; key: String) {
158 std::env::remove_var(key);
159 Ok(())
160});
161
162host_fn!(pub has_env(user_data: State; key: String) -> Json<bool> {
163 Ok(Json(std::env::var(key).is_ok()))
164});
165
166host_fn!(pub get_os(user_data: State;) -> String {
167 Ok(std::env::consts::OS.to_string())
168});
169
170host_fn!(pub get_arch(user_data: State;) -> String {
171 Ok(std::env::consts::ARCH.to_string())
172});
173
174host_fn!(pub as_service(user_data: State; name: String) -> Json<Service> {
175 let state = user_data.get()?;
176 let state = state.lock().unwrap();
177 let graph = state.graph.clone();
178 let service = common::as_service(graph, name)?;
179 Ok(Json(service))
180});
181
182host_fn!(pub call(user_data: State; opts: Json<Module>) -> String {
183 let opts = opts.into_inner();
184 let module = opts.url.clone();
185 let module = match module.starts_with("http") {
186 true => Wasm::url(module),
187 false => Wasm::file(module),
188 };
189
190 let manifest = Manifest::new([module]);
191 let mut plugin = PluginBuilder::new(manifest.clone())
192 .with_wasi(true)
193 .with_function("set_runner", [PTR], [], user_data.clone(), set_runner)
194 .with_function("cache", [PTR], [PTR], user_data.clone(), cache)
195 .with_function("devbox", [], [PTR], user_data.clone(), devbox)
196 .with_function("devenv", [], [PTR], user_data.clone(), devenv)
197 .with_function("directory", [PTR], [PTR], user_data.clone(), directory)
198 .with_function("entries", [PTR], [PTR], user_data.clone(), entries)
199 .with_function("envhub", [], [PTR], user_data.clone(), envhub)
200 .with_function("unzip", [PTR], [PTR], user_data.clone(), unzip)
201 .with_function("file", [PTR], [PTR], user_data.clone(), file)
202 .with_function("flox", [], [PTR], user_data.clone(), flox)
203 .with_function("git", [PTR], [PTR], user_data.clone(), git)
204 .with_function("branch", [PTR], [], user_data.clone(), branch)
205 .with_function("commit", [], [PTR], user_data.clone(), commit)
206 .with_function("tree", [], [PTR], user_data.clone(), tree)
207 .with_function("http", [PTR], [PTR], user_data.clone(), http)
208 .with_function("nix", [PTR], [PTR], user_data.clone(), nix)
209 .with_function("pipeline", [PTR], [PTR], user_data.clone(), pipeline)
210 .with_function("pixi", [], [PTR], user_data.clone(), pixi)
211 .with_function("pkgx", [], [PTR], user_data.clone(), pkgx)
212 .with_function("mise", [], [PTR], user_data.clone(), mise)
213 .with_function("trust", [PTR], [], user_data.clone(), trust)
214 .with_function("with_exec", [PTR], [], user_data.clone(), with_exec)
215 .with_function("with_workdir", [PTR], [], user_data.clone(), with_workdir)
216 .with_function("with_cache", [PTR], [], user_data.clone(), with_cache)
217 .with_function("stdout", [], [PTR], user_data.clone(), stdout)
218 .with_function("stderr", [], [PTR], user_data.clone(), stderr)
219 .with_function("zip", [PTR], [PTR], user_data.clone(), zip)
220 .with_function("tar_czvf", [PTR], [PTR], user_data.clone(), tar_czvf)
221 .with_function("tar_xzvf", [PTR], [PTR], user_data.clone(), tar_xzvf)
222 .with_function("md5", [PTR], [PTR], user_data.clone(), md5)
223 .with_function("sha256", [PTR], [PTR], user_data.clone(), sha256)
224 .with_function("chmod", [PTR], [PTR], user_data.clone(), chmod)
225 .with_function("with_file", [PTR], [], user_data.clone(), with_file)
226 .with_function("get_env", [PTR], [PTR], user_data.clone(), get_env)
227 .with_function("set_envs", [PTR], [], user_data.clone(), set_envs)
228 .with_function("remove_env", [PTR], [], user_data.clone(), remove_env)
229 .with_function("has_env", [PTR], [PTR], user_data.clone(), has_env)
230 .with_function("get_os", [], [PTR], user_data.clone(), get_os)
231 .with_function("get_arch", [], [PTR], user_data.clone(), get_arch)
232 .with_function("call", [PTR], [PTR], user_data.clone(), call)
233 .with_function("with_packages", [PTR], [], user_data.clone(), with_packages)
234 .with_function("as_service", [PTR], [PTR], user_data.clone(), as_service)
235 .with_function("with_service", [PTR], [], user_data.clone(), with_service)
236 .with_function("wait_on", [PTR], [], user_data.clone(), wait_on)
237 .with_function("add_secretmanager", [PTR], [PTR], user_data.clone(), add_secretmanager)
238 .with_function("get_secret", [PTR], [PTR], user_data.clone(), get_secret)
239 .with_function("set_secret", [PTR], [PTR], user_data.clone(), set_secret)
240 .with_function("with_secret_variable", [PTR], [], user_data.clone(), with_secret_variable)
241 .with_function("get_secret_plaintext", [PTR], [PTR], user_data.clone(), get_secret_plaintext)
242 .with_function("hermit", [], [PTR], user_data.clone(), hermit)
243 .with_function("install", [], [], user_data.clone(), install)
244 .build()
245 .unwrap();
246
247 let func = opts.function.clone();
248 let args = opts.args.clone();
249 let result = plugin.call::<&str, &str>(func, &args)?;
250 Ok(result.to_string())
251});
252
253host_fn!(pub wait_on(user_data: State; args: Json<Vec<u32>>) {
254 let state = user_data.get()?;
255 let state = state.lock().unwrap();
256 let graph = state.graph.clone();
257 let args = args.into_inner();
258
259 if args.len() == 1 {
260 common::wait_on(graph, args[0], None)?;
261 return Ok(())
262 }
263 if args.len() >= 2 {
264 common::wait_on(graph, args[0], Some(args[1]))?;
265 }
266 Ok(())
267});
268
269host_fn!(pub add_secretmanager(user_data: State; provider: Json<Provider>) -> String {
270 let state = user_data.get()?;
271 let state = state.lock().unwrap();
272 let graph = state.graph.clone();
273 let provider = provider.into_inner();
274 let id = common::add_secretmanager(graph, provider)?;
275 Ok(id)
276});
277
278host_fn!(pub get_secret(user_data: State; params: Json<Vec<String>>) -> Json<Vec<Secret>> {
279 let state = user_data.get()?;
280 let state = state.lock().unwrap();
281 let graph = state.graph.clone();
282 let params = params.into_inner();
283
284 if params.len() != 2 {
285 return Err(Error::msg("Invalid number of arguments"));
286 }
287
288 let secret = common::get_secret(graph, ¶ms[0], ¶ms[1])?;
289 Ok(Json(secret))
290});
291
292host_fn!(pub with_secret_variable(user_data: State; params: Json<Vec<String>>) {
293 let state = user_data.get()?;
294 let state = state.lock().unwrap();
295 let graph = state.graph.clone();
296 let params = params.into_inner();
297 if params.len() != 2 {
298 return Err(Error::msg("Invalid number of arguments"));
299 }
300 let g = graph.lock().unwrap();
301 let secret_name = g.secret_names.get(¶ms[1]).unwrap();
302 let secret_name = secret_name.clone();
303 drop(g);
304
305 common::with_secret_variable(graph, ¶ms[0], ¶ms[1], &secret_name)?;
306 Ok(())
307});
308
309host_fn!(pub set_secret(user_data: State; params: Json<Vec<String>>) -> String {
310 let state = user_data.get()?;
311 let state = state.lock().unwrap();
312 let graph = state.graph.clone();
313 let params = params.into_inner();
314
315 if params.len() != 2 {
316 return Err(Error::msg("Invalid number of arguments"));
317 }
318
319 let secret_id = common::set_secret(graph, ¶ms[0], ¶ms[1])?;
320 Ok(secret_id)
321});
322
323host_fn!(pub get_secret_plaintext(user_data: State; params: Json<Vec<String>>) -> String {
324 let state = user_data.get()?;
325 let state = state.lock().unwrap();
326 let graph = state.graph.clone();
327 let params = params.into_inner();
328
329 if params.len() != 2 {
330 return Err(Error::msg("Invalid number of arguments"));
331 }
332
333 let secret = common::get_secret_plaintext(graph, ¶ms[0], ¶ms[1])?;
334 Ok(secret)
335});