mirror of
https://git.alemi.dev/tci.git
synced 2024-11-23 08:34:51 +01:00
feat: refactor, accept multiple setup/cleanup ...
run setup and cleanup inside git directory but tci script in tempdir, better handle errors, no longer receive config from argv, also very much refactor
This commit is contained in:
parent
57683aab78
commit
4a1fef20fd
1 changed files with 88 additions and 70 deletions
158
src/main.rs
158
src/main.rs
|
@ -1,15 +1,20 @@
|
|||
use std::path::PathBuf;
|
||||
|
||||
use git2::{Config, Repository};
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
enum TciError {
|
||||
#[error("could not understand file system structure, bailing out")]
|
||||
FsError,
|
||||
FsError(&'static str),
|
||||
|
||||
#[error("missing tci script")]
|
||||
MissingScript,
|
||||
|
||||
#[error("there is no argv0 (wtf??), can't figure out which hook invoked tci!")]
|
||||
MissingArgv0,
|
||||
#[error("unexpected i/o error")]
|
||||
IoError(#[from] std::io::Error),
|
||||
|
||||
#[error("git error")]
|
||||
GitError(#[from] git2::Error),
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, serde::Deserialize)]
|
||||
|
@ -18,24 +23,18 @@ struct TciConfig {
|
|||
allow_all: bool,
|
||||
|
||||
#[serde(default)]
|
||||
setup: Option<String>,
|
||||
setup: Vec<String>,
|
||||
|
||||
#[serde(default)]
|
||||
cleanup: Option<String>,
|
||||
cleanup: Vec<String>,
|
||||
}
|
||||
|
||||
fn main() {
|
||||
if let Err(e) = tci() {
|
||||
println!("[!] error running tci: {e}");
|
||||
}
|
||||
}
|
||||
|
||||
fn tci() -> Result<(), Box<dyn std::error::Error>> {
|
||||
// load tci config
|
||||
let config_path = std::env::args().nth(1)
|
||||
.as_deref()
|
||||
.unwrap_or("/etc/tci/config.toml")
|
||||
.to_string();
|
||||
// TODO how could we receive this?
|
||||
// arguments are used by git
|
||||
// idk how we could reliably set env vars???
|
||||
let config_path = "/etc/tci/config.toml";
|
||||
|
||||
let cfg_raw = std::fs::read_to_string(config_path)
|
||||
.unwrap_or_default();
|
||||
|
@ -46,61 +45,9 @@ fn tci() -> Result<(), Box<dyn std::error::Error>> {
|
|||
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().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 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
|
||||
if let Err(e) = tci(cfg) {
|
||||
println!("[!] error running tci: {e}");
|
||||
}
|
||||
|
||||
let tmp = tempdir::TempDir::new(&format!("tci-{repo_name}"))?;
|
||||
|
||||
Repository::clone(repo_path.to_str().ok_or(TciError::FsError)?, tmp.path())?;
|
||||
std::env::set_current_dir(tmp.path())?;
|
||||
|
||||
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() {
|
||||
return Err(Box::new(TciError::MissingScript));
|
||||
}
|
||||
|
||||
let res = shell(tmp.path().join(&tci_script))?;
|
||||
println!("{res}");
|
||||
|
||||
|
||||
if let Some(cleanup) = cfg.cleanup {
|
||||
print!("[-] cleaning up ('{cleanup}')");
|
||||
let res = shell(cleanup)?;
|
||||
if !res.is_empty() {
|
||||
print!(": {res}");
|
||||
}
|
||||
println!();
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn shell<S : AsRef<std::ffi::OsStr>>(cmd: S) -> std::io::Result<String> {
|
||||
|
@ -109,8 +56,79 @@ fn shell<S : AsRef<std::ffi::OsStr>>(cmd: S) -> std::io::Result<String> {
|
|||
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");
|
||||
println!("[?] shell produced non-utf8 output ({e})");
|
||||
Ok(format!("{:?}", output.stdout))
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn tci(cfg: TciConfig) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let repo_path = std::env::current_dir()?;
|
||||
|
||||
// check config before even creating temp dir and cloning repo
|
||||
let git_config = Config::open(&repo_path.join("config"))?;
|
||||
let tci_script = git_config.get_str("tci.script").unwrap_or(".tci").to_string();
|
||||
|
||||
// check if CI is allowed
|
||||
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
|
||||
}
|
||||
|
||||
// run setup hooks, if provided
|
||||
for setup in cfg.setup {
|
||||
print!("[+] setting up ('{setup}')");
|
||||
let res = shell(setup)?;
|
||||
if !res.is_empty() {
|
||||
print!(": {res}");
|
||||
}
|
||||
println!();
|
||||
}
|
||||
|
||||
let res = tci_hook(&repo_path, &tci_script);
|
||||
|
||||
for cleanup in cfg.cleanup {
|
||||
print!("[-] cleaning up ('{cleanup}')");
|
||||
let res = shell(cleanup)?;
|
||||
if !res.is_empty() {
|
||||
print!(": {res}");
|
||||
}
|
||||
println!();
|
||||
}
|
||||
|
||||
res?; // only check error AFTER running cleanup hooks
|
||||
|
||||
println!("[o] script run successfully");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn tci_hook(repo_path: &PathBuf, tci_script: &str) -> Result<(), TciError> {
|
||||
let repo_name = repo_path
|
||||
.parent().ok_or(TciError::FsError("missing .git parent"))?
|
||||
.file_name().ok_or(TciError::FsError("no file name for .git parent"))?
|
||||
.to_str().ok_or(TciError::FsError(".git parent file name is not a valid string"))?
|
||||
.to_string();
|
||||
|
||||
let tmp = tempdir::TempDir::new(&format!("tci-{repo_name}"))?;
|
||||
|
||||
// TODO allow customizing clone? just clone recursive? just let hook setup submodules?
|
||||
Repository::clone(
|
||||
repo_path.to_str()
|
||||
.ok_or(TciError::FsError("repo path is not a valid string"))?,
|
||||
tmp.path(),
|
||||
)?;
|
||||
|
||||
if !tmp.path().join(tci_script).is_file() {
|
||||
return Err(TciError::MissingScript);
|
||||
}
|
||||
|
||||
std::env::set_current_dir(tmp.path())?;
|
||||
|
||||
println!("[o] running tci script for repo '{repo_name}'");
|
||||
let res = shell(tmp.path().join(tci_script))?;
|
||||
println!("{res}");
|
||||
|
||||
std::env::set_current_dir(repo_path)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue