Add hook execution, logging and config
Signed-off-by: trivernis <trivernis@protonmail.com>main
parent
3a7d9111bc
commit
35c7004de3
@ -1,9 +1,59 @@
|
||||
use crate::error::MultihookResult;
|
||||
use std::path::{Path, PathBuf};
|
||||
use tokio::process::Command;
|
||||
|
||||
pub enum HookAction {}
|
||||
pub enum HookAction {
|
||||
Script(PathBuf),
|
||||
Command(String),
|
||||
}
|
||||
|
||||
impl HookAction {
|
||||
pub async fn execute(&self, body: &str) -> MultihookResult<()> {
|
||||
match self {
|
||||
HookAction::Script(s) => Self::execute_script(s, body).await,
|
||||
HookAction::Command(c) => Self::execute_command(c, body).await,
|
||||
}
|
||||
}
|
||||
|
||||
async fn execute_command(command: &str, body: &str) -> MultihookResult<()> {
|
||||
let output = Command::new("sh")
|
||||
.env("HOOK_BODY", body)
|
||||
.arg("-c")
|
||||
.arg(command)
|
||||
.output()
|
||||
.await?;
|
||||
let stderr = String::from_utf8_lossy(&output.stderr[..]);
|
||||
let stdout = String::from_utf8_lossy(&output.stdout[..]);
|
||||
log::debug!("Command output is: {}", stdout);
|
||||
|
||||
if stderr.len() > 0 {
|
||||
log::error!("Errors occurred during command execution: {}", stderr);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn execute_script(script: &PathBuf, body: &str) -> MultihookResult<()> {
|
||||
let output = Command::new(script).env("HOOK_BODY", body).output().await?;
|
||||
let stderr = String::from_utf8_lossy(&output.stderr[..]);
|
||||
let stdout = String::from_utf8_lossy(&output.stdout[..]);
|
||||
log::debug!("Script output is: {}", stdout);
|
||||
|
||||
if stderr.len() > 0 {
|
||||
log::error!("Errors occurred during script execution: {}", stderr);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<String> for HookAction {
|
||||
fn from(action: String) -> Self {
|
||||
let path = PathBuf::from(&action);
|
||||
if Path::new(&path).exists() {
|
||||
Self::Script(path)
|
||||
} else {
|
||||
Self::Command(action)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,56 @@
|
||||
use std::str::FromStr;
|
||||
|
||||
use chrono::Local;
|
||||
use colored::*;
|
||||
use log::{Level, LevelFilter};
|
||||
|
||||
/// Initializes the env_logger with a custom format
|
||||
/// that also logs the thread names
|
||||
pub fn init_logger() {
|
||||
fern::Dispatch::new()
|
||||
.format(|out, message, record| {
|
||||
let color = get_level_style(record.level());
|
||||
let mut target = record.target().to_string();
|
||||
target.truncate(39);
|
||||
|
||||
out.finish(format_args!(
|
||||
"{:<40}| {} {}: {}",
|
||||
target.dimmed().italic(),
|
||||
Local::now().format("%Y-%m-%dT%H:%M:%S"),
|
||||
record
|
||||
.level()
|
||||
.to_string()
|
||||
.to_lowercase()
|
||||
.as_str()
|
||||
.color(color),
|
||||
message
|
||||
))
|
||||
})
|
||||
.level(
|
||||
std::env::var("RUST_LOG")
|
||||
.ok()
|
||||
.and_then(|level| log::LevelFilter::from_str(&level).ok())
|
||||
.unwrap_or(LevelFilter::Info),
|
||||
)
|
||||
.level_for("tokio", log::LevelFilter::Info)
|
||||
.level_for("tracing", log::LevelFilter::Warn)
|
||||
.level_for("rustls", log::LevelFilter::Warn)
|
||||
.level_for("h2", log::LevelFilter::Warn)
|
||||
.level_for("hyper", log::LevelFilter::Warn)
|
||||
.level_for("tokio_util", log::LevelFilter::Warn)
|
||||
.level_for("want", log::LevelFilter::Warn)
|
||||
.level_for("mio", log::LevelFilter::Warn)
|
||||
.chain(std::io::stdout())
|
||||
.apply()
|
||||
.expect("failed to init logger");
|
||||
}
|
||||
|
||||
fn get_level_style(level: Level) -> colored::Color {
|
||||
match level {
|
||||
Level::Trace => colored::Color::Magenta,
|
||||
Level::Debug => colored::Color::Blue,
|
||||
Level::Info => colored::Color::Green,
|
||||
Level::Warn => colored::Color::Yellow,
|
||||
Level::Error => colored::Color::Red,
|
||||
}
|
||||
}
|
@ -1,7 +1,22 @@
|
||||
mod server;
|
||||
use crate::logging::init_logger;
|
||||
use crate::server::HookServer;
|
||||
use crate::settings::get_settings;
|
||||
|
||||
mod action;
|
||||
mod error;
|
||||
mod logging;
|
||||
mod server;
|
||||
mod settings;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
init_logger();
|
||||
let settings = get_settings();
|
||||
let mut server = HookServer::new();
|
||||
for (name, endpoint) in &settings.endpoints {
|
||||
log::info!("Adding endpoint {} with path {}", name, &endpoint.path);
|
||||
server.add_hook(endpoint.path.clone(), endpoint.action.clone().into())
|
||||
}
|
||||
|
||||
fn main() {
|
||||
println!("Hello, world!");
|
||||
server.start(&settings.server.address).await
|
||||
}
|
||||
|
@ -0,0 +1,80 @@
|
||||
use crate::error::MultihookResult;
|
||||
use config::File;
|
||||
use lazy_static::lazy_static;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
use std::fs;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
pub struct Settings {
|
||||
pub server: ServerSettings,
|
||||
pub endpoints: HashMap<String, EndpointSettings>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
pub struct ServerSettings {
|
||||
pub address: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
pub struct EndpointSettings {
|
||||
pub path: String,
|
||||
pub action: String,
|
||||
}
|
||||
|
||||
impl Default for Settings {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
endpoints: HashMap::new(),
|
||||
server: ServerSettings {
|
||||
address: String::from("127.0.0.1:8080"),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_settings() -> &'static Settings {
|
||||
lazy_static! {
|
||||
static ref SETTINGS: Settings = load_settings().expect("Failed to get settings");
|
||||
}
|
||||
|
||||
&*SETTINGS
|
||||
}
|
||||
|
||||
fn load_settings() -> MultihookResult<Settings> {
|
||||
let config_dir = dirs::config_dir()
|
||||
.unwrap_or(PathBuf::from(".config"))
|
||||
.join("multihook");
|
||||
if !Path::new(&config_dir).exists() {
|
||||
fs::create_dir(&config_dir)?;
|
||||
}
|
||||
write_toml_pretty(
|
||||
&config_dir.clone().join("default-config.toml"),
|
||||
&Settings::default(),
|
||||
)?;
|
||||
|
||||
let mut settings = config::Config::default();
|
||||
settings
|
||||
.merge(
|
||||
glob::glob(&format!("{}/*.toml", config_dir.to_string_lossy()))
|
||||
.unwrap()
|
||||
.map(|path| File::from(path.unwrap()))
|
||||
.collect::<Vec<_>>(),
|
||||
)?
|
||||
.merge(config::Environment::with_prefix("MULTIHOOK"))?;
|
||||
|
||||
let settings: Settings = settings.try_into()?;
|
||||
|
||||
Ok(settings)
|
||||
}
|
||||
|
||||
fn write_toml_pretty<T: Serialize>(path: &PathBuf, value: &T) -> MultihookResult<()> {
|
||||
let mut buf_str = String::new();
|
||||
let mut serializer = toml::Serializer::pretty(&mut buf_str);
|
||||
serializer.pretty_array(true);
|
||||
value.serialize(&mut serializer)?;
|
||||
fs::write(path, buf_str.as_bytes())?;
|
||||
|
||||
Ok(())
|
||||
}
|
Loading…
Reference in New Issue