mirror of
https://git.alemi.dev/tci.git
synced 2024-11-14 04:39:19 +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]
|
[dependencies]
|
||||||
git2 = "0.18.2"
|
git2 = "0.18.2"
|
||||||
|
serde = { version = "1.0.196", features = ["derive"] }
|
||||||
tempdir = "0.3.7"
|
tempdir = "0.3.7"
|
||||||
|
thiserror = "1.0.57"
|
||||||
|
toml = { version = "0.8.10", features = ["parse"] }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
|
|
||||||
|
|
132
src/main.rs
132
src/main.rs
|
@ -1,58 +1,116 @@
|
||||||
use git2::{Config, Repository};
|
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() {
|
fn main() {
|
||||||
let callback = std::path::Path::new(&std::env::args().next().expect("no argv0??"))
|
if let Err(e) = tci() {
|
||||||
.file_name()
|
println!("[!] error running tci: {e}");
|
||||||
.expect("path terminates with '...'")
|
}
|
||||||
.to_str()
|
}
|
||||||
.expect("could not represent OsStr")
|
|
||||||
|
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();
|
.to_string();
|
||||||
|
|
||||||
let repo_path = std::env::current_dir()
|
let cfg_raw = std::fs::read_to_string(config_path)
|
||||||
.expect("not in a directory???");
|
.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
|
let repo_name = repo_path
|
||||||
.parent()
|
.parent().ok_or(TciError::FsError)?
|
||||||
.expect("in root???")
|
.file_name().ok_or(TciError::FsError)?
|
||||||
.file_name()
|
.to_str().ok_or(TciError::FsError)?
|
||||||
.expect("path terminates with '...'")
|
|
||||||
.to_str()
|
|
||||||
.expect("could not represent OsStr")
|
|
||||||
.to_string();
|
.to_string();
|
||||||
|
|
||||||
// check config before even creating temp dir and cloning repo
|
// 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 git_config = Config::open(&repo_path.join("config"))?;
|
||||||
let Ok(true) = cfg.get_bool("tci.allow") else {
|
if !cfg.allow_all && !git_config.get_bool("tci.allow").unwrap_or(false) {
|
||||||
std::process::exit(0);
|
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");
|
Repository::clone(repo_path.to_str().ok_or(TciError::FsError)?, tmp.path())?;
|
||||||
std::env::set_current_dir(tmp.path()).expect("could not change directory");
|
std::env::set_current_dir(tmp.path())?;
|
||||||
|
|
||||||
println!("[+] running tci hook [{callback}] for repo '{repo_name}'");
|
if let Some(setup) = cfg.setup {
|
||||||
let tci_script = cfg.get_str("tci.script").unwrap_or(".tci").to_string();
|
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() {
|
if !tmp.path().join(&tci_script).is_file() {
|
||||||
println!("[?] tci enabled for repo, but no .tci script found");
|
return Err(Box::new(TciError::MissingScript));
|
||||||
std::process::exit(1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let output = match std::process::Command::new(tmp.path().join(&tci_script)).output() {
|
let res = shell(tmp.path().join(&tci_script))?;
|
||||||
Ok(out) => out,
|
println!("{res}");
|
||||||
Err(e) => {
|
|
||||||
println!("[!] error running tci script: {e}");
|
|
||||||
std::process::exit(1);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
match std::str::from_utf8(&output.stdout) {
|
|
||||||
Ok(s) => println!("{s}"),
|
if let Some(cleanup) = cfg.cleanup {
|
||||||
Err(e) => {
|
print!("[-] cleaning up ('{cleanup}')");
|
||||||
println!("[?] script produced non-utf8 output ({e}), showing as bytes");
|
let res = shell(cleanup)?;
|
||||||
println!("{:?}", output.stdout);
|
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