mirror of
https://git.alemi.dev/tci.git
synced 2024-12-25 04:34:52 +01:00
feat: parse config file, run setup and cleanup
This commit is contained in:
parent
60e7edc765
commit
183e659839
2 changed files with 98 additions and 37 deletions
|
@ -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]
|
||||
|
||||
|
|
132
src/main.rs
132
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<String>,
|
||||
|
||||
#[serde(default)]
|
||||
cleanup: Option<String>,
|
||||
}
|
||||
|
||||
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<dyn std::error::Error>> {
|
||||
// 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<S : AsRef<std::ffi::OsStr>>(cmd: S) -> std::io::Result<String> {
|
||||
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))
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue