feat: can clone using git

This commit is contained in:
Kasper Juul Hermansen 2025-02-28 23:08:40 +01:00
parent f10a1c0ac5
commit dfe2ac62e3
Signed by: kjuulh
SSH Key Fingerprint: SHA256:RjXh0p7U6opxnfd3ga/Y9TCo18FYlHFdSpRIV72S/QM
5 changed files with 123 additions and 0 deletions

View File

@ -1,5 +1,6 @@
use std::{collections::BTreeMap, fmt::Debug, path::PathBuf}; use std::{collections::BTreeMap, fmt::Debug, path::PathBuf};
use colored_json::Paint;
use kdl::{KdlDocument, KdlNode, KdlValue}; use kdl::{KdlDocument, KdlNode, KdlValue};
use serde::Serialize; use serde::Serialize;
@ -53,6 +54,7 @@ impl TryFrom<KdlDocument> for Plan {
#[serde(untagged)] #[serde(untagged)]
pub enum ProjectPlan { pub enum ProjectPlan {
Local { path: PathBuf }, Local { path: PathBuf },
Git { url: String, path: Option<PathBuf> },
NoPlan, NoPlan,
} }
@ -74,6 +76,24 @@ impl TryFrom<&KdlNode> for ProjectPlan {
}); });
} }
if let Some(git) = children.get_arg("git") {
return Ok(Self::Git {
url: git
.as_string()
.map(|l| l.to_string())
.ok_or(anyhow::anyhow!("a git url is required"))?,
path: children
.get("git")
.and_then(|git| {
git.entries()
.iter()
.filter(|i| i.name().is_some())
.find(|i| i.name().expect("to have a value").to_string() == "path")
})
.and_then(|i| i.value().as_string().map(|p| p.to_string().into())),
});
}
Ok(Self::NoPlan) Ok(Self::NoPlan)
} }
} }

View File

@ -5,6 +5,65 @@ use anyhow::Context;
use crate::model::Project; use crate::model::Project;
pub mod local; pub mod local;
pub mod git {
use std::{
env::temp_dir,
path::{Path, PathBuf},
};
use super::local;
pub async fn reconcile(
url: &str,
path: &Option<PathBuf>,
plan_dir: &Path,
) -> anyhow::Result<()> {
let temp = TempDir::new();
tokio::fs::create_dir_all(&temp.0).await?;
let mut cmd = tokio::process::Command::new("git");
cmd.args(["clone", url, &temp.0.display().to_string(), "--depth=1"]);
tracing::info!("cloning plan: {}", url);
let out = cmd.output().await?;
if !out.status.success() {
let stdout = std::str::from_utf8(&out.stdout).unwrap_or_default();
let stderr = std::str::from_utf8(&out.stderr).unwrap_or_default();
anyhow::bail!("failed to process git plan: {}, {}", stdout, stderr)
}
let temp_plan_dir = if let Some(path) = path {
temp.0.join(path)
} else {
temp.0.to_path_buf()
};
local::reconcile(&temp_plan_dir, plan_dir).await?;
drop(temp);
Ok(())
}
struct TempDir(PathBuf);
impl TempDir {
fn new() -> Self {
Self(
temp_dir()
.join("forest")
.join(uuid::Uuid::new_v4().to_string()),
)
}
}
impl Drop for TempDir {
fn drop(&mut self) {
std::fs::remove_dir_all(&self.0).expect("to be able to remove temp dir");
}
}
}
#[derive(Default)] #[derive(Default)]
pub struct PlanReconciler {} pub struct PlanReconciler {}
@ -43,6 +102,9 @@ impl PlanReconciler {
let source = &destination.join(path); let source = &destination.join(path);
local::reconcile(source, &plan_dir).await?; local::reconcile(source, &plan_dir).await?;
} }
crate::model::ProjectPlan::Git { url, path } => {
git::reconcile(url, path, &plan_dir).await?;
}
crate::model::ProjectPlan::NoPlan => { crate::model::ProjectPlan::NoPlan => {
tracing::debug!("no plan, returning"); tracing::debug!("no plan, returning");
return Ok(None); return Ok(None);

View File

@ -0,0 +1,33 @@
project {
name local
description """
A simple local project that depends on ../plan for its utility scripts
"""
plan {
git "ssh://git@git.front.kjuulh.io/kjuulh/forest" path="examples/plan"
}
global {
someName "name"
someKey {
someNestedKey "somevalue"
some {
key {
val
val
}
}
}
}
templates type=jinja2 {
path "templates/*.jinja2"
output "output/"
}
scripts {
hello type=shell {}
}
}

View File

@ -0,0 +1,5 @@
#!/usr/bin/env zsh
set -e
echo "hello world"

View File

@ -0,0 +1,3 @@
something
val is mapping: {{ global.someKey.some.key.val is mapping }}