1use std::{
2 fs::{self},
3 sync::{mpsc::Receiver, Arc, Mutex},
4};
5
6use async_graphql::{Context, Error, Object, ID};
7use fluentci_common::common;
8use fluentci_core::deps::{Graph, GraphCommand};
9use fluentci_ext::devbox::Devbox as DevboxExt;
10use fluentci_ext::devenv::Devenv as DevenvExt;
11use fluentci_ext::envhub::Envhub as EnvhubExt;
12use fluentci_ext::flox::Flox as FloxExt;
13use fluentci_ext::git::Git as GitExt;
14use fluentci_ext::hermit::Hermit as HermitExt;
15use fluentci_ext::http::Http as HttpExt;
16use fluentci_ext::mise::Mise as MiseExt;
17use fluentci_ext::nix::Nix as NixExt;
18use fluentci_ext::pixi::Pixi as PixiExt;
19use fluentci_ext::pkgx::Pkgx as PkgxExt;
20use fluentci_ext::proto::Proto as ProtoExt;
21use fluentci_ext::runner::Runner;
22use fluentci_types::{nix, pipeline as types};
23use uuid::Uuid;
24
25use crate::{
26 schema::objects::{file::File, git::Git, mise::Mise, nix::NixArgs},
27 util::{extract_git_repo, validate_git_url},
28};
29
30use super::{
31 devbox::Devbox, devenv::Devenv, envhub::Envhub, flox::Flox, hermit::Hermit, nix::Nix,
32 pixi::Pixi, pkgx::Pkgx, proto::Proto, service::Service,
33};
34
35#[derive(Debug, Clone, Default)]
36pub struct Pipeline {
37 pub id: ID,
38}
39
40#[Object]
41impl Pipeline {
42 async fn id(&self) -> &ID {
43 &self.id
44 }
45
46 async fn http(&self, ctx: &Context<'_>, url: String) -> Result<File, Error> {
47 let graph = ctx.data::<Arc<Mutex<Graph>>>().unwrap();
48 let mut graph = graph.lock().unwrap();
49 graph.runner = Arc::new(Box::new(HttpExt::default()));
50 graph.runner.setup()?;
51 graph.work_dir = format!(
52 "{}/.fluentci/cache",
53 dirs::home_dir().unwrap().to_str().unwrap()
54 );
55 fs::create_dir_all(&graph.work_dir)?;
56
57 let id = Uuid::new_v4().to_string();
58
59 let dep_id = graph.vertices[graph.size() - 1].id.clone();
60 let deps = match graph.size() {
61 1 => vec![],
62 _ => vec![dep_id],
63 };
64
65 graph.execute(GraphCommand::AddVertex(
66 id.clone(),
67 "http".into(),
68 url.clone(),
69 deps,
70 Arc::new(Box::new(HttpExt::default())),
71 ))?;
72 graph.execute_vertex(&id)?;
73
74 if graph.size() > 2 {
75 let x = graph.size() - 2;
76 let y = graph.size() - 1;
77 graph.execute(GraphCommand::AddEdge(x, y))?;
78 }
79 let filename = sha256::digest(url).to_string();
80 let work_dir = graph.work_dir.clone();
81
82 let file = File {
83 id: ID(id.clone()),
84 path: format!("{}/{}", work_dir, filename),
85 };
86
87 graph.execute(GraphCommand::AddVolume(
88 id,
89 "file".into(),
90 file.path.clone(),
91 ))?;
92
93 Ok(file)
94 }
95
96 async fn git(&self, ctx: &Context<'_>, url: String) -> Result<Git, Error> {
97 let graph = ctx.data::<Arc<Mutex<Graph>>>().unwrap();
98 let mut graph = graph.lock().unwrap();
99 graph.runner = Arc::new(Box::new(GitExt::default()));
100 graph.runner.setup()?;
101 graph.work_dir = format!(
102 "{}/.fluentci/cache",
103 dirs::home_dir().unwrap().to_str().unwrap()
104 );
105
106 if !validate_git_url(&url) {
107 return Err(Error::new("Invalid git url"));
108 }
109 let repo = extract_git_repo(&url);
110 graph.work_dir = format!("{}/{}", graph.work_dir, repo);
111
112 fs::create_dir_all(&graph.work_dir)?;
113
114 let id = Uuid::new_v4().to_string();
115
116 let dep_id = graph.vertices[graph.size() - 1].id.clone();
117 let deps = match graph.size() {
118 1 => vec![],
119 _ => vec![dep_id],
120 };
121 graph.execute(GraphCommand::AddVertex(
122 id.clone(),
123 "git".into(),
124 url.clone(),
125 deps,
126 Arc::new(Box::new(GitExt::default())),
127 ))?;
128 graph.execute_vertex(&id)?;
129
130 if graph.size() > 2 {
131 let x = graph.size() - 2;
132 let y = graph.size() - 1;
133 graph.execute(GraphCommand::AddEdge(x, y))?;
134 }
135
136 graph.work_dir = format!(
137 "{}/{}",
138 graph.work_dir,
139 url.split("/").last().unwrap().replace(".git", "")
140 );
141
142 let git = Git { id: ID(id) };
143 Ok(git)
144 }
145
146 async fn devbox(&self, ctx: &Context<'_>) -> Result<Devbox, Error> {
147 let graph = ctx.data::<Arc<Mutex<Graph>>>().unwrap();
148 let mut graph = graph.lock().unwrap();
149 graph.runner = Arc::new(Box::new(DevboxExt::default()));
150 graph.runner.setup()?;
151
152 let id = Uuid::new_v4().to_string();
153
154 let dep_id = graph.vertices[graph.size() - 1].id.clone();
155 let deps = match graph.size() {
156 1 => vec![],
157 _ => vec![dep_id],
158 };
159 graph.execute(GraphCommand::AddVertex(
160 id.clone(),
161 "devbox".into(),
162 "".into(),
163 deps,
164 Arc::new(Box::new(DevboxExt::default())),
165 ))?;
166
167 if graph.size() > 2 {
168 let x = graph.size() - 2;
169 let y = graph.size() - 1;
170 graph.execute(GraphCommand::AddEdge(x, y))?;
171 }
172
173 let devbox = Devbox { id: ID(id) };
174 Ok(devbox)
175 }
176
177 async fn devenv(&self, ctx: &Context<'_>) -> Result<Devenv, Error> {
178 let graph = ctx.data::<Arc<Mutex<Graph>>>().unwrap();
179 let mut graph = graph.lock().unwrap();
180 graph.runner = Arc::new(Box::new(DevenvExt::default()));
181 graph.runner.setup()?;
182
183 let id = Uuid::new_v4().to_string();
184
185 let dep_id = graph.vertices[graph.size() - 1].id.clone();
186 let deps = match graph.size() {
187 1 => vec![],
188 _ => vec![dep_id],
189 };
190 graph.execute(GraphCommand::AddVertex(
191 id.clone(),
192 "devenv".into(),
193 "".into(),
194 deps,
195 Arc::new(Box::new(DevenvExt::default())),
196 ))?;
197
198 if graph.size() > 2 {
199 let x = graph.size() - 2;
200 let y = graph.size() - 1;
201 graph.execute(GraphCommand::AddEdge(x, y))?;
202 }
203
204 let devenv = Devenv { id: ID(id) };
205 Ok(devenv)
206 }
207
208 async fn flox(&self, ctx: &Context<'_>) -> Result<Flox, Error> {
209 let graph = ctx.data::<Arc<Mutex<Graph>>>().unwrap();
210 let mut graph = graph.lock().unwrap();
211 graph.runner = Arc::new(Box::new(FloxExt::default()));
212 graph.runner.setup()?;
213
214 let id = Uuid::new_v4().to_string();
215
216 let dep_id = graph.vertices[graph.size() - 1].id.clone();
217 let deps = match graph.size() {
218 1 => vec![],
219 _ => vec![dep_id],
220 };
221 graph.execute(GraphCommand::AddVertex(
222 id.clone(),
223 "flox".into(),
224 "".into(),
225 deps,
226 Arc::new(Box::new(FloxExt::default())),
227 ))?;
228
229 if graph.size() > 2 {
230 let x = graph.size() - 2;
231 let y = graph.size() - 1;
232 graph.execute(GraphCommand::AddEdge(x, y))?;
233 }
234
235 let flox = Flox { id: ID(id) };
236 Ok(flox)
237 }
238
239 async fn nix(&self, ctx: &Context<'_>, args: Option<NixArgs>) -> Result<Nix, Error> {
240 let graph = ctx.data::<Arc<Mutex<Graph>>>().unwrap();
241 let mut graph = graph.lock().unwrap();
242 let args: nix::NixArgs = args.unwrap_or_default().into();
243 graph.runner = Arc::new(Box::new(NixExt::new(args.clone())));
244 graph.runner.setup()?;
245 graph.nix_args = args.clone();
246
247 let id = Uuid::new_v4().to_string();
248
249 let dep_id = graph.vertices[graph.size() - 1].id.clone();
250 let deps = match graph.size() {
251 1 => vec![],
252 _ => vec![dep_id],
253 };
254
255 graph.execute(GraphCommand::AddVertex(
256 id.clone(),
257 "nix".into(),
258 "".into(),
259 deps,
260 Arc::new(Box::new(NixExt::new(args))),
261 ))?;
262
263 if graph.size() > 2 {
264 let x = graph.size() - 2;
265 let y = graph.size() - 1;
266 graph.execute(GraphCommand::AddEdge(x, y))?;
267 }
268
269 let nix = Nix { id: ID(id) };
270 Ok(nix)
271 }
272
273 async fn pkgx(&self, ctx: &Context<'_>) -> Result<Pkgx, Error> {
274 let graph = ctx.data::<Arc<Mutex<Graph>>>().unwrap();
275 let mut graph = graph.lock().unwrap();
276 graph.runner = Arc::new(Box::new(PkgxExt::default()));
277 graph.runner.setup()?;
278
279 let id = Uuid::new_v4().to_string();
280
281 let dep_id = graph.vertices[graph.size() - 1].id.clone();
282 let deps = match graph.size() {
283 1 => vec![],
284 _ => vec![dep_id],
285 };
286 graph.execute(GraphCommand::AddVertex(
287 id.clone(),
288 "pkgx".into(),
289 "".into(),
290 deps,
291 Arc::new(Box::new(PkgxExt::default())),
292 ))?;
293
294 if graph.size() > 2 {
295 let x = graph.size() - 2;
296 let y = graph.size() - 1;
297 graph.execute(GraphCommand::AddEdge(x, y))?;
298 }
299
300 let pkgx = Pkgx { id: ID(id) };
301 Ok(pkgx)
302 }
303
304 async fn pixi(&self, ctx: &Context<'_>) -> Result<Pixi, Error> {
305 let graph = ctx.data::<Arc<Mutex<Graph>>>().unwrap();
306 let mut graph = graph.lock().unwrap();
307 graph.runner = Arc::new(Box::new(PixiExt::default()));
308 graph.runner.setup()?;
309
310 let id = Uuid::new_v4().to_string();
311
312 let dep_id = graph.vertices[graph.size() - 1].id.clone();
313 let deps = match graph.size() {
314 1 => vec![],
315 _ => vec![dep_id],
316 };
317 graph.execute(GraphCommand::AddVertex(
318 id.clone(),
319 "pixi".into(),
320 "".into(),
321 deps,
322 Arc::new(Box::new(PixiExt::default())),
323 ))?;
324
325 if graph.size() > 2 {
326 let x = graph.size() - 2;
327 let y = graph.size() - 1;
328 graph.execute(GraphCommand::AddEdge(x, y))?;
329 }
330
331 let pixi = Pixi { id: ID(id) };
332 Ok(pixi)
333 }
334
335 async fn proto(&self, ctx: &Context<'_>) -> Result<Proto, Error> {
336 let graph = ctx.data::<Arc<Mutex<Graph>>>().unwrap();
337 let mut graph = graph.lock().unwrap();
338 graph.runner = Arc::new(Box::new(ProtoExt::default()));
339 graph.runner.setup()?;
340
341 let id = Uuid::new_v4().to_string();
342
343 let dep_id = graph.vertices[graph.size() - 1].id.clone();
344 let deps = match graph.size() {
345 1 => vec![],
346 _ => vec![dep_id],
347 };
348 graph.execute(GraphCommand::AddVertex(
349 id.clone(),
350 "proto".into(),
351 "".into(),
352 deps,
353 Arc::new(Box::new(ProtoExt::default())),
354 ))?;
355
356 if graph.size() > 2 {
357 let x = graph.size() - 2;
358 let y = graph.size() - 1;
359 graph.execute(GraphCommand::AddEdge(x, y))?;
360 }
361
362 let proto = Proto { id: ID(id) };
363 Ok(proto)
364 }
365
366 async fn hermit(&self, ctx: &Context<'_>) -> Result<Hermit, Error> {
367 let graph = ctx.data::<Arc<Mutex<Graph>>>().unwrap();
368 let mut graph = graph.lock().unwrap();
369 graph.runner = Arc::new(Box::new(HermitExt::default()));
370 graph.runner.setup()?;
371
372 let id = Uuid::new_v4().to_string();
373
374 let dep_id = graph.vertices[graph.size() - 1].id.clone();
375 let deps = match graph.size() {
376 1 => vec![],
377 _ => vec![dep_id],
378 };
379 graph.execute(GraphCommand::AddVertex(
380 id.clone(),
381 "hermit".into(),
382 "".into(),
383 deps,
384 Arc::new(Box::new(HermitExt::default())),
385 ))?;
386
387 if graph.size() > 2 {
388 let x = graph.size() - 2;
389 let y = graph.size() - 1;
390 graph.execute(GraphCommand::AddEdge(x, y))?;
391 }
392
393 let hermit = Hermit { id: ID(id) };
394 Ok(hermit)
395 }
396
397 async fn mise(&self, ctx: &Context<'_>) -> Result<Mise, Error> {
398 let graph = ctx.data::<Arc<Mutex<Graph>>>().unwrap();
399 let mut graph = graph.lock().unwrap();
400 graph.runner = Arc::new(Box::new(MiseExt::default()));
401 graph.runner.setup()?;
402
403 let id = Uuid::new_v4().to_string();
404
405 let dep_id = graph.vertices[graph.size() - 1].id.clone();
406 let deps = match graph.size() {
407 1 => vec![],
408 _ => vec![dep_id],
409 };
410 graph.execute(GraphCommand::AddVertex(
411 id.clone(),
412 "mise".into(),
413 "".into(),
414 deps,
415 Arc::new(Box::new(MiseExt::default())),
416 ))?;
417
418 if graph.size() > 2 {
419 let x = graph.size() - 2;
420 let y = graph.size() - 1;
421 graph.execute(GraphCommand::AddEdge(x, y))?;
422 }
423
424 let mise = Mise { id: ID(id) };
425 Ok(mise)
426 }
427
428 async fn envhub(&self, ctx: &Context<'_>) -> Result<Envhub, Error> {
429 let graph = ctx.data::<Arc<Mutex<Graph>>>().unwrap();
430 let mut graph = graph.lock().unwrap();
431 graph.runner = Arc::new(Box::new(EnvhubExt::default()));
432 graph.runner.setup()?;
433
434 let id = Uuid::new_v4().to_string();
435
436 let dep_id = graph.vertices[graph.size() - 1].id.clone();
437 let deps = match graph.size() {
438 1 => vec![],
439 _ => vec![dep_id],
440 };
441 graph.execute(GraphCommand::AddVertex(
442 id.clone(),
443 "envhub".into(),
444 "".into(),
445 deps,
446 Arc::new(Box::new(EnvhubExt::default())),
447 ))?;
448
449 if graph.size() > 2 {
450 let x = graph.size() - 2;
451 let y = graph.size() - 1;
452 graph.execute(GraphCommand::AddEdge(x, y))?;
453 }
454
455 let envhub = Envhub { id: ID(id) };
456 Ok(envhub)
457 }
458
459 async fn with_exec(&self, ctx: &Context<'_>, args: Vec<String>) -> Result<&Pipeline, Error> {
460 let graph = ctx.data::<Arc<Mutex<Graph>>>().unwrap();
461 common::with_exec(graph.clone(), args, Arc::new(Box::new(Runner::default())))?;
462 Ok(self)
463 }
464
465 async fn with_workdir(&self, ctx: &Context<'_>, path: String) -> Result<&Pipeline, Error> {
466 let graph = ctx.data::<Arc<Mutex<Graph>>>().unwrap();
467 common::with_workdir(graph.clone(), path, Arc::new(Box::new(Runner::default())))?;
468 Ok(self)
469 }
470
471 async fn with_service(&self, ctx: &Context<'_>, service: ID) -> Result<&Pipeline, Error> {
472 let graph = ctx.data::<Arc<Mutex<Graph>>>().unwrap();
473 common::with_service(graph.clone(), service.into())?;
474 Ok(self)
475 }
476
477 async fn with_cache(
478 &self,
479 ctx: &Context<'_>,
480 path: String,
481 cache: ID,
482 ) -> Result<&Pipeline, Error> {
483 let graph = ctx.data::<Arc<Mutex<Graph>>>().unwrap();
484 common::with_cache(graph.clone(), cache.into(), path)?;
485 Ok(self)
486 }
487
488 async fn with_file(
489 &self,
490 ctx: &Context<'_>,
491 path: String,
492 file_id: ID,
493 ) -> Result<&Pipeline, Error> {
494 let graph = ctx.data::<Arc<Mutex<Graph>>>().unwrap();
495 common::with_file(graph.clone(), file_id.into(), path)?;
496 Ok(self)
497 }
498
499 async fn stdout(&self, ctx: &Context<'_>) -> Result<String, Error> {
500 let graph = ctx.data::<Arc<Mutex<Graph>>>().unwrap();
501 let rx = ctx.data::<Arc<Mutex<Receiver<(String, usize)>>>>().unwrap();
502 common::stdout(graph.clone(), rx.clone()).map_err(|e| Error::new(e.to_string()))
503 }
504
505 async fn stderr(&self, ctx: &Context<'_>) -> Result<String, Error> {
506 let graph = ctx.data::<Arc<Mutex<Graph>>>().unwrap();
507 let rx = ctx.data::<Arc<Mutex<Receiver<(String, usize)>>>>().unwrap();
508 common::stderr(graph.clone(), rx.clone()).map_err(|e| Error::new(e.to_string()))
509 }
510
511 async fn as_service(&self, ctx: &Context<'_>, name: String) -> Result<Service, Error> {
512 let graph = ctx.data::<Arc<Mutex<Graph>>>().unwrap();
513 let service = common::as_service(graph.clone(), name)?;
514 Ok(service.into())
515 }
516
517 async fn with_env_variable(
518 &self,
519 ctx: &Context<'_>,
520 name: String,
521 value: String,
522 ) -> Result<&Pipeline, Error> {
523 let graph = ctx.data::<Arc<Mutex<Graph>>>().unwrap();
524 common::with_env_variable(graph.clone(), &name, &value)?;
525 Ok(self)
526 }
527
528 async fn wait_on(
529 &self,
530 ctx: &Context<'_>,
531 port: u32,
532 timeout: Option<u32>,
533 ) -> Result<&Pipeline, Error> {
534 let graph = ctx.data::<Arc<Mutex<Graph>>>().unwrap();
535 common::wait_on(graph.clone(), port, timeout)?;
536 Ok(self)
537 }
538
539 async fn with_secret_variable(
540 &self,
541 ctx: &Context<'_>,
542 name: String,
543 secret: ID,
544 ) -> Result<&Pipeline, Error> {
545 let graph = ctx.data::<Arc<Mutex<Graph>>>().unwrap();
546 let g = graph.lock().unwrap();
547 let secret_name = g.secret_names.get(secret.as_str()).unwrap().clone();
548 drop(g);
549 common::with_secret_variable(graph.clone(), &name, secret.as_str(), &secret_name)?;
550 Ok(self)
551 }
552}
553
554impl From<types::Pipeline> for Pipeline {
555 fn from(pipeline: types::Pipeline) -> Self {
556 Self {
557 id: ID(pipeline.id),
558 }
559 }
560}