From 183e6598390f8ff366d92026a703b9d533b914e5 Mon Sep 17 00:00:00 2001 From: alemi Date: Tue, 13 Feb 2024 19:42:03 +0100 Subject: [PATCH] feat: parse config file, run setup and cleanup --- Cargo.toml | 3 ++ src/main.rs | 132 +++++++++++++++++++++++++++++++++++++--------------- 2 files changed, 98 insertions(+), 37 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 992a862..bdd7578 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,7 +12,10 @@ repository = "https://git.alemi.dev/tci.git" [dependencies] git2 = "0.18.2" +serde = { version = "1.0.196", features = ["derive"] } tempdir = "0.3.7" +thiserror = "1.0.57" +toml = { version = "0.8.10", features = ["parse"] } [dev-dependencies] diff --git a/src/main.rs b/src/main.rs index 74fbd9a..39c6f06 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,58 +1,116 @@ use git2::{Config, Repository}; +#[derive(Debug, thiserror::Error)] +enum TciError { + #[error("could not understand file system structure, bailing out")] + FsError, + + #[error("missing tci script")] + MissingScript, + + #[error("there is no argv0 (wtf??), can't figure out which hook invoked tci!")] + MissingArgv0, +} + +#[derive(Debug, Default, serde::Deserialize)] +struct TciConfig { + #[serde(default)] + allow_all: bool, + + #[serde(default)] + setup: Option, + + #[serde(default)] + cleanup: Option, +} + fn main() { - let callback = std::path::Path::new(&std::env::args().next().expect("no argv0??")) - .file_name() - .expect("path terminates with '...'") - .to_str() - .expect("could not represent OsStr") + if let Err(e) = tci() { + println!("[!] error running tci: {e}"); + } +} + +fn tci() -> Result<(), Box> { + // load tci config + let config_path = std::env::args().nth(1) + .as_deref() + .unwrap_or("/etc/tci/tci.toml") .to_string(); - let repo_path = std::env::current_dir() - .expect("not in a directory???"); + let cfg_raw = std::fs::read_to_string(config_path) + .unwrap_or_default(); + + let cfg = toml::from_str(&cfg_raw) + .unwrap_or_else(|e| { + println!("[!] invalid config: {e}"); + TciConfig::default() + }); + + // find out which hook is invoking tci + let callback = std::path::Path::new(&std::env::args().next().ok_or(TciError::MissingArgv0)?) + .file_name().ok_or(TciError::FsError)? + .to_str().ok_or(TciError::FsError)? + .to_string(); + + let repo_path = std::env::current_dir()?; let repo_name = repo_path - .parent() - .expect("in root???") - .file_name() - .expect("path terminates with '...'") - .to_str() - .expect("could not represent OsStr") + .parent().ok_or(TciError::FsError)? + .file_name().ok_or(TciError::FsError)? + .to_str().ok_or(TciError::FsError)? .to_string(); // check config before even creating temp dir and cloning repo - let cfg = Config::open(&repo_path.join("config")).expect("could not read git config"); - let Ok(true) = cfg.get_bool("tci.allow") else { - std::process::exit(0); - }; + let git_config = Config::open(&repo_path.join("config"))?; + if !cfg.allow_all && !git_config.get_bool("tci.allow").unwrap_or(false) { + return Ok(()); // we are in whitelist mode and this repo is not whitelisted + } - let tmp = tempdir::TempDir::new(&format!("tci-{repo_name}")).expect("could not create tempdir"); + let tmp = tempdir::TempDir::new(&format!("tci-{repo_name}"))?; - let repo = Repository::clone(repo_path.to_str().unwrap(), tmp.path()).expect("could not clone repository"); - std::env::set_current_dir(tmp.path()).expect("could not change directory"); + Repository::clone(repo_path.to_str().ok_or(TciError::FsError)?, tmp.path())?; + std::env::set_current_dir(tmp.path())?; - println!("[+] running tci hook [{callback}] for repo '{repo_name}'"); - let tci_script = cfg.get_str("tci.script").unwrap_or(".tci").to_string(); + if let Some(setup) = cfg.setup { + print!("[+] setting up ('{setup}')"); + let res = shell(setup)?; + if !res.is_empty() { + print!(": {res}"); + } + println!(); + } + + println!("[o] running tci hook [{callback}] for repo '{repo_name}'"); + let tci_script = git_config.get_str("tci.script").unwrap_or(".tci").to_string(); if !tmp.path().join(&tci_script).is_file() { - println!("[?] tci enabled for repo, but no .tci script found"); - std::process::exit(1); + return Err(Box::new(TciError::MissingScript)); } - let output = match std::process::Command::new(tmp.path().join(&tci_script)).output() { - Ok(out) => out, - Err(e) => { - println!("[!] error running tci script: {e}"); - std::process::exit(1); - } - }; + let res = shell(tmp.path().join(&tci_script))?; + println!("{res}"); - match std::str::from_utf8(&output.stdout) { - Ok(s) => println!("{s}"), - Err(e) => { - println!("[?] script produced non-utf8 output ({e}), showing as bytes"); - println!("{:?}", output.stdout); - }, + + if let Some(cleanup) = cfg.cleanup { + print!("[-] cleaning up ('{cleanup}')"); + let res = shell(cleanup)?; + if !res.is_empty() { + print!(": {res}"); + } + println!(); } + Ok(()) +} + +fn shell>(cmd: S) -> std::io::Result { + let output = std::process::Command::new(cmd).output()?; + + match std::str::from_utf8(&output.stdout) { + Ok(s) => Ok(s.to_string()), + Err(e) => { + println!("[?] script produced non-utf8 output ({e}), showing as bytes"); + Ok(format!("{:?}", output.stdout)) + }, + } }