Repository: comtrya/comtrya (language: rust)

Issue:
New Action: Git Clone



Discussion / hints:
oooh nice. You'll want to think about secrets management here too somehow, for cloning private repos.

Fix patch:
diff --git a/.gitignore b/.gitignore
index 22a120f8..1ddad62e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,4 @@
 /target
 /_cicd-intermediates
 /Cargo.lock
+/.build
diff --git a/examples/git/clone.yaml b/examples/git/clone.yaml
new file mode 100644
index 00000000..df05e624
--- /dev/null
+++ b/examples/git/clone.yaml
@@ -0,0 +1,4 @@
+actions:
+  - action: git.clone
+    repository: https://github.com/comtrya/comtrya
+    directory: ./.build/comtrya
diff --git a/src/actions/git/clone.rs b/src/actions/git/clone.rs
new file mode 100644
index 00000000..89428689
--- /dev/null
+++ b/src/actions/git/clone.rs
@@ -0,0 +1,27 @@
+use crate::contexts::Contexts;
+use crate::steps::Step;
+use crate::{actions::Action, manifests::Manifest};
+use serde::{Deserialize, Serialize};
+use std::path::PathBuf;
+
+#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
+pub struct GitClone {
+    pub repository: String,
+    pub reference: Option<String>,
+    pub directory: String,
+}
+
+impl Action for GitClone {
+    fn plan(&self, _: &Manifest, _: &Contexts) -> Vec<Step> {
+        vec![Step {
+            atom: Box::new(crate::atoms::git::Clone {
+                repository: self.repository.clone(),
+                reference: self.reference.clone(),
+                directory: PathBuf::from(self.directory.clone()),
+                ..Default::default()
+            }),
+            initializers: vec![],
+            finalizers: vec![],
+        }]
+    }
+}
diff --git a/src/actions/git/mod.rs b/src/actions/git/mod.rs
new file mode 100644
index 00000000..ecceaf34
--- /dev/null
+++ b/src/actions/git/mod.rs
@@ -0,0 +1,2 @@
+mod clone;
+pub use clone::GitClone;
diff --git a/src/actions/mod.rs b/src/actions/mod.rs
index 1afb109c..292874e5 100644
--- a/src/actions/mod.rs
+++ b/src/actions/mod.rs
@@ -1,6 +1,7 @@
 mod command;
 mod directory;
 mod file;
+mod git;
 mod package;
 
 use crate::contexts::{to_koto, Contexts};
@@ -11,6 +12,7 @@ use directory::{DirectoryCopy, DirectoryCreate};
 use file::copy::FileCopy;
 use file::download::FileDownload;
 use file::link::FileLink;
+use git::GitClone;
 use koto::{Koto, KotoSettings};
 use package::install::PackageInstall;
 use serde::{Deserialize, Serialize};
@@ -123,6 +125,9 @@ pub enum Actions {
     #[serde(alias = "file.link")]
     FileLink(ConditionalVariantAction<FileLink>),
 
+    #[serde(alias = "git.clone")]
+    GitClone(ConditionalVariantAction<GitClone>),
+
     #[serde(alias = "package.install", alias = "package.installed")]
     PackageInstall(ConditionalVariantAction<PackageInstall>),
 }
@@ -136,6 +141,7 @@ impl Actions {
             Actions::FileCopy(a) => a,
             Actions::FileDownload(a) => a,
             Actions::FileLink(a) => a,
+            Actions::GitClone(a) => a,
             Actions::PackageInstall(a) => a,
         }
     }
diff --git a/src/atoms/git/clone.rs b/src/atoms/git/clone.rs
new file mode 100644
index 00000000..b6807ed5
--- /dev/null
+++ b/src/atoms/git/clone.rs
@@ -0,0 +1,100 @@
+use std::path::PathBuf;
+
+use super::super::Atom;
+use gitsync::GitSync;
+use tracing::instrument;
+
+#[derive(Default)]
+pub struct Clone {
+    pub repository: String,
+    pub directory: PathBuf,
+    pub reference: Option<String>,
+}
+
+impl std::fmt::Display for Clone {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        write!(
+            f,
+            "GitClone {}#{} to {:?}",
+            self.repository,
+            self.reference.clone().unwrap_or(String::from("main")),
+            self.directory,
+        )
+    }
+}
+
+impl Atom for Clone {
+    #[instrument(name = "git.clone.plan", level = "info", skip(self))]
+    fn plan(&self) -> bool {
+        !self.directory.exists()
+    }
+
+    #[instrument(name = "git.clone.execute", level = "info", skip(self))]
+    fn execute(&mut self) -> anyhow::Result<()> {
+        let git_sync = GitSync {
+            repo: self.repository.clone(),
+            branch: self.reference.clone(),
+            dir: self.directory.clone(),
+            ..Default::default()
+        };
+
+        // we may add .sync as another atom
+        git_sync
+            .bootstrap()
+            .map_err(|err| anyhow::anyhow!("{:?}", err))
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn it_can_plan() {
+        let temp_dir = match tempfile::tempdir() {
+            std::result::Result::Ok(dir) => dir,
+            std::result::Result::Err(_) => {
+                assert_eq!(false, true);
+                return;
+            }
+        };
+
+        let git_clone = Clone {
+            repository: String::from("https://github.com/comtrya/comtrya"),
+            directory: temp_dir.path().to_path_buf(),
+            ..Default::default()
+        };
+
+        assert_eq!(false, git_clone.plan());
+
+        let git_clone = Clone {
+            repository: String::from("https://github.com/comtrya/comtrya"),
+            directory: temp_dir.path().join("nonexistent").to_path_buf(),
+            ..Default::default()
+        };
+
+        assert_eq!(true, git_clone.plan());
+    }
+
+    #[test]
+    fn it_can_execute() {
+        let temp_dir = match tempfile::tempdir() {
+            std::result::Result::Ok(dir) => dir,
+            std::result::Result::Err(_) => {
+                assert_eq!(false, true);
+                return;
+            }
+        };
+
+        let mut git_clone = Clone {
+            repository: String::from("https://github.com/comtrya/comtrya"),
+            directory: temp_dir.path().join("clone").to_path_buf(),
+            ..Default::default()
+        };
+
+        match git_clone.execute() {
+            Ok(_) => (),
+            Err(_) => assert_eq!(false, true),
+        }
+    }
+}
diff --git a/src/atoms/git/mod.rs b/src/atoms/git/mod.rs
new file mode 100644
index 00000000..76762a55
--- /dev/null
+++ b/src/atoms/git/mod.rs
@@ -0,0 +1,2 @@
+mod clone;
+pub use clone::Clone;
diff --git a/src/atoms/mod.rs b/src/atoms/mod.rs
index 10284d63..2eaa5a83 100644
--- a/src/atoms/mod.rs
+++ b/src/atoms/mod.rs
@@ -1,6 +1,7 @@
 pub mod command;
 pub mod directory;
 pub mod file;
+pub mod git;
 pub mod http;
 
 pub trait Atom: std::fmt::Display {


Test patch: