143 lines
3.9 KiB
Rust
143 lines
3.9 KiB
Rust
use clap::Parser;
|
|
use config::AppConfig;
|
|
use db::cache::AppCache;
|
|
use db::database::AppDatabase;
|
|
use git::hook::GitServiceHooks;
|
|
use slog::{Drain, OwnedKVList, Record};
|
|
use tokio::signal;
|
|
use tokio_util::sync::CancellationToken;
|
|
|
|
mod args;
|
|
|
|
use args::HookArgs;
|
|
|
|
#[tokio::main]
|
|
async fn main() -> anyhow::Result<()> {
|
|
// 1. Load configuration
|
|
let cfg = AppConfig::load();
|
|
|
|
// 2. Init slog logging
|
|
let log_level = cfg.log_level().unwrap_or_else(|_| "info".to_string());
|
|
let log = build_slog_logger(&log_level);
|
|
|
|
// 3. Connect to database
|
|
let db = AppDatabase::init(&cfg).await?;
|
|
slog::info!(log, "database connected");
|
|
|
|
// 4. Connect to Redis cache (also provides the cluster pool for hook queue)
|
|
let cache = AppCache::init(&cfg).await?;
|
|
slog::info!(log, "cache connected");
|
|
|
|
// 5. Parse CLI args
|
|
let args = HookArgs::parse();
|
|
|
|
slog::info!(log, "git-hook worker starting";
|
|
"worker_id" => %args.worker_id.unwrap_or_else(|| "default".to_string())
|
|
);
|
|
|
|
// 5. Build HTTP client for webhook delivery
|
|
let http = reqwest::Client::builder()
|
|
.user_agent("Code-Git-Hook/1.0")
|
|
.build()
|
|
.unwrap_or_else(|_| reqwest::Client::new());
|
|
|
|
// 6. Build and run git hook service
|
|
let hooks = GitServiceHooks::new(
|
|
db,
|
|
cache.clone(),
|
|
cache.redis_pool().clone(),
|
|
log.clone(),
|
|
cfg,
|
|
std::sync::Arc::new(http),
|
|
);
|
|
|
|
let cancel = CancellationToken::new();
|
|
let cancel_clone = cancel.clone();
|
|
|
|
// Spawn signal handler
|
|
let log_clone = log.clone();
|
|
tokio::spawn(async move {
|
|
let ctrl_c = async {
|
|
signal::ctrl_c()
|
|
.await
|
|
.expect("failed to install CTRL+C handler");
|
|
};
|
|
|
|
#[cfg(unix)]
|
|
let term = async {
|
|
use tokio::signal::unix::{SignalKind, signal};
|
|
let mut sig =
|
|
signal(SignalKind::terminate()).expect("failed to install SIGTERM handler");
|
|
sig.recv().await;
|
|
};
|
|
|
|
#[cfg(not(unix))]
|
|
let term = std::future::pending::<()>();
|
|
|
|
tokio::select! {
|
|
_ = ctrl_c => {
|
|
slog::info!(log_clone, "received SIGINT, initiating shutdown");
|
|
}
|
|
_ = term => {
|
|
slog::info!(log_clone, "received SIGTERM, initiating shutdown");
|
|
}
|
|
}
|
|
cancel_clone.cancel();
|
|
});
|
|
|
|
hooks.run(cancel).await?;
|
|
|
|
slog::info!(log, "git-hook worker stopped");
|
|
Ok(())
|
|
}
|
|
|
|
fn build_slog_logger(level: &str) -> slog::Logger {
|
|
let level_filter = match level {
|
|
"trace" => 0usize,
|
|
"debug" => 1usize,
|
|
"info" => 2usize,
|
|
"warn" => 3usize,
|
|
"error" => 4usize,
|
|
_ => 2usize,
|
|
};
|
|
|
|
struct StderrDrain(usize);
|
|
|
|
impl Drain for StderrDrain {
|
|
type Ok = ();
|
|
type Err = ();
|
|
#[inline]
|
|
fn log(&self, record: &Record, _logger: &OwnedKVList) -> Result<(), ()> {
|
|
let slog_level = match record.level() {
|
|
slog::Level::Trace => 0,
|
|
slog::Level::Debug => 1,
|
|
slog::Level::Info => 2,
|
|
slog::Level::Warning => 3,
|
|
slog::Level::Error => 4,
|
|
slog::Level::Critical => 5,
|
|
};
|
|
if slog_level < self.0 {
|
|
return Ok(());
|
|
}
|
|
let _ = eprintln!(
|
|
"{} [{}] {}:{} - {}",
|
|
chrono::Utc::now().format("%Y-%m-%dT%H:%M:%S%.3fZ"),
|
|
record.level().to_string(),
|
|
record
|
|
.file()
|
|
.rsplit_once('/')
|
|
.map(|(_, s)| s)
|
|
.unwrap_or(record.file()),
|
|
record.line(),
|
|
record.msg(),
|
|
);
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
let drain = StderrDrain(level_filter);
|
|
let drain = std::sync::Mutex::new(drain);
|
|
let drain = slog::Fuse::new(drain);
|
|
slog::Logger::root(drain, slog::o!())
|
|
}
|