Add hook execution, logging and config

Signed-off-by: trivernis <trivernis@protonmail.com>
main
trivernis 3 years ago
parent 3a7d9111bc
commit 35c7004de3
Signed by: Trivernis
GPG Key ID: DFFFCC2C7A02DB45

264
Cargo.lock generated

@ -2,6 +2,32 @@
# It is not intended for manual editing.
version = 3
[[package]]
name = "aho-corasick"
version = "0.7.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f"
dependencies = [
"memchr",
]
[[package]]
name = "arrayvec"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b"
[[package]]
name = "atty"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
dependencies = [
"hermit-abi",
"libc",
"winapi",
]
[[package]]
name = "autocfg"
version = "1.0.1"
@ -57,6 +83,46 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "chrono"
version = "0.4.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73"
dependencies = [
"libc",
"num-integer",
"num-traits 0.2.14",
"time",
"winapi",
]
[[package]]
name = "colored"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b3616f750b84d8f0de8a58bda93e08e2a81ad3f523089b05f1dffecab48c6cbd"
dependencies = [
"atty",
"lazy_static",
"winapi",
]
[[package]]
name = "config"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b1b9d958c2b1368a663f05538fc1b5975adce1e19f435acceae987aceeeb369"
dependencies = [
"lazy_static",
"nom",
"rust-ini",
"serde 1.0.127",
"serde-hjson",
"serde_json",
"toml",
"yaml-rust",
]
[[package]]
name = "cpufeatures"
version = "0.1.5"
@ -75,6 +141,35 @@ dependencies = [
"generic-array",
]
[[package]]
name = "dirs"
version = "3.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "30baa043103c9d0c2a57cf537cc2f35623889dc0d405e6c3cccfadbc81c71309"
dependencies = [
"dirs-sys",
]
[[package]]
name = "dirs-sys"
version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "03d86534ed367a67548dc68113a0f5db55432fdfbb6e6f9d77704397d95d5780"
dependencies = [
"libc",
"redox_users",
"winapi",
]
[[package]]
name = "fern"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8c9a4820f0ccc8a7afd67c39a0f1a0f4b07ca1725164271a64939d7aeb9af065"
dependencies = [
"log",
]
[[package]]
name = "fnv"
version = "1.0.7"
@ -186,6 +281,12 @@ dependencies = [
"wasi 0.10.2+wasi-snapshot-preview1",
]
[[package]]
name = "glob"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574"
[[package]]
name = "h2"
version = "0.3.3"
@ -345,12 +446,31 @@ version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "lexical-core"
version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6607c62aa161d23d17a9072cc5da0be67cdfc89d3afb1e8d9c842bebc2525ffe"
dependencies = [
"arrayvec",
"bitflags",
"cfg-if",
"ryu",
"static_assertions",
]
[[package]]
name = "libc"
version = "0.2.98"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "320cfe77175da3a483efed4bc0adc1968ca050b098ce4f2f1c13a56626128790"
[[package]]
name = "linked-hash-map"
version = "0.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7fb9b38af92608140b86b693604b9ffcc5824240a484d1ecd4795bacb2fe88f3"
[[package]]
name = "log"
version = "0.4.14"
@ -414,8 +534,18 @@ dependencies = [
name = "multihook"
version = "0.1.0"
dependencies = [
"chrono",
"colored",
"config",
"dirs",
"fern",
"glob",
"lazy_static",
"log",
"serde 1.0.127",
"thiserror",
"tokio",
"toml",
"warp",
]
@ -437,6 +567,17 @@ dependencies = [
"twoway",
]
[[package]]
name = "nom"
version = "5.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ffb4262d26ed83a1c0a33a38fe2bb15797329c85770da05e6b828ddb782627af"
dependencies = [
"lexical-core",
"memchr",
"version_check",
]
[[package]]
name = "ntapi"
version = "0.3.6"
@ -446,6 +587,34 @@ dependencies = [
"winapi",
]
[[package]]
name = "num-integer"
version = "0.1.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db"
dependencies = [
"autocfg",
"num-traits 0.2.14",
]
[[package]]
name = "num-traits"
version = "0.1.43"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "92e5113e9fd4cc14ded8e499429f396a20f98c772a47cc8622a736e1ec843c31"
dependencies = [
"num-traits 0.2.14",
]
[[package]]
name = "num-traits"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290"
dependencies = [
"autocfg",
]
[[package]]
name = "num_cpus"
version = "1.13.0"
@ -626,6 +795,33 @@ dependencies = [
"bitflags",
]
[[package]]
name = "redox_users"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "528532f3d801c87aec9def2add9ca802fe569e44a544afe633765267840abe64"
dependencies = [
"getrandom 0.2.3",
"redox_syscall",
]
[[package]]
name = "regex"
version = "1.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.6.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b"
[[package]]
name = "remove_dir_all"
version = "0.5.3"
@ -635,6 +831,12 @@ dependencies = [
"winapi",
]
[[package]]
name = "rust-ini"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3e52c148ef37f8c375d49d5a73aa70713125b7f19095948a923f80afdeb22ec2"
[[package]]
name = "ryu"
version = "1.0.5"
@ -653,11 +855,43 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ea6a9290e3c9cf0f18145ef7ffa62d68ee0bf5fcd651017e586dc7fd5da448c2"
[[package]]
name = "serde"
version = "0.8.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9dad3f759919b92c3068c696c15c3d17238234498bbdcc80f2c469606f948ac8"
[[package]]
name = "serde"
version = "1.0.127"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f03b9878abf6d14e6779d3f24f07b2cfa90352cfec4acc5aab8f1ac7f146fae8"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde-hjson"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a3a4e0ea8a88553209f6cc6cfe8724ecad22e1acf372793c27d995290fe74f8"
dependencies = [
"lazy_static",
"num-traits 0.1.43",
"regex",
"serde 0.8.23",
]
[[package]]
name = "serde_derive"
version = "1.0.127"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a024926d3432516606328597e0f224a51355a493b49fdd67e9209187cbe55ecc"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "serde_json"
@ -667,7 +901,7 @@ checksum = "336b10da19a12ad094b59d870ebde26a45402e5b470add4b5fd03c5048a32127"
dependencies = [
"itoa",
"ryu",
"serde",
"serde 1.0.127",
]
[[package]]
@ -679,7 +913,7 @@ dependencies = [
"form_urlencoded",
"itoa",
"ryu",
"serde",
"serde 1.0.127",
]
[[package]]
@ -720,6 +954,12 @@ dependencies = [
"winapi",
]
[[package]]
name = "static_assertions"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
[[package]]
name = "syn"
version = "1.0.74"
@ -858,6 +1098,15 @@ dependencies = [
"tokio",
]
[[package]]
name = "toml"
version = "0.5.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa"
dependencies = [
"serde 1.0.127",
]
[[package]]
name = "tower-service"
version = "0.3.1"
@ -1010,7 +1259,7 @@ dependencies = [
"percent-encoding",
"pin-project",
"scoped-tls",
"serde",
"serde 1.0.127",
"serde_json",
"serde_urlencoded",
"tokio",
@ -1054,3 +1303,12 @@ name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "yaml-rust"
version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85"
dependencies = [
"linked-hash-map",
]

@ -8,6 +8,19 @@ edition = "2018"
[dependencies]
warp = "0.3.1"
thiserror = "1.0.26"
config = "0.11.0"
lazy_static = "1.4.0"
dirs = "3.0.2"
toml = "0.5.8"
glob = "0.3.0"
log = "0.4.14"
colored = "2.0.0"
chrono = "0.4.19"
fern = "0.6.0"
[dependencies.serde]
version = "1.0.127"
features = ["derive"]
[dependencies.tokio]
version = "1.9.0"

@ -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)
}
}
}

@ -12,8 +12,17 @@ pub enum MultihookError {
#[error("Failed to parse body as utf8 string {0}")]
UTF8Error(#[from] FromUtf8Error),
#[error("Unknown endpoint")]
UnknownEndpoint,
#[error(transparent)]
TomlSerializeError(#[from] toml::ser::Error),
#[error(transparent)]
TomlDeserializeError(#[from] toml::de::Error),
#[error(transparent)]
IoError(#[from] std::io::Error),
#[error(transparent)]
ConfigError(#[from] config::ConfigError),
}
impl Reject for MultihookError {}

@ -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
}

@ -1,7 +1,9 @@
use crate::action::HookAction;
use crate::error::{MultihookError, MultihookResult};
use crate::error::MultihookError;
use std::collections::HashMap;
use std::net::SocketAddr;
use std::sync::Arc;
use warp::http::Response;
use warp::hyper::body::Bytes;
use warp::{Filter, Rejection};
@ -10,6 +12,12 @@ pub struct HookServer {
}
impl HookServer {
pub fn new() -> Self {
Self {
endpoints: Default::default(),
}
}
pub fn add_hook(&mut self, point: String, action: HookAction) {
self.endpoints.insert(point, action);
}
@ -18,13 +26,24 @@ impl HookServer {
body: Bytes,
point: String,
action: Arc<HookAction>,
) -> Result<impl warp::Reply, Rejection> {
) -> Result<Response<String>, Rejection> {
let body = String::from_utf8(body.to_vec()).map_err(MultihookError::from)?;
action.execute(&body).await?;
Ok(format!("Hook {} executed", point))
log::info!("Hook '{}' executed", point);
Ok(Response::builder()
.body(format!("Hook '{}' executed", point))
.unwrap())
}
async fn not_found_response() -> Result<Response<String>, Rejection> {
log::info!("Endpoint not found");
Ok(Response::builder()
.status(404)
.body(String::from("Endpoint not found"))
.unwrap())
}
pub fn start(self) {
pub async fn start(self, address: &str) {
let routes = self
.endpoints
.into_iter()
@ -35,15 +54,25 @@ impl HookServer {
.and(warp::body::bytes())
.and_then(move |b| {
let action = Arc::clone(&action);
Self::execute_action(b, point.clone(), action)
let point = point.clone();
async move { Self::execute_action(b, point, action).await }
})
.map(|_| warp::reply())
.boxed()
})
.fold(warp::any().map(warp::reply).boxed(), |routes, route| {
routes.or(route).unify().boxed()
});
.fold(
warp::get()
.and_then(|| async { Self::not_found_response().await })
.boxed(),
|routes, route| routes.or(route).unify().boxed(),
);
let routes = warp::serve(routes);
log::info!("Starting server on {}", address);
warp::serve(routes)
.bind(
address
.parse::<SocketAddr>()
.expect("Invalid address in settings"),
)
.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…
Cancel
Save