#![feature(proc_macro_hygiene, decl_macro)] use anyhow::{anyhow, bail, Result}; use log::{debug, info, trace}; use regex::Regex; use rocket::{fairing::AdHoc, get, post, routes, State}; use rocket_contrib::json::Json; use serde::{Deserialize, Serialize}; use std::{collections::HashMap, fs::File, io::BufReader, net::SocketAddr, process::Command}; #[derive(Debug, Deserialize, Serialize)] struct Config { hooks: HashMap, } #[derive(Debug, Deserialize, Serialize)] struct Hook { action: Option, filters: HashMap, } #[derive(Debug, Deserialize, Serialize)] struct Filter { pointer: String, regex: String, } #[derive(Debug, Deserialize, Serialize)] struct Data(serde_json::Value); #[get("/")] fn index() -> &'static str { "Hello, webhookey!" } fn execute_hook(name: &str, hook: &Hook, data: &serde_json::Value) -> Result<()> { debug!("Running hook `{}`", name); for (filter_name, filter) in hook.filters.iter() { debug!("Matching filter `{}`", filter_name); if let Some(value) = data.pointer(&filter.pointer) { let regex = Regex::new(&filter.regex)?; if let Some(value) = value.as_str() { if !regex.is_match(value) { info!("Filter `{}` in hook `{}` did not match", filter_name, name); return Ok(()); } } else { anyhow!( "Could not parse pointer in hook `{}` from filter `{}`", name, filter_name ); } } } if let Some(action) = &hook.action { info!("Execute `{}` from hook `{}`", action, name); Command::new(action).spawn()?; } Ok(()) } #[post("/", format = "json", data = "")] fn receive_hook(address: SocketAddr, config: State, data: Json) -> Result { info!("POST request received from: {}", address); let data = serde_json::to_value(data.0)?; trace!("Data received from: {}\n{}", address, data); for (hook_name, hook) in config.hooks.iter() { execute_hook(&hook_name, &hook, &data)?; } Ok("Request received.".to_string()) } fn get_config() -> Result { if let Ok(config) = File::open("/etc/webhookey/config.yml") { info!("Loading configuration from `/etc/webhookey/config.yml`"); return Ok(config); } if let Some(mut path) = dirs::config_dir() { path.push("webhookey/config.yml"); if let Ok(config) = File::open(&path) { info!( "Loading configuration from `{}`", path.to_str().unwrap_or("path not printable"), ); return Ok(config); } } if let Ok(config) = File::open("config.yml") { info!("Loading configuration from `./config.yml`"); return Ok(config); } bail!("No configuration files found."); } fn main() -> Result<()> { env_logger::init(); let config = get_config()?; let config: Config = serde_yaml::from_reader(BufReader::new(config))?; trace!("Parsed configuration:\n{}", serde_yaml::to_string(&config)?); rocket::ignite() .mount("/", routes![index, receive_hook]) .attach(AdHoc::on_attach("webhookey config", move |rocket| { Ok(rocket.manage(config)) })) .launch(); Ok(()) }