From a15a713a0c0d9bf34f4038d3999c1f8d21d3c7f9 Mon Sep 17 00:00:00 2001 From: trivernis Date: Sat, 7 Aug 2021 14:57:14 +0200 Subject: [PATCH] Add jsonpath placeholder support Signed-off-by: trivernis --- Cargo.lock | 163 +++++++++++++++++++++++++++++++++++++--- Cargo.toml | 3 + src/action.rs | 42 ++++------- src/command_template.rs | 72 ++++++++++++++++++ src/main.rs | 8 ++ src/settings.rs | 4 +- 6 files changed, 252 insertions(+), 40 deletions(-) create mode 100644 src/command_template.rs diff --git a/Cargo.lock b/Cargo.lock index 52fc786..687c6aa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,21 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "addr2line" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e61f2b7f93d2c7d2b08263acaa4a363b3e276806c68af6134c44f523bf1aacd" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + [[package]] name = "aho-corasick" version = "0.7.18" @@ -34,6 +49,21 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" +[[package]] +name = "backtrace" +version = "0.3.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7a905d892734eea339e896738c14b9afce22b5318f64b951e70bf3844419b01" +dependencies = [ + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + [[package]] name = "base64" version = "0.13.0" @@ -77,6 +107,12 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b700ce4376041dcd0a327fd0097c41095743c4c8af8887265942faf1100bd040" +[[package]] +name = "cc" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e70cc2f62c6ce1868963827bd677764c62d07c3d9a3e1fb1177ee1a9ab199eb2" + [[package]] name = "cfg-if" version = "1.0.0" @@ -161,6 +197,15 @@ dependencies = [ "winapi", ] +[[package]] +name = "error-chain" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff511d5dc435d703f4971bc399647c9bc38e20cb41452e3b9feb4765419ed3f3" +dependencies = [ + "backtrace", +] + [[package]] name = "fern" version = "0.6.0" @@ -281,6 +326,12 @@ dependencies = [ "wasi 0.10.2+wasi-snapshot-preview1", ] +[[package]] +name = "gimli" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0a01e0497841a3b2db4f8afa483cce65f7e96a3498bd6c541734792aeac8fe7" + [[package]] name = "glob" version = "0.3.0" @@ -440,6 +491,19 @@ version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736" +[[package]] +name = "jsonpath" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8061db09019f1879ba586685694fe18279f597b1b3a9dd308f35e596be6cdf7d" +dependencies = [ + "error-chain", + "pest", + "pest_derive", + "serde 1.0.127", + "serde_json", +] + [[package]] name = "lazy_static" version = "1.4.0" @@ -508,6 +572,16 @@ dependencies = [ "unicase", ] +[[package]] +name = "miniz_oxide" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a92518e98c078586bc6c934028adcca4c92a53d6a958196de835170a01d84e4b" +dependencies = [ + "adler", + "autocfg", +] + [[package]] name = "mio" version = "0.7.13" @@ -540,9 +614,12 @@ dependencies = [ "dirs", "fern", "glob", + "jsonpath", "lazy_static", "log", + "regex", "serde 1.0.127", + "serde_json", "thiserror", "tokio", "toml", @@ -625,6 +702,15 @@ dependencies = [ "libc", ] +[[package]] +name = "object" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c55827317fb4c08822499848a14237d2874d6f139828893017237e7ab93eb386" +dependencies = [ + "memchr", +] + [[package]] name = "once_cell" version = "1.8.0" @@ -643,6 +729,23 @@ version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" +[[package]] +name = "pest" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fce5d8b5cc33983fc74f78ad552b5522ab41442c4ca91606e4236eb4b5ceefc" + +[[package]] +name = "pest_derive" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3294f437119209b084c797604295f40227cffa35c57220b1e99a6ff3bf8ee4" +dependencies = [ + "pest", + "quote 0.3.15", + "syn 0.11.11", +] + [[package]] name = "pin-project" version = "1.0.8" @@ -659,8 +762,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e8fe8163d14ce7f0cdac2e040116f22eac817edabff0be91e8aff7e9accf389" dependencies = [ "proc-macro2", - "quote", - "syn", + "quote 1.0.9", + "syn 1.0.74", ] [[package]] @@ -687,7 +790,7 @@ version = "1.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c7ed8b8c7b886ea3ed7dde405212185f423ab44682667c8c6dd14aa1d9f6612" dependencies = [ - "unicode-xid", + "unicode-xid 0.2.2", ] [[package]] @@ -696,6 +799,12 @@ version = "1.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" +[[package]] +name = "quote" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a6e920b65c65f10b2ae65c831a81a073a89edd28c7cce89475bff467ab4167a" + [[package]] name = "quote" version = "1.0.9" @@ -837,6 +946,12 @@ version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3e52c148ef37f8c375d49d5a73aa70713125b7f19095948a923f80afdeb22ec2" +[[package]] +name = "rustc-demangle" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dead70b0b5e03e9c814bcb6b01e03e68f7c57a80aa48c72ec92152ab3e818d49" + [[package]] name = "ryu" version = "1.0.5" @@ -889,8 +1004,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a024926d3432516606328597e0f224a51355a493b49fdd67e9209187cbe55ecc" dependencies = [ "proc-macro2", - "quote", - "syn", + "quote 1.0.9", + "syn 1.0.74", ] [[package]] @@ -960,6 +1075,17 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" +[[package]] +name = "syn" +version = "0.11.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3b891b9015c88c576343b9b3e41c2c11a51c219ef067b264bd9c8aa9b441dad" +dependencies = [ + "quote 0.3.15", + "synom", + "unicode-xid 0.0.4", +] + [[package]] name = "syn" version = "1.0.74" @@ -967,8 +1093,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1873d832550d4588c3dbc20f01361ab00bfe741048f71e3fecf145a7cc18b29c" dependencies = [ "proc-macro2", - "quote", - "unicode-xid", + "quote 1.0.9", + "unicode-xid 0.2.2", +] + +[[package]] +name = "synom" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a393066ed9010ebaed60b9eafa373d4b1baac186dd7e008555b0f702b51945b6" +dependencies = [ + "unicode-xid 0.0.4", ] [[package]] @@ -1001,8 +1136,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "060d69a0afe7796bf42e9e2ff91f5ee691fb15c53d38b4b62a9a53eb23164745" dependencies = [ "proc-macro2", - "quote", - "syn", + "quote 1.0.9", + "syn 1.0.74", ] [[package]] @@ -1056,8 +1191,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "54473be61f4ebe4efd09cec9bd5d16fa51d70ea0192213d754d2d500457db110" dependencies = [ "proc-macro2", - "quote", - "syn", + "quote 1.0.9", + "syn 1.0.74", ] [[package]] @@ -1201,6 +1336,12 @@ dependencies = [ "tinyvec", ] +[[package]] +name = "unicode-xid" +version = "0.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c1f860d7d29cf02cb2f3f359fd35991af3d30bac52c57d265a3c461074cb4dc" + [[package]] name = "unicode-xid" version = "0.2.2" diff --git a/Cargo.toml b/Cargo.toml index 919706b..a8653b6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,6 +17,9 @@ log = "0.4.14" colored = "2.0.0" chrono = "0.4.19" fern = "0.6.0" +serde_json = "1.0.66" +jsonpath = "0.1.1" +regex = "1.5.4" [dependencies.serde] version = "1.0.127" diff --git a/src/action.rs b/src/action.rs index af5935c..cbb72c8 100644 --- a/src/action.rs +++ b/src/action.rs @@ -1,21 +1,25 @@ +use crate::command_template::CommandTemplate; use crate::error::MultihookResult; -use std::path::{Path, PathBuf}; +use serde_json::Value; +use std::fs::read_to_string; +use std::path::PathBuf; use tokio::process::Command; -pub enum HookAction { - Script(PathBuf), - Command(String), +pub struct HookAction { + command: CommandTemplate, } 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, + pub fn new(command: S) -> Self { + Self { + command: CommandTemplate::new(command), } } - async fn execute_command(command: &str, body: &str) -> MultihookResult<()> { + pub async fn execute(&self, body: &str) -> MultihookResult<()> { + let json_body: Value = serde_json::from_str(body).unwrap_or_default(); + let command = self.command.evaluate(&json_body); + let output = Command::new("sh") .env("HOOK_BODY", body) .arg("-c") @@ -32,28 +36,12 @@ impl HookAction { 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 for HookAction { fn from(action: String) -> Self { let path = PathBuf::from(&action); - if Path::new(&path).exists() { - Self::Script(path) - } else { - Self::Command(action) - } + let contents = read_to_string(path).unwrap_or(action); + Self::new(contents) } } diff --git a/src/command_template.rs b/src/command_template.rs new file mode 100644 index 0000000..30f056c --- /dev/null +++ b/src/command_template.rs @@ -0,0 +1,72 @@ +use jsonpath::Selector; +use lazy_static::lazy_static; +use regex::{Match, Regex}; +use serde_json::Value; + +pub struct CommandTemplate { + src: String, + matches: Vec<(usize, usize)>, +} + +impl CommandTemplate { + pub fn new(command: S) -> Self { + lazy_static! { + static ref PLACEHOLDER_REGEX: Regex = Regex::new(r"\{\{(.*?)\}\}").unwrap(); + } + let command = command.to_string(); + let matches = PLACEHOLDER_REGEX + .find_iter(&command) + .map(|m: Match| (m.start(), m.end())) + .collect(); + Self { + src: command, + matches, + } + } + + pub fn evaluate(&self, json: &Value) -> String { + let mut result_string = String::with_capacity(self.src.len()); + let mut last_index = 0; + + for (start, end) in &self.matches { + let before = &self.src[last_index..*start]; + let query = &self.src[*start + 2..*end - 2]; + result_string.push_str(before); + result_string.push_str(&evaluate_path(query, json).unwrap_or_default()); + + last_index = *end; + } + result_string.push_str(&self.src[last_index..]); + + result_string + } +} + +fn evaluate_path(query: &str, json: &Value) -> Option { + let selector = Selector::new(query).ok()?; + let results = selector + .find(json) + .map(json_to_string) + .collect::>(); + + Some(results.join("\n")) +} + +fn json_to_string(value: &Value) -> String { + match value { + Value::Null => String::with_capacity(0), + Value::Bool(b) => b.to_string(), + Value::Number(n) => n.to_string(), + Value::String(s) => s.to_owned(), + Value::Array(a) => a + .iter() + .map(|v| json_to_string(v)) + .collect::>() + .join("\n"), + Value::Object(o) => o + .iter() + .map(|(k, v)| format!("{} = {}", k, json_to_string(v))) + .collect::>() + .join("\n"), + } +} diff --git a/src/main.rs b/src/main.rs index 76f4638..01b8878 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,8 +1,10 @@ use crate::logging::init_logger; use crate::server::HookServer; use crate::settings::get_settings; +use std::path::{Path, PathBuf}; mod action; +mod command_template; mod error; mod logging; mod server; @@ -11,6 +13,12 @@ mod settings; #[tokio::main] async fn main() { init_logger(); + let data_dir = dirs::data_dir() + .map(|d| d.join("multihook")) + .unwrap_or(PathBuf::from(".")); + if !Path::new(&data_dir).exists() { + std::fs::create_dir(data_dir).expect("Failed to create data dir"); + } let settings = get_settings(); let mut server = HookServer::new(); for (name, endpoint) in &settings.endpoints { diff --git a/src/settings.rs b/src/settings.rs index 1adb01a..a258938 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -44,8 +44,8 @@ pub fn get_settings() -> &'static Settings { fn load_settings() -> MultihookResult { let config_dir = dirs::config_dir() - .unwrap_or(PathBuf::from(".config")) - .join("multihook"); + .map(|c| c.join("multihook")) + .unwrap_or(PathBuf::from(".config")); if !Path::new(&config_dir).exists() { fs::create_dir(&config_dir)?; }