From aa729e418152d8f8d8f443ecf8a429f92ec85e2f Mon Sep 17 00:00:00 2001 From: Tsiry Sandratraina Date: Sun, 17 Mar 2024 15:45:09 +0000 Subject: [PATCH] feat: finish git extension feature --- Cargo.lock | 3 + crates/core/src/deps.rs | 13 +++ crates/ext/Cargo.toml | 1 + crates/ext/src/devbox.rs | 65 +---------- crates/ext/src/devenv.rs | 65 +---------- crates/ext/src/envhub.rs | 64 +--------- crates/ext/src/flox.rs | 63 +--------- crates/ext/src/git.rs | 108 +++++++---------- crates/ext/src/git_checkout.rs | 29 +++++ crates/ext/src/git_last_commit.rs | 29 +++++ crates/ext/src/http.rs | 109 +++++++---------- crates/ext/src/lib.rs | 75 +++++++++++- crates/ext/src/nix.rs | 65 +---------- crates/ext/src/pkgx.rs | 71 +---------- crates/ext/src/runner.rs | 67 +---------- crates/graphql/Cargo.toml | 2 + crates/graphql/src/lib.rs | 1 + crates/graphql/src/schema/cache.rs | 2 - crates/graphql/src/schema/devbox.rs | 2 - crates/graphql/src/schema/devenv.rs | 2 - crates/graphql/src/schema/directory.rs | 2 - crates/graphql/src/schema/file.rs | 2 - crates/graphql/src/schema/flox.rs | 2 - crates/graphql/src/schema/git.rs | 19 ++- crates/graphql/src/schema/http.rs | 2 - crates/graphql/src/schema/nix.rs | 2 - crates/graphql/src/schema/objects/devbox.rs | 4 - crates/graphql/src/schema/objects/devenv.rs | 4 - .../graphql/src/schema/objects/directory.rs | 31 ++++- crates/graphql/src/schema/objects/flox.rs | 4 - crates/graphql/src/schema/objects/git.rs | 87 +++++++++++++- crates/graphql/src/schema/objects/nix.rs | 4 - crates/graphql/src/schema/objects/pipeline.rs | 110 +++++++++++++++--- crates/graphql/src/schema/objects/pkgx.rs | 4 - crates/graphql/src/schema/pipeline.rs | 2 - crates/graphql/src/schema/pkgx.rs | 2 - crates/graphql/src/util.rs | 27 +++++ 37 files changed, 510 insertions(+), 634 deletions(-) create mode 100644 crates/ext/src/git_checkout.rs create mode 100644 crates/ext/src/git_last_commit.rs create mode 100644 crates/graphql/src/util.rs diff --git a/Cargo.lock b/Cargo.lock index 6bf7aa8..d9da824 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -891,6 +891,7 @@ dependencies = [ "anyhow", "fluentci-types", "owo-colors", + "regex", "reqwest", "sha256", "users", @@ -907,6 +908,8 @@ dependencies = [ "fluentci-core", "fluentci-ext", "fluentci-types", + "regex", + "tokio", "uuid", ] diff --git a/crates/core/src/deps.rs b/crates/core/src/deps.rs index e334c14..eb5b575 100644 --- a/crates/core/src/deps.rs +++ b/crates/core/src/deps.rs @@ -38,6 +38,7 @@ impl Graph { } pub fn execute(&mut self, command: GraphCommand) { + let skip = vec!["git", "git-checkout", "git-last-commit", "tree", "http"]; match command { GraphCommand::AddVertex(id, label, command, needs) => { if let Some(vertex) = self.vertices.iter_mut().find(|v| v.id == id) { @@ -63,6 +64,7 @@ impl Graph { } } while let Some(i) = stack.pop() { + let label = &self.vertices[i].label.as_str(); if visited[i] { continue; } @@ -71,6 +73,10 @@ impl Graph { stack.push(edge.to); } + if skip.contains(&label) { + continue; + } + let (tx, rx) = mpsc::channel(); if self.vertices[i].label == "withWorkdir" { @@ -118,7 +124,9 @@ impl Graph { stack.push(i); } } + while let Some(i) = stack.pop() { + let label = &self.vertices[i].label.as_str(); if visited[i] { continue; } @@ -126,6 +134,11 @@ impl Graph { for edge in self.edges.iter().filter(|e| e.from == i) { stack.push(edge.to); } + + if skip.contains(&label) { + continue; + } + let (tx, rx) = mpsc::channel(); if self.vertices[i].label == "withWorkdir" { diff --git a/crates/ext/Cargo.toml b/crates/ext/Cargo.toml index 7771af1..260dcd1 100644 --- a/crates/ext/Cargo.toml +++ b/crates/ext/Cargo.toml @@ -12,6 +12,7 @@ version = "0.1.0" anyhow = "1.0.80" fluentci-types = {path = "../types"} owo-colors = "4.0.0" +regex = "1.10.3" reqwest = {version = "0.11.26", features = ["rustls-tls", "blocking"], default-features = false} sha256 = "1.5.0" users = "0.11.0" diff --git a/crates/ext/src/devbox.rs b/crates/ext/src/devbox.rs index 5cfd521..7062a0a 100644 --- a/crates/ext/src/devbox.rs +++ b/crates/ext/src/devbox.rs @@ -1,11 +1,9 @@ use std::{ - io::{BufRead, BufReader}, process::{Command, ExitStatus, Stdio}, - sync::mpsc::{self, Receiver, Sender}, - thread, + sync::mpsc::Sender, }; -use crate::{nix::Nix, Extension}; +use crate::{exec, nix::Nix, Extension}; use anyhow::Error; use fluentci_types::Output; @@ -41,9 +39,6 @@ impl Extension for Devbox { return Ok(ExitStatus::default()); } - let (stdout_tx, stdout_rx): (Sender, Receiver) = mpsc::channel(); - let (stderr_tx, stderr_rx): (Sender, Receiver) = mpsc::channel(); - Command::new("bash") .arg("-c") .arg("[ -f devbox.json ] || devbox init") @@ -53,60 +48,8 @@ impl Extension for Devbox { .spawn()? .wait()?; - let mut child = Command::new("bash") - .arg("-c") - .arg(format!("devbox run {}", cmd)) - .current_dir(work_dir) - .stdout(Stdio::piped()) - .stderr(Stdio::piped()) - .spawn()?; - - let stdout_tx_clone = stdout_tx.clone(); - let stdout = child.stdout.take().unwrap(); - let stderr = child.stderr.take().unwrap(); - - let out_clone = out.clone(); - let tx_clone = tx.clone(); - - thread::spawn(move || { - let mut stdout = String::new(); - while let Ok(line) = stdout_rx.recv() { - println!("{}", line); - stdout.push_str(&line); - stdout.push_str("\n"); - } - if out_clone == Output::Stdout && last_cmd { - tx_clone.send(stdout).unwrap(); - } - }); - - thread::spawn(move || { - let mut stderr = String::new(); - while let Ok(line) = stderr_rx.recv() { - println!("{}", line); - stderr.push_str(&line); - stderr.push_str("\n"); - } - if out == Output::Stderr && last_cmd { - tx.send(stderr).unwrap(); - } - }); - - thread::spawn(move || { - let reader = BufReader::new(stdout); - for line in reader.lines() { - stdout_tx_clone.send(line.unwrap()).unwrap(); - } - }); - - thread::spawn(move || { - let reader = BufReader::new(stderr); - for line in reader.lines() { - stderr_tx.send(line.unwrap()).unwrap(); - } - }); - - child.wait().map_err(Error::from) + let cmd = format!("devbox run {}", cmd); + exec(&cmd, tx, out, last_cmd, work_dir) } fn setup(&self) -> Result<(), Error> { diff --git a/crates/ext/src/devenv.rs b/crates/ext/src/devenv.rs index babc8db..3142f6c 100644 --- a/crates/ext/src/devenv.rs +++ b/crates/ext/src/devenv.rs @@ -1,11 +1,9 @@ use std::{ - io::{BufRead, BufReader}, process::{Command, ExitStatus, Stdio}, - sync::mpsc::{self, Receiver, Sender}, - thread, + sync::mpsc::Sender, }; -use crate::{nix::Nix, pkgx::Pkgx, Extension}; +use crate::{exec, nix::Nix, pkgx::Pkgx, Extension}; use anyhow::Error; use fluentci_types::Output; @@ -29,9 +27,6 @@ impl Extension for Devenv { Pkgx::default().install(vec!["direnv"])?; - let (stdout_tx, stdout_rx): (Sender, Receiver) = mpsc::channel(); - let (stderr_tx, stderr_rx): (Sender, Receiver) = mpsc::channel(); - Command::new("bash") .arg("-c") .arg("[ -f devenv.nix ] || devenv init") @@ -41,60 +36,8 @@ impl Extension for Devenv { .spawn()? .wait()?; - let mut child = Command::new("bash") - .arg("-c") - .arg(format!("devenv shell {}", cmd)) - .current_dir(work_dir) - .stdout(Stdio::piped()) - .stderr(Stdio::piped()) - .spawn()?; - - let stdout_tx_clone = stdout_tx.clone(); - let stdout = child.stdout.take().unwrap(); - let stderr = child.stderr.take().unwrap(); - - let out_clone = out.clone(); - let tx_clone = tx.clone(); - - thread::spawn(move || { - let mut stdout = String::new(); - while let Ok(line) = stdout_rx.recv() { - println!("{}", line); - stdout.push_str(&line); - stdout.push_str("\n"); - } - if out_clone == Output::Stdout && last_cmd { - tx_clone.send(stdout).unwrap(); - } - }); - - thread::spawn(move || { - let mut stderr = String::new(); - while let Ok(line) = stderr_rx.recv() { - println!("{}", line); - stderr.push_str(&line); - stderr.push_str("\n"); - } - if out == Output::Stderr && last_cmd { - tx.send(stderr).unwrap(); - } - }); - - thread::spawn(move || { - let reader = BufReader::new(stdout); - for line in reader.lines() { - stdout_tx_clone.send(line.unwrap()).unwrap(); - } - }); - - thread::spawn(move || { - let reader = BufReader::new(stderr); - for line in reader.lines() { - stderr_tx.send(line.unwrap()).unwrap(); - } - }); - - child.wait().map_err(Error::from) + let cmd = format!("devenv shell {}", cmd); + exec(&cmd, tx, out, last_cmd, work_dir) } fn setup(&self) -> Result<(), Error> { diff --git a/crates/ext/src/envhub.rs b/crates/ext/src/envhub.rs index 261c117..9b4c59a 100644 --- a/crates/ext/src/envhub.rs +++ b/crates/ext/src/envhub.rs @@ -1,11 +1,9 @@ use std::{ - io::{BufRead, BufReader}, process::{Command, ExitStatus, Stdio}, - sync::mpsc::{self, Receiver, Sender}, - thread, + sync::mpsc::Sender, }; -use crate::{nix::Nix, pkgx::Pkgx, Extension}; +use crate::{exec, nix::Nix, pkgx::Pkgx, Extension}; use anyhow::Error; use fluentci_types::Output; @@ -27,63 +25,7 @@ impl Extension for Envhub { return Ok(ExitStatus::default()); } - let (stdout_tx, stdout_rx): (Sender, Receiver) = mpsc::channel(); - let (stderr_tx, stderr_rx): (Sender, Receiver) = mpsc::channel(); - - let mut child = Command::new("bash") - .arg("-c") - .arg(cmd) - .current_dir(work_dir) - .stdout(Stdio::piped()) - .stderr(Stdio::piped()) - .spawn()?; - - let stdout_tx_clone = stdout_tx.clone(); - let stdout = child.stdout.take().unwrap(); - let stderr = child.stderr.take().unwrap(); - - let out_clone = out.clone(); - let tx_clone = tx.clone(); - - thread::spawn(move || { - let mut stdout = String::new(); - while let Ok(line) = stdout_rx.recv() { - println!("{}", line); - stdout.push_str(&line); - stdout.push_str("\n"); - } - if out_clone == Output::Stdout && last_cmd { - tx_clone.send(stdout).unwrap(); - } - }); - - thread::spawn(move || { - let mut stderr = String::new(); - while let Ok(line) = stderr_rx.recv() { - println!("{}", line); - stderr.push_str(&line); - stderr.push_str("\n"); - } - if out == Output::Stderr && last_cmd { - tx.send(stderr).unwrap(); - } - }); - - thread::spawn(move || { - let reader = BufReader::new(stdout); - for line in reader.lines() { - stdout_tx_clone.send(line.unwrap()).unwrap(); - } - }); - - thread::spawn(move || { - let reader = BufReader::new(stderr); - for line in reader.lines() { - stderr_tx.send(line.unwrap()).unwrap(); - } - }); - - child.wait().map_err(Error::from) + exec(cmd, tx, out, last_cmd, work_dir) } fn setup(&self) -> Result<(), Error> { diff --git a/crates/ext/src/flox.rs b/crates/ext/src/flox.rs index 1d9ab4f..c48716b 100644 --- a/crates/ext/src/flox.rs +++ b/crates/ext/src/flox.rs @@ -1,11 +1,9 @@ use std::{ - io::{BufRead, BufReader}, process::{Command, ExitStatus, Stdio}, sync::mpsc::{self, Receiver, Sender}, - thread, }; -use crate::{nix::Nix, Extension}; +use crate::{exec, nix::Nix, Extension}; use anyhow::Error; use fluentci_types::Output; @@ -27,9 +25,6 @@ impl Extension for Flox { return Ok(ExitStatus::default()); } - let (stdout_tx, stdout_rx): (Sender, Receiver) = mpsc::channel(); - let (stderr_tx, stderr_rx): (Sender, Receiver) = mpsc::channel(); - Command::new("bash") .arg("-c") .arg("[ -d .flox ] || flox init") @@ -39,60 +34,8 @@ impl Extension for Flox { .spawn()? .wait()?; - let mut child = Command::new("bash") - .arg("-c") - .arg(format!("flox activate -- {}", cmd)) - .current_dir(work_dir) - .stdout(Stdio::piped()) - .stderr(Stdio::piped()) - .spawn()?; - - let stdout_tx_clone = stdout_tx.clone(); - let stdout = child.stdout.take().unwrap(); - let stderr = child.stderr.take().unwrap(); - - let out_clone = out.clone(); - let tx_clone = tx.clone(); - - thread::spawn(move || { - let mut stdout = String::new(); - while let Ok(line) = stdout_rx.recv() { - println!("{}", line); - stdout.push_str(&line); - stdout.push_str("\n"); - } - if out_clone == Output::Stdout && last_cmd { - tx_clone.send(stdout).unwrap(); - } - }); - - thread::spawn(move || { - let mut stderr = String::new(); - while let Ok(line) = stderr_rx.recv() { - println!("{}", line); - stderr.push_str(&line); - stderr.push_str("\n"); - } - if out == Output::Stderr && last_cmd { - tx.send(stderr).unwrap(); - } - }); - - thread::spawn(move || { - let reader = BufReader::new(stdout); - for line in reader.lines() { - stdout_tx_clone.send(line.unwrap()).unwrap(); - } - }); - - thread::spawn(move || { - let reader = BufReader::new(stderr); - for line in reader.lines() { - stderr_tx.send(line.unwrap()).unwrap(); - } - }); - - child.wait().map_err(Error::from) + let cmd = format!("flox activate -- {}", cmd); + exec(&cmd, tx, out, last_cmd, work_dir) } fn setup(&self) -> Result<(), Error> { diff --git a/crates/ext/src/git.rs b/crates/ext/src/git.rs index 05ad052..d5ded07 100644 --- a/crates/ext/src/git.rs +++ b/crates/ext/src/git.rs @@ -1,17 +1,29 @@ -use std::{ - io::{BufRead, BufReader}, - process::{Command, ExitStatus, Stdio}, - sync::mpsc::{self, Receiver, Sender}, - thread, -}; +use std::{path::Path, process::ExitStatus, sync::mpsc::Sender}; -use crate::{pkgx::Pkgx, Extension}; +use crate::{exec, pkgx::Pkgx, Extension}; use anyhow::Error; use fluentci_types::Output; #[derive(Default)] pub struct Git {} +impl Git { + pub fn validate_url(&self, url: &str) -> Result<(), Error> { + if url.is_empty() { + return Err(Error::msg("URL is empty")); + } + if !regex::Regex::new( + r"^(?:https:\/\/([^\/]+)\/([^\/]+)\/([^\/]+)|git@([^:]+):([^\/]+)\/([^\/]+))$", + ) + .unwrap() + .is_match(url) + { + return Err(Error::msg("Invalid URL")); + } + Ok(()) + } +} + impl Extension for Git { fn exec( &self, @@ -23,67 +35,20 @@ impl Extension for Git { ) -> Result { self.setup()?; - if url.is_empty() { - return Ok(ExitStatus::default()); + if self.validate_url(url).is_err() { + return Err(Error::msg("Invalid URL")); } - let (stdout_tx, stdout_rx): (Sender, Receiver) = mpsc::channel(); - let (stderr_tx, stderr_rx): (Sender, Receiver) = mpsc::channel(); - - let mut child = Command::new("bash") - .arg("-c") - .arg(format!("git clone {}", url)) - .current_dir(work_dir) - .stdout(Stdio::piped()) - .stderr(Stdio::piped()) - .spawn()?; - - let stdout_tx_clone = stdout_tx.clone(); - let stdout = child.stdout.take().unwrap(); - let stderr = child.stderr.take().unwrap(); - - let out_clone = out.clone(); - let tx_clone = tx.clone(); - - thread::spawn(move || { - let mut stdout = String::new(); - while let Ok(line) = stdout_rx.recv() { - println!("{}", line); - stdout.push_str(&line); - stdout.push_str("\n"); - } - if out_clone == Output::Stdout && last_cmd { - tx_clone.send(stdout).unwrap(); - } - }); - - thread::spawn(move || { - let mut stderr = String::new(); - while let Ok(line) = stderr_rx.recv() { - println!("{}", line); - stderr.push_str(&line); - stderr.push_str("\n"); - } - if out == Output::Stderr && last_cmd { - tx.send(stderr).unwrap(); - } - }); - - thread::spawn(move || { - let reader = BufReader::new(stdout); - for line in reader.lines() { - stdout_tx_clone.send(line.unwrap()).unwrap(); - } - }); - - thread::spawn(move || { - let reader = BufReader::new(stderr); - for line in reader.lines() { - stderr_tx.send(line.unwrap()).unwrap(); - } - }); + let repo = url.split('/').last().unwrap().replace(".git", ""); + let git_dir = format!("{}/{}/.git", work_dir, repo); + if Path::new(&git_dir).exists() { + let cmd = format!("git pull {}", url); + let work_dir = format!("{}/{}", work_dir, repo); + return exec(&cmd, tx, out, last_cmd, &work_dir); + } - child.wait().map_err(Error::from) + let cmd = format!("git clone {}", url); + exec(&cmd, tx, out, last_cmd, work_dir) } fn setup(&self) -> Result<(), Error> { @@ -91,3 +56,16 @@ impl Extension for Git { Ok(()) } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_validate_url() { + let git = Git::default(); + assert!(git.validate_url("https://github.com/tsirysndr/me").is_ok()); + assert!(git.validate_url("git@github.com:tsirysndr/me").is_ok()); + assert!(git.validate_url("github.com:tsirysndr/me").is_err()); + } +} diff --git a/crates/ext/src/git_checkout.rs b/crates/ext/src/git_checkout.rs new file mode 100644 index 0000000..271caae --- /dev/null +++ b/crates/ext/src/git_checkout.rs @@ -0,0 +1,29 @@ +use std::{process::ExitStatus, sync::mpsc::Sender}; + +use crate::{exec, pkgx::Pkgx, Extension}; +use anyhow::Error; +use fluentci_types::Output; + +#[derive(Default)] +pub struct GitCheckout {} + +impl Extension for GitCheckout { + fn exec( + &self, + branch: &str, + tx: Sender, + out: Output, + last_cmd: bool, + work_dir: &str, + ) -> Result { + self.setup()?; + + let cmd = format!("git checkout {}", branch); + exec(&cmd, tx, out, last_cmd, work_dir) + } + + fn setup(&self) -> Result<(), Error> { + Pkgx::default().install(vec!["git"])?; + Ok(()) + } +} diff --git a/crates/ext/src/git_last_commit.rs b/crates/ext/src/git_last_commit.rs new file mode 100644 index 0000000..2bd44eb --- /dev/null +++ b/crates/ext/src/git_last_commit.rs @@ -0,0 +1,29 @@ +use std::{process::ExitStatus, sync::mpsc::Sender}; + +use crate::{exec, pkgx::Pkgx, Extension}; +use anyhow::Error; +use fluentci_types::Output; + +#[derive(Default)] +pub struct GitLastCommit {} + +impl Extension for GitLastCommit { + fn exec( + &self, + branch: &str, + tx: Sender, + out: Output, + last_cmd: bool, + work_dir: &str, + ) -> Result { + self.setup()?; + + let cmd = format!("git log -1 --pretty=format:%H {}", branch); + exec(&cmd, tx, out, last_cmd, work_dir) + } + + fn setup(&self) -> Result<(), Error> { + Pkgx::default().install(vec!["git"])?; + Ok(()) + } +} diff --git a/crates/ext/src/http.rs b/crates/ext/src/http.rs index 7e1899a..ac4fa9a 100644 --- a/crates/ext/src/http.rs +++ b/crates/ext/src/http.rs @@ -1,17 +1,27 @@ -use std::{ - io::{BufRead, BufReader}, - process::{Command, ExitStatus, Stdio}, - sync::mpsc::{self, Receiver, Sender}, - thread, -}; +use std::{process::ExitStatus, sync::mpsc::Sender}; -use crate::{pkgx::Pkgx, Extension}; +use crate::{exec, pkgx::Pkgx, Extension}; use anyhow::Error; use fluentci_types::Output; #[derive(Default)] pub struct Http {} +impl Http { + pub fn validate_url(&self, url: &str) -> Result<(), Error> { + if url.is_empty() { + return Err(Error::msg("URL is empty")); + } + if !regex::Regex::new(r"^(http|https)://[^ ]+$") + .unwrap() + .is_match(url) + { + return Err(Error::msg("Invalid URL")); + } + Ok(()) + } +} + impl Extension for Http { fn exec( &self, @@ -22,69 +32,14 @@ impl Extension for Http { work_dir: &str, ) -> Result { self.setup()?; - - if url.is_empty() { - return Ok(ExitStatus::default()); + if self.validate_url(url).is_err() { + return Err(Error::msg("Invalid URL")); } - let (stdout_tx, stdout_rx): (Sender, Receiver) = mpsc::channel(); - let (stderr_tx, stderr_rx): (Sender, Receiver) = mpsc::channel(); - let filename = sha256::digest(url).to_string(); - let mut child = Command::new("bash") - .arg("-c") - .arg(format!("curl -s {} -o {}", url, filename)) - .current_dir(work_dir) - .stdout(Stdio::piped()) - .stderr(Stdio::piped()) - .spawn()?; - let stdout_tx_clone = stdout_tx.clone(); - let stdout = child.stdout.take().unwrap(); - let stderr = child.stderr.take().unwrap(); - - let out_clone = out.clone(); - let tx_clone = tx.clone(); - - thread::spawn(move || { - let mut stdout = String::new(); - while let Ok(line) = stdout_rx.recv() { - println!("{}", line); - stdout.push_str(&line); - stdout.push_str("\n"); - } - if out_clone == Output::Stdout && last_cmd { - tx_clone.send(stdout).unwrap(); - } - }); - - thread::spawn(move || { - let mut stderr = String::new(); - while let Ok(line) = stderr_rx.recv() { - println!("{}", line); - stderr.push_str(&line); - stderr.push_str("\n"); - } - if out == Output::Stderr && last_cmd { - tx.send(stderr).unwrap(); - } - }); - - thread::spawn(move || { - let reader = BufReader::new(stdout); - for line in reader.lines() { - stdout_tx_clone.send(line.unwrap()).unwrap(); - } - }); - - thread::spawn(move || { - let reader = BufReader::new(stderr); - for line in reader.lines() { - stderr_tx.send(line.unwrap()).unwrap(); - } - }); - - child.wait().map_err(Error::from) + let cmd = format!("curl -s {} -o {}", url, filename); + exec(&cmd, tx, out, last_cmd, work_dir) } fn setup(&self) -> Result<(), Error> { @@ -92,3 +47,25 @@ impl Extension for Http { Ok(()) } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_validate_url() { + let http = Http::default(); + assert!(http.validate_url("https://example.com").is_ok()); + assert!(http.validate_url("http://example.com").is_ok()); + assert!(http.validate_url("http://example.com/").is_ok()); + assert!(http.validate_url("http://example.com/path").is_ok()); + assert!(http.validate_url("http://example.com/path?query").is_ok()); + assert!(http + .validate_url("http://example.com/path?query#fragment") + .is_ok()); + assert!(http + .validate_url("example.com/path?query#fragment") + .is_err()); + assert!(http.validate_url("ftp://example.com").is_err()); + } +} diff --git a/crates/ext/src/lib.rs b/crates/ext/src/lib.rs index 17dffb9..df1e114 100644 --- a/crates/ext/src/lib.rs +++ b/crates/ext/src/lib.rs @@ -1,4 +1,9 @@ -use std::{process::ExitStatus, sync::mpsc::Sender}; +use std::{ + io::{BufRead, BufReader}, + process::{Command, ExitStatus, Stdio}, + sync::mpsc::{self, Receiver, Sender}, + thread, +}; use anyhow::Error; use fluentci_types::Output; @@ -8,6 +13,8 @@ pub mod devenv; pub mod envhub; pub mod flox; pub mod git; +pub mod git_checkout; +pub mod git_last_commit; pub mod http; pub mod nix; pub mod pkgx; @@ -24,3 +31,69 @@ pub trait Extension { ) -> Result; fn setup(&self) -> Result<(), Error>; } + +pub fn exec( + cmd: &str, + tx: Sender, + out: Output, + last_cmd: bool, + work_dir: &str, +) -> Result { + let (stdout_tx, stdout_rx): (Sender, Receiver) = mpsc::channel(); + let (stderr_tx, stderr_rx): (Sender, Receiver) = mpsc::channel(); + + let mut child = Command::new("bash") + .arg("-c") + .arg(cmd) + .current_dir(work_dir) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn()?; + + let stdout_tx_clone = stdout_tx.clone(); + let stdout = child.stdout.take().unwrap(); + let stderr = child.stderr.take().unwrap(); + + let out_clone = out.clone(); + let tx_clone = tx.clone(); + + thread::spawn(move || { + let mut stdout = String::new(); + while let Ok(line) = stdout_rx.recv() { + println!("{}", line); + stdout.push_str(&line); + stdout.push_str("\n"); + } + if out_clone == Output::Stdout && last_cmd { + tx_clone.send(stdout).unwrap(); + } + }); + + thread::spawn(move || { + let mut stderr = String::new(); + while let Ok(line) = stderr_rx.recv() { + println!("{}", line); + stderr.push_str(&line); + stderr.push_str("\n"); + } + if out == Output::Stderr && last_cmd { + tx.send(stderr).unwrap(); + } + }); + + thread::spawn(move || { + let reader = BufReader::new(stdout); + for line in reader.lines() { + stdout_tx_clone.send(line.unwrap()).unwrap(); + } + }); + + thread::spawn(move || { + let reader = BufReader::new(stderr); + for line in reader.lines() { + stderr_tx.send(line.unwrap()).unwrap(); + } + }); + + child.wait().map_err(Error::from) +} diff --git a/crates/ext/src/nix.rs b/crates/ext/src/nix.rs index 776f685..c118d86 100644 --- a/crates/ext/src/nix.rs +++ b/crates/ext/src/nix.rs @@ -1,13 +1,11 @@ use std::{ env, - io::{BufRead, BufReader}, process::{Command, ExitStatus, Stdio}, - sync::mpsc::{self, Receiver, Sender}, - thread, + sync::mpsc::Sender, }; use users::get_current_username; -use crate::Extension; +use crate::{exec, Extension}; use anyhow::Error; use fluentci_types::Output; @@ -29,9 +27,6 @@ impl Extension for Nix { return Ok(ExitStatus::default()); } - let (stdout_tx, stdout_rx): (Sender, Receiver) = mpsc::channel(); - let (stderr_tx, stderr_rx): (Sender, Receiver) = mpsc::channel(); - Command::new("bash") .arg("-c") .arg("nix flake init") @@ -41,60 +36,8 @@ impl Extension for Nix { .spawn()? .wait()?; - let mut child = Command::new("bash") - .arg("-c") - .arg(format!("nix develop -c {}", cmd)) - .current_dir(work_dir) - .stdout(Stdio::piped()) - .stderr(Stdio::piped()) - .spawn()?; - - let stdout_tx_clone = stdout_tx.clone(); - let stdout = child.stdout.take().unwrap(); - let stderr = child.stderr.take().unwrap(); - - let out_clone = out.clone(); - let tx_clone = tx.clone(); - - thread::spawn(move || { - let mut stdout = String::new(); - while let Ok(line) = stdout_rx.recv() { - println!("{}", line); - stdout.push_str(&line); - stdout.push_str("\n"); - } - if out_clone == Output::Stdout && last_cmd { - tx_clone.send(stdout).unwrap(); - } - }); - - thread::spawn(move || { - let mut stderr = String::new(); - while let Ok(line) = stderr_rx.recv() { - println!("{}", line); - stderr.push_str(&line); - stderr.push_str("\n"); - } - if out == Output::Stderr && last_cmd { - tx.send(stderr).unwrap(); - } - }); - - thread::spawn(move || { - let reader = BufReader::new(stdout); - for line in reader.lines() { - stdout_tx_clone.send(line.unwrap()).unwrap(); - } - }); - - thread::spawn(move || { - let reader = BufReader::new(stderr); - for line in reader.lines() { - stderr_tx.send(line.unwrap()).unwrap(); - } - }); - - child.wait().map_err(Error::from) + let cmd = format!("nix develop -c {}", cmd); + exec(&cmd, tx, out, last_cmd, work_dir) } fn setup(&self) -> Result<(), Error> { diff --git a/crates/ext/src/pkgx.rs b/crates/ext/src/pkgx.rs index b0dcf5b..a707031 100644 --- a/crates/ext/src/pkgx.rs +++ b/crates/ext/src/pkgx.rs @@ -1,12 +1,10 @@ use std::{ env, - io::{BufRead, BufReader}, process::{Command, ExitStatus, Stdio}, - sync::mpsc::{self, Receiver, Sender}, - thread, + sync::mpsc::Sender, }; -use crate::Extension; +use crate::{exec, Extension}; use anyhow::Error; use fluentci_types::Output; @@ -43,69 +41,8 @@ impl Extension for Pkgx { return Ok(ExitStatus::default()); } - let (stdout_tx, stdout_rx): (Sender, Receiver) = mpsc::channel(); - let (stderr_tx, stderr_rx): (Sender, Receiver) = mpsc::channel(); - - let mut child = Command::new("bash") - .arg("-c") - .arg(format!("eval \"$(pkgx --shellcode)\" && dev; {}", cmd)) - .current_dir(work_dir) - .stdout(Stdio::piped()) - .stderr(Stdio::piped()) - .spawn()?; - - let stdout_tx_clone = stdout_tx.clone(); - let stdout = child.stdout.take().unwrap(); - let stderr = child.stderr.take().unwrap(); - - let out_clone = out.clone(); - let tx_clone = tx.clone(); - - thread::spawn(move || { - let mut stdout = String::new(); - while let Ok(line) = stdout_rx.recv() { - println!("{}", line); - stdout.push_str(&line); - stdout.push_str("\n"); - } - if out_clone == Output::Stdout && last_cmd { - match tx_clone.send(stdout) { - Ok(_) => {} - Err(e) => eprintln!("{}", e), - } - } - }); - - thread::spawn(move || { - let mut stderr = String::new(); - while let Ok(line) = stderr_rx.recv() { - println!("{}", line); - stderr.push_str(&line); - stderr.push_str("\n"); - } - if out == Output::Stderr && last_cmd { - match tx.send(stderr) { - Ok(_) => {} - Err(e) => eprintln!("{}", e), - } - } - }); - - thread::spawn(move || { - let reader = BufReader::new(stdout); - for line in reader.lines() { - stdout_tx_clone.send(line.unwrap()).unwrap(); - } - }); - - thread::spawn(move || { - let reader = BufReader::new(stderr); - for line in reader.lines() { - stderr_tx.send(line.unwrap()).unwrap(); - } - }); - - child.wait().map_err(Error::from) + let cmd = format!("eval \"$(pkgx --shellcode)\" && {}", cmd); + exec(&cmd, tx, out, last_cmd, work_dir) } fn setup(&self) -> Result<(), Error> { diff --git a/crates/ext/src/runner.rs b/crates/ext/src/runner.rs index 0f77423..45838a7 100644 --- a/crates/ext/src/runner.rs +++ b/crates/ext/src/runner.rs @@ -1,11 +1,6 @@ -use std::{ - io::{BufRead, BufReader}, - process::{Command, ExitStatus, Stdio}, - sync::mpsc::{self, Receiver, Sender}, - thread, -}; +use std::{process::ExitStatus, sync::mpsc::Sender}; -use crate::Extension; +use crate::{exec, Extension}; use anyhow::Error; use fluentci_types::Output; @@ -25,63 +20,7 @@ impl Extension for Runner { return Ok(ExitStatus::default()); } - let (stdout_tx, stdout_rx): (Sender, Receiver) = mpsc::channel(); - let (stderr_tx, stderr_rx): (Sender, Receiver) = mpsc::channel(); - - let mut child = Command::new("bash") - .arg("-c") - .arg(cmd) - .current_dir(work_dir) - .stdout(Stdio::piped()) - .stderr(Stdio::piped()) - .spawn()?; - - let stdout_tx_clone = stdout_tx.clone(); - let stdout = child.stdout.take().unwrap(); - let stderr = child.stderr.take().unwrap(); - - let out_clone = out.clone(); - let tx_clone = tx.clone(); - - thread::spawn(move || { - let mut stdout = String::new(); - while let Ok(line) = stdout_rx.recv() { - println!("{}", line); - stdout.push_str(&line); - stdout.push_str("\n"); - } - if out_clone == Output::Stdout && last_cmd { - tx_clone.send(stdout).unwrap(); - } - }); - - thread::spawn(move || { - let mut stderr = String::new(); - while let Ok(line) = stderr_rx.recv() { - println!("{}", line); - stderr.push_str(&line); - stderr.push_str("\n"); - } - if out == Output::Stderr && last_cmd { - tx.send(stderr).unwrap(); - } - }); - - thread::spawn(move || { - let reader = BufReader::new(stdout); - for line in reader.lines() { - stdout_tx_clone.send(line.unwrap()).unwrap(); - } - }); - - thread::spawn(move || { - let reader = BufReader::new(stderr); - for line in reader.lines() { - stderr_tx.send(line.unwrap()).unwrap(); - } - }); - - child.wait().map_err(Error::from) + exec(cmd, tx, out, last_cmd, work_dir) } fn setup(&self) -> Result<(), Error> { diff --git a/crates/graphql/Cargo.toml b/crates/graphql/Cargo.toml index 34f4b5f..3a5479f 100644 --- a/crates/graphql/Cargo.toml +++ b/crates/graphql/Cargo.toml @@ -13,6 +13,8 @@ dirs = "5.0.1" fluentci-core = {path = "../core", version = "0.1.0"} fluentci-ext = {path = "../ext", version = "0.1.0"} fluentci-types = {path = "../types", version = "0.1.0"} +regex = "1.10.3" +tokio = "1.36.0" uuid = {version = "1.7.0", features = [ "v4", diff --git a/crates/graphql/src/lib.rs b/crates/graphql/src/lib.rs index c66cd1e..4e41576 100644 --- a/crates/graphql/src/lib.rs +++ b/crates/graphql/src/lib.rs @@ -2,5 +2,6 @@ use async_graphql::{EmptyMutation, EmptySubscription, Schema}; use schema::Query; pub mod schema; +pub mod util; pub type FluentCISchema = Schema; diff --git a/crates/graphql/src/schema/cache.rs b/crates/graphql/src/schema/cache.rs index 050bfc7..5e332f3 100644 --- a/crates/graphql/src/schema/cache.rs +++ b/crates/graphql/src/schema/cache.rs @@ -21,8 +21,6 @@ impl CacheQuery { vec![], )); - drop(graph); - let cache = Cache { id: ID(id), key, diff --git a/crates/graphql/src/schema/devbox.rs b/crates/graphql/src/schema/devbox.rs index ed9c222..c259fbc 100644 --- a/crates/graphql/src/schema/devbox.rs +++ b/crates/graphql/src/schema/devbox.rs @@ -26,8 +26,6 @@ impl DevboxQuery { vec![], )); - drop(graph); - let devbox = Devbox { id: ID(id) }; Ok(devbox) } diff --git a/crates/graphql/src/schema/devenv.rs b/crates/graphql/src/schema/devenv.rs index 80dfcbb..778a56c 100644 --- a/crates/graphql/src/schema/devenv.rs +++ b/crates/graphql/src/schema/devenv.rs @@ -26,8 +26,6 @@ impl DevenvQuery { vec![], )); - drop(graph); - let devenv = Devenv { id: ID(id) }; Ok(devenv) } diff --git a/crates/graphql/src/schema/directory.rs b/crates/graphql/src/schema/directory.rs index 84616fc..ba1e3ea 100644 --- a/crates/graphql/src/schema/directory.rs +++ b/crates/graphql/src/schema/directory.rs @@ -35,8 +35,6 @@ impl DirectoryQuery { vec![], )); - drop(graph); - let directory = Directory { id: ID(id), path }; Ok(directory) } diff --git a/crates/graphql/src/schema/file.rs b/crates/graphql/src/schema/file.rs index b85e2aa..e6b4c70 100644 --- a/crates/graphql/src/schema/file.rs +++ b/crates/graphql/src/schema/file.rs @@ -21,8 +21,6 @@ impl FileQuery { vec![], )); - drop(graph); - let file = File { id: ID(id), path }; Ok(file) } diff --git a/crates/graphql/src/schema/flox.rs b/crates/graphql/src/schema/flox.rs index 5bc1cf0..6840aec 100644 --- a/crates/graphql/src/schema/flox.rs +++ b/crates/graphql/src/schema/flox.rs @@ -26,8 +26,6 @@ impl FloxQuery { vec![], )); - drop(graph); - let flox = Flox { id: ID(id) }; Ok(flox) } diff --git a/crates/graphql/src/schema/git.rs b/crates/graphql/src/schema/git.rs index 468205a..2ec2c45 100644 --- a/crates/graphql/src/schema/git.rs +++ b/crates/graphql/src/schema/git.rs @@ -3,6 +3,8 @@ use std::{ sync::{Arc, Mutex}, }; +use crate::util::{extract_git_repo, validate_git_url}; + use super::objects::git::Git; use async_graphql::{Context, Error, Object, ID}; use fluentci_core::deps::{Graph, GraphCommand}; @@ -24,19 +26,28 @@ impl GitQuery { "{}/.fluentci/cache", dirs::home_dir().unwrap().to_str().unwrap() ); + + if !validate_git_url(&url) { + return Err(Error::new("Invalid git url")); + } + let repo = extract_git_repo(&url); + graph.work_dir = format!("{}/{}", graph.work_dir, repo); + fs::create_dir_all(&graph.work_dir)?; let id = Uuid::new_v4().to_string(); graph.execute(GraphCommand::AddVertex( id.clone(), "git".into(), - url, + url.clone(), vec![], )); graph.execute_vertex(&id)?; - - drop(graph); - + graph.work_dir = format!( + "{}/{}", + graph.work_dir, + url.split("/").last().unwrap().replace(".git", "") + ); let git = Git { id: ID(id) }; Ok(git) } diff --git a/crates/graphql/src/schema/http.rs b/crates/graphql/src/schema/http.rs index c7ae83a..f860841 100644 --- a/crates/graphql/src/schema/http.rs +++ b/crates/graphql/src/schema/http.rs @@ -34,8 +34,6 @@ impl HttpQuery { vec![], )); graph.execute_vertex(&id)?; - - drop(graph); let file = File { id: ID(id), path: "/file".into(), diff --git a/crates/graphql/src/schema/nix.rs b/crates/graphql/src/schema/nix.rs index 6775608..91498de 100644 --- a/crates/graphql/src/schema/nix.rs +++ b/crates/graphql/src/schema/nix.rs @@ -26,8 +26,6 @@ impl NixQuery { vec![], )); - drop(graph); - let nix = Nix { id: ID(id) }; Ok(nix) } diff --git a/crates/graphql/src/schema/objects/devbox.rs b/crates/graphql/src/schema/objects/devbox.rs index f0fc420..e7e59d3 100644 --- a/crates/graphql/src/schema/objects/devbox.rs +++ b/crates/graphql/src/schema/objects/devbox.rs @@ -94,8 +94,6 @@ impl Devbox { let rx = ctx.data::>>>().unwrap(); let rx = rx.lock().unwrap(); let (stdout, code) = rx.recv().unwrap(); - drop(rx); - drop(graph); if code != 0 { return Err(Error::new(format!( @@ -114,8 +112,6 @@ impl Devbox { let rx = ctx.data::>>>().unwrap(); let rx = rx.lock().unwrap(); let (stderr, code) = rx.recv().unwrap(); - drop(rx); - drop(graph); if code != 0 { return Err(Error::new(format!( diff --git a/crates/graphql/src/schema/objects/devenv.rs b/crates/graphql/src/schema/objects/devenv.rs index 6d7c223..788eeee 100644 --- a/crates/graphql/src/schema/objects/devenv.rs +++ b/crates/graphql/src/schema/objects/devenv.rs @@ -94,8 +94,6 @@ impl Devenv { let rx = ctx.data::>>>().unwrap(); let rx = rx.lock().unwrap(); let (stdout, code) = rx.recv().unwrap(); - drop(rx); - drop(graph); if code != 0 { return Err(Error::new(format!( @@ -114,8 +112,6 @@ impl Devenv { let rx = ctx.data::>>>().unwrap(); let rx = rx.lock().unwrap(); let (stderr, code) = rx.recv().unwrap(); - drop(rx); - drop(graph); if code != 0 { return Err(Error::new(format!( diff --git a/crates/graphql/src/schema/objects/directory.rs b/crates/graphql/src/schema/objects/directory.rs index dff7ffc..3601ee8 100644 --- a/crates/graphql/src/schema/objects/directory.rs +++ b/crates/graphql/src/schema/objects/directory.rs @@ -33,6 +33,31 @@ impl Directory { &self.path } + async fn directory(&self, path: String) -> Result { + let id = Uuid::new_v4().to_string(); + let directory = Directory { id: ID(id), path }; + Ok(directory) + } + + async fn entries(&self) -> Result, Error> { + let path = self.path.clone(); + + if !Path::new(&path).exists() { + return Err(Error::new(format!("Path `{}` does not exist", path))); + } + + let entries = tokio::task::spawn_blocking(move || { + let entries = std::fs::read_dir(&path) + .unwrap() + .map(|res| res.unwrap().file_name().into_string().unwrap()) + .collect(); + entries + }) + .await + .unwrap(); + Ok(entries) + } + async fn devbox(&self, ctx: &Context<'_>) -> Result { let graph = ctx.data::>>().unwrap(); let mut graph = graph.lock().unwrap(); @@ -172,7 +197,7 @@ impl Directory { async fn with_exec(&self, ctx: &Context<'_>, args: Vec) -> Result<&Directory, Error> { let graph = ctx.data::>>().unwrap(); let mut graph = graph.lock().unwrap(); - + println!(">> with exec"); let id = Uuid::new_v4().to_string(); let dep_id = graph.vertices[graph.size() - 1].id.clone(); let deps = match graph.size() { @@ -210,8 +235,6 @@ impl Directory { let rx = ctx.data::>>>().unwrap(); let rx = rx.lock().unwrap(); let (stdout, code) = rx.recv().unwrap(); - drop(rx); - drop(graph); if code != 0 { return Err(Error::new(format!( @@ -230,8 +253,6 @@ impl Directory { let rx = ctx.data::>>>().unwrap(); let rx = rx.lock().unwrap(); let (stderr, code) = rx.recv().unwrap(); - drop(rx); - drop(graph); if code != 0 { return Err(Error::new(format!( diff --git a/crates/graphql/src/schema/objects/flox.rs b/crates/graphql/src/schema/objects/flox.rs index 731a546..f36bfd2 100644 --- a/crates/graphql/src/schema/objects/flox.rs +++ b/crates/graphql/src/schema/objects/flox.rs @@ -94,8 +94,6 @@ impl Flox { let rx = ctx.data::>>>().unwrap(); let rx = rx.lock().unwrap(); let (stdout, code) = rx.recv().unwrap(); - drop(rx); - drop(graph); if code != 0 { return Err(Error::new(format!( @@ -114,8 +112,6 @@ impl Flox { let rx = ctx.data::>>>().unwrap(); let rx = rx.lock().unwrap(); let (stderr, code) = rx.recv().unwrap(); - drop(rx); - drop(graph); if code != 0 { return Err(Error::new(format!( diff --git a/crates/graphql/src/schema/objects/git.rs b/crates/graphql/src/schema/objects/git.rs index 3493f30..5a66666 100644 --- a/crates/graphql/src/schema/objects/git.rs +++ b/crates/graphql/src/schema/objects/git.rs @@ -1,4 +1,10 @@ -use async_graphql::{Error, Object, ID}; +use std::sync::{Arc, Mutex}; + +use async_graphql::{Context, Error, Object, ID}; +use fluentci_core::deps::{Graph, GraphCommand}; +use fluentci_ext::git_checkout::GitCheckout as GitCheckoutExt; +use fluentci_ext::git_last_commit::GitLastCommit as GitLastCommitExt; +use fluentci_ext::runner::Runner as RunnerExt; use uuid::Uuid; use super::directory::Directory; @@ -14,15 +20,88 @@ impl Git { &self.id } - async fn branch(&self) -> Result<&Git, Error> { + async fn branch(&self, ctx: &Context<'_>, name: String) -> Result<&Git, Error> { + let graph = ctx.data::>>().unwrap(); + let mut graph = graph.lock().unwrap(); + graph.runner = Arc::new(Box::new(GitCheckoutExt::default())); + graph.runner.setup()?; + + let id = Uuid::new_v4().to_string(); + + let dep_id = graph.vertices[graph.size() - 1].id.clone(); + let deps = match graph.size() { + 1 => vec![], + _ => vec![dep_id], + }; + graph.execute(GraphCommand::AddVertex( + id.clone(), + "git-checkout".into(), + name, + deps, + )); + graph.execute_vertex(&id)?; + + if graph.size() > 2 { + let x = graph.size() - 2; + let y = graph.size() - 1; + graph.execute(GraphCommand::AddEdge(x, y)); + } + Ok(&self) } - async fn tree(&self) -> Result { + async fn commit(&self, ctx: &Context<'_>) -> Result { + let graph = ctx.data::>>().unwrap(); + let mut graph = graph.lock().unwrap(); + graph.runner = Arc::new(Box::new(GitLastCommitExt::default())); + graph.runner.setup()?; + let id = Uuid::new_v4().to_string(); + + let dep_id = graph.vertices[graph.size() - 1].id.clone(); + let deps = match graph.size() { + 1 => vec![], + _ => vec![dep_id], + }; + graph.execute(GraphCommand::AddVertex( + id.clone(), + "git-last-commit".into(), + "".into(), + deps, + )); + graph.execute_vertex(&id)?; + + if graph.size() > 2 { + let x = graph.size() - 2; + let y = graph.size() - 1; + graph.execute(GraphCommand::AddEdge(x, y)); + } + + Ok("".into()) + } + + async fn tree(&self, ctx: &Context<'_>) -> Result { + let id = Uuid::new_v4().to_string(); + let graph = ctx.data::>>().unwrap(); + let mut graph = graph.lock().unwrap(); + + let dep_id = graph.vertices[graph.size() - 1].id.clone(); + + graph.execute(GraphCommand::AddVertex( + id.clone(), + "tree".into(), + "".into(), + vec![dep_id], + )); + + let x = graph.size() - 2; + let y = graph.size() - 1; + graph.execute(GraphCommand::AddEdge(x, y)); + graph.runner = Arc::new(Box::new(RunnerExt::default())); + let directory = Directory { id: ID(id), - path: "/demo".into(), + path: graph.work_dir.clone(), }; Ok(directory) } diff --git a/crates/graphql/src/schema/objects/nix.rs b/crates/graphql/src/schema/objects/nix.rs index a379232..013919e 100644 --- a/crates/graphql/src/schema/objects/nix.rs +++ b/crates/graphql/src/schema/objects/nix.rs @@ -95,8 +95,6 @@ impl Nix { let rx = ctx.data::>>>().unwrap(); let rx = rx.lock().unwrap(); let (stdout, code) = rx.recv().unwrap(); - drop(rx); - drop(graph); if code != 0 { return Err(Error::new(format!( @@ -116,8 +114,6 @@ impl Nix { let rx = ctx.data::>>>().unwrap(); let rx = rx.lock().unwrap(); let (stderr, code) = rx.recv().unwrap(); - drop(rx); - drop(graph); if code != 0 { return Err(Error::new(format!( diff --git a/crates/graphql/src/schema/objects/pipeline.rs b/crates/graphql/src/schema/objects/pipeline.rs index 4901f6f..f4424b6 100644 --- a/crates/graphql/src/schema/objects/pipeline.rs +++ b/crates/graphql/src/schema/objects/pipeline.rs @@ -16,7 +16,10 @@ use fluentci_ext::pkgx::Pkgx as PkgxExt; use fluentci_types::Output; use uuid::Uuid; -use crate::schema::objects::{file::File, git::Git}; +use crate::{ + schema::objects::{file::File, git::Git}, + util::{extract_git_repo, validate_git_url}, +}; use super::{devbox::Devbox, devenv::Devenv, flox::Flox, nix::Nix, pkgx::Pkgx}; @@ -43,15 +46,26 @@ impl Pipeline { fs::create_dir_all(&graph.work_dir)?; let id = Uuid::new_v4().to_string(); + + let dep_id = graph.vertices[graph.size() - 1].id.clone(); + let deps = match graph.size() { + 1 => vec![], + _ => vec![dep_id], + }; graph.execute(GraphCommand::AddVertex( id.clone(), "http".into(), url, - vec![], + deps, )); graph.execute_vertex(&id)?; - drop(graph); + if graph.size() > 2 { + let x = graph.size() - 2; + let y = graph.size() - 1; + graph.execute(GraphCommand::AddEdge(x, y)); + } + let file = File { id: ID(id), path: "/file".into(), @@ -68,17 +82,41 @@ impl Pipeline { "{}/.fluentci/cache", dirs::home_dir().unwrap().to_str().unwrap() ); + + if !validate_git_url(&url) { + return Err(Error::new("Invalid git url")); + } + let repo = extract_git_repo(&url); + graph.work_dir = format!("{}/{}", graph.work_dir, repo); + fs::create_dir_all(&graph.work_dir)?; let id = Uuid::new_v4().to_string(); + + let dep_id = graph.vertices[graph.size() - 1].id.clone(); + let deps = match graph.size() { + 1 => vec![], + _ => vec![dep_id], + }; graph.execute(GraphCommand::AddVertex( id.clone(), "git".into(), - url, - vec![], + url.clone(), + deps, )); graph.execute_vertex(&id)?; - drop(graph); + + if graph.size() > 2 { + let x = graph.size() - 2; + let y = graph.size() - 1; + graph.execute(GraphCommand::AddEdge(x, y)); + } + + graph.work_dir = format!( + "{}/{}", + graph.work_dir, + url.split("/").last().unwrap().replace(".git", "") + ); let git = Git { id: ID(id) }; Ok(git) @@ -91,13 +129,25 @@ impl Pipeline { graph.runner.setup()?; let id = Uuid::new_v4().to_string(); + + let dep_id = graph.vertices[graph.size() - 1].id.clone(); + let deps = match graph.size() { + 1 => vec![], + _ => vec![dep_id], + }; graph.execute(GraphCommand::AddVertex( id.clone(), "devbox".into(), "".into(), - vec![], + deps, )); + if graph.size() > 2 { + let x = graph.size() - 2; + let y = graph.size() - 1; + graph.execute(GraphCommand::AddEdge(x, y)); + } + let devbox = Devbox { id: ID(id) }; Ok(devbox) } @@ -109,13 +159,25 @@ impl Pipeline { graph.runner.setup()?; let id = Uuid::new_v4().to_string(); + + let dep_id = graph.vertices[graph.size() - 1].id.clone(); + let deps = match graph.size() { + 1 => vec![], + _ => vec![dep_id], + }; graph.execute(GraphCommand::AddVertex( id.clone(), "devenv".into(), "".into(), - vec![], + deps, )); + if graph.size() > 2 { + let x = graph.size() - 2; + let y = graph.size() - 1; + graph.execute(GraphCommand::AddEdge(x, y)); + } + let devenv = Devenv { id: ID(id) }; Ok(devenv) } @@ -127,13 +189,25 @@ impl Pipeline { graph.runner.setup()?; let id = Uuid::new_v4().to_string(); + + let dep_id = graph.vertices[graph.size() - 1].id.clone(); + let deps = match graph.size() { + 1 => vec![], + _ => vec![dep_id], + }; graph.execute(GraphCommand::AddVertex( id.clone(), "flox".into(), "".into(), - vec![], + deps, )); + if graph.size() > 2 { + let x = graph.size() - 2; + let y = graph.size() - 1; + graph.execute(GraphCommand::AddEdge(x, y)); + } + let flox = Flox { id: ID(id) }; Ok(flox) } @@ -176,13 +250,25 @@ impl Pipeline { graph.runner.setup()?; let id = Uuid::new_v4().to_string(); + + let dep_id = graph.vertices[graph.size() - 1].id.clone(); + let deps = match graph.size() { + 1 => vec![], + _ => vec![dep_id], + }; graph.execute(GraphCommand::AddVertex( id.clone(), "pkgx".into(), "".into(), - vec![], + deps, )); + if graph.size() > 2 { + let x = graph.size() - 2; + let y = graph.size() - 1; + graph.execute(GraphCommand::AddEdge(x, y)); + } + let pkgx = Pkgx { id: ID(id) }; Ok(pkgx) } @@ -261,8 +347,6 @@ impl Pipeline { let rx = ctx.data::>>>().unwrap(); let rx = rx.lock().unwrap(); let (stdout, code) = rx.recv().unwrap(); - drop(rx); - drop(graph); if code != 0 { return Err(Error::new(format!( @@ -281,8 +365,6 @@ impl Pipeline { let rx = ctx.data::>>>().unwrap(); let rx = rx.lock().unwrap(); let (stderr, code) = rx.recv().unwrap(); - drop(rx); - drop(graph); if code != 0 { return Err(Error::new(format!( diff --git a/crates/graphql/src/schema/objects/pkgx.rs b/crates/graphql/src/schema/objects/pkgx.rs index 5882d03..7b64919 100644 --- a/crates/graphql/src/schema/objects/pkgx.rs +++ b/crates/graphql/src/schema/objects/pkgx.rs @@ -94,8 +94,6 @@ impl Pkgx { let rx = ctx.data::>>>().unwrap(); let rx = rx.lock().unwrap(); let (stdout, code) = rx.recv().unwrap(); - drop(rx); - drop(graph); if code != 0 { return Err(Error::new(format!( @@ -114,8 +112,6 @@ impl Pkgx { let rx = ctx.data::>>>().unwrap(); let rx = rx.lock().unwrap(); let (stderr, code) = rx.recv().unwrap(); - drop(rx); - drop(graph); if code != 0 { return Err(Error::new(format!( diff --git a/crates/graphql/src/schema/pipeline.rs b/crates/graphql/src/schema/pipeline.rs index fca0df3..5d4495d 100644 --- a/crates/graphql/src/schema/pipeline.rs +++ b/crates/graphql/src/schema/pipeline.rs @@ -26,8 +26,6 @@ impl PipelineQuery { "".into(), vec![], )); - - drop(graph); let pipeline = Pipeline { id: ID(id) }; Ok(pipeline) } diff --git a/crates/graphql/src/schema/pkgx.rs b/crates/graphql/src/schema/pkgx.rs index 1aee378..39474af 100644 --- a/crates/graphql/src/schema/pkgx.rs +++ b/crates/graphql/src/schema/pkgx.rs @@ -26,8 +26,6 @@ impl PkgxQuery { vec![], )); - drop(graph); - let pkgx = Pkgx { id: ID(id) }; Ok(pkgx) } diff --git a/crates/graphql/src/util.rs b/crates/graphql/src/util.rs new file mode 100644 index 0000000..806f322 --- /dev/null +++ b/crates/graphql/src/util.rs @@ -0,0 +1,27 @@ +pub fn extract_git_repo(url: &str) -> String { + let mut repo = url + .replace("https://", "") + .replace("http://", "") + .replace("git@", "") + .replace(":", "/") + .replace(".git", ""); + + if repo.ends_with('/') { + repo.pop(); + } + + // remove last part of the url + // example: + // github.com/owner/repo.git -> github.com/owner + let mut parts: Vec<&str> = repo.split('/').collect(); + parts.pop(); + parts.join("/") +} + +pub fn validate_git_url(url: &str) -> bool { + regex::Regex::new( + r"^(?:https:\/\/([^\/]+)\/([^\/]+)\/([^\/]+)|git@([^:]+):([^\/]+)\/([^\/]+))$", + ) + .unwrap() + .is_match(url) +}