From 3977c172db9d1543b9ec0d163765868c6072fe72 Mon Sep 17 00:00:00 2001 From: Michael Date: Thu, 16 Aug 2018 15:05:58 +0000 Subject: [PATCH 01/12] use a service function --- examples/github.rs | 3 +-- src/hook.rs | 3 +-- src/lib.rs | 65 ++++++---------------------------------------- src/service.rs | 60 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 70 insertions(+), 61 deletions(-) create mode 100644 src/service.rs diff --git a/examples/github.rs b/examples/github.rs index 21c10e9..d0c1119 100644 --- a/examples/github.rs +++ b/examples/github.rs @@ -54,8 +54,7 @@ fn main() -> Result<(), Error> { let secret = GenericArray::from_iter(config.secret.bytes()); let mut mac = Hmac::::new(&secret); mac.input(payload.body.as_bytes()); - let signature = mac - .result() + let signature = mac.result() .code() .into_iter() .map(|b| format!("{:02x}", b)) diff --git a/src/hook.rs b/src/hook.rs index 0844f68..f1676a8 100644 --- a/src/hook.rs +++ b/src/hook.rs @@ -31,8 +31,7 @@ impl Hook { where P: AsRef, { - let filename = path - .as_ref() + let filename = path.as_ref() .file_name() .ok_or(err_msg("what the fuck bro"))? .to_str() diff --git a/src/lib.rs b/src/lib.rs index 20d29bd..f080ece 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,8 +2,8 @@ #[macro_use] extern crate failure; -extern crate hyper; extern crate futures; +extern crate hyper; extern crate serde; #[macro_use] extern crate serde_json; @@ -17,6 +17,7 @@ extern crate walkdir; pub mod config; pub mod handler; pub mod hook; +pub mod service; use std::collections::HashMap; use std::net::SocketAddrV4; @@ -28,8 +29,8 @@ use std::time::Duration; use failure::{err_msg, Error}; use hyper::rt::Future; -use hyper::service::service_fn_ok; -use hyper::{Body, Request, Response, Server, StatusCode}; +use hyper::service::service_fn; +use hyper::Server; use notify::{RecommendedWatcher, RecursiveMode, Watcher}; use regex::Regex; use walkdir::WalkDir; @@ -37,68 +38,19 @@ use walkdir::WalkDir; pub use config::Config; pub use handler::*; use hook::*; +use service::*; const URIPATTERN_STR: &str = r"/webhook/(?P[A-Za-z._][A-Za-z0-9._]*)"; lazy_static! { static ref URIPATTERN: Regex = Regex::new(URIPATTERN_STR).unwrap(); - static ref HANDLERS: Mutex>> = Mutex::new(HashMap::new()); + // static ref HANDLERS: Mutex>> = Mutex::new(HashMap::new()); static ref PROGRAMS: Mutex> = Mutex::new(HashMap::new()); static ref HOOKS: Mutex> = Mutex::new(HashMap::new()); } const NOTFOUND: &str = "

Looks like you took a wrong turn!

There's nothing to see here.

"; -fn service_fn(req: Request) -> Result, Error> { - let path = req.uri().path().to_owned(); - let captures = URIPATTERN - .captures(path.as_ref()) - .ok_or(err_msg("Did not match url pattern"))?; - let name = captures - .name("name") - .ok_or(err_msg("Missing name"))? - .as_str(); - let hooks = HOOKS.lock().unwrap(); - let hook = hooks - .get(name) - .ok_or(err_msg(format!("Hook '{}' doesn't exist", name)))?; - - let req_obj = { - let headers = req - .headers() - .clone() - .into_iter() - .filter_map(|(k, v)| { - let key = k.unwrap().as_str().to_owned(); - v.to_str().map(|value| (key, value.to_owned())).ok() - }).collect::>(); - let method = req.method().as_str().to_owned(); - // probably not idiomatically the best way to do it - // i was just trying to get something working - let body = "wip".to_owned(); - json!({ - "body": body, - "headers": headers, - "method": method, - }) - }; - hook.iter() - .fold(Ok(req_obj), |prev, handler| { - prev.and_then(|val| handler.run(val)) - }).map(|_| Response::new(Body::from("success"))) -} - -fn service_fn_wrapper(req: Request) -> Response { - let uri = req.uri().path().to_owned(); - service_fn(req).unwrap_or_else(|err| { - eprintln!("Error from '{}': {}", uri, err); - Response::builder() - .status(StatusCode::NOT_FOUND) - .body(Body::from(NOTFOUND)) - .unwrap() - }) -} - fn load_config

(root: P) where P: AsRef, @@ -123,8 +75,7 @@ where if !path.is_file() { continue; } - match path - .file_name() + match path.file_name() .and_then(|s| s.to_str()) .ok_or(err_msg("???")) .map(|s| { @@ -197,7 +148,7 @@ pub fn run(config: &Config) -> Result<(), Error> { let addr: SocketAddrV4 = SocketAddrV4::from_str(config.bind.as_ref())?; let server = Server::bind(&addr.into()) - .serve(|| service_fn_ok(service_fn_wrapper)) + .serve(|| service_fn(dip_service)) .map_err(|e| eprintln!("server error: {}", e)); println!("Listening on {:?}", addr); hyper::rt::run(server); diff --git a/src/service.rs b/src/service.rs new file mode 100644 index 0000000..b81011b --- /dev/null +++ b/src/service.rs @@ -0,0 +1,60 @@ +use std::collections::HashMap; + +use failure::{err_msg, Error}; +use futures::{future, Future}; +use hyper::{Body, Request, Response, StatusCode}; + +use {HOOKS, NOTFOUND, URIPATTERN}; + +fn service_fn(req: Request) -> Result, Error> { + let path = req.uri().path().to_owned(); + let captures = URIPATTERN + .captures(path.as_ref()) + .ok_or(err_msg("Did not match url pattern"))?; + let name = captures + .name("name") + .ok_or(err_msg("Missing name"))? + .as_str(); + let hooks = HOOKS.lock().unwrap(); + let hook = hooks + .get(name) + .ok_or(err_msg(format!("Hook '{}' doesn't exist", name)))?; + + let req_obj = { + let headers = req.headers() + .clone() + .into_iter() + .filter_map(|(k, v)| { + let key = k.unwrap().as_str().to_owned(); + v.to_str().map(|value| (key, value.to_owned())).ok() + }) + .collect::>(); + let method = req.method().as_str().to_owned(); + // probably not idiomatically the best way to do it + // i was just trying to get something working + let body = "wip".to_owned(); + json!({ + "body": body, + "headers": headers, + "method": method, + }) + }; + hook.iter() + .fold(Ok(req_obj), |prev, handler| { + prev.and_then(|val| handler.run(val)) + }) + .map(|_| Response::new(Body::from("success"))) +} + +pub fn dip_service( + req: Request, +) -> Box, Error = String> + Send> { + let uri = req.uri().path().to_owned(); + Box::new(future::ok(service_fn(req).unwrap_or_else(|err| { + eprintln!("Error from '{}': {}", uri, err); + Response::builder() + .status(StatusCode::NOT_FOUND) + .body(Body::from(NOTFOUND)) + .unwrap() + }))) +} From 6b6087c4e95bf62a5a0ee4d735557b4bfa3f137a Mon Sep 17 00:00:00 2001 From: Michael Date: Thu, 16 Aug 2018 15:10:22 +0000 Subject: [PATCH 02/12] move the structopt struct into the lib --- src/config.rs | 8 ++++++++ src/lib.rs | 2 ++ src/main.rs | 24 ++---------------------- 3 files changed, 12 insertions(+), 22 deletions(-) diff --git a/src/config.rs b/src/config.rs index 4d37664..e56942d 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,8 +1,16 @@ use std::path::{Path, PathBuf}; +#[derive(Debug, StructOpt)] pub struct Config { + /// The root configuration directory for dip. This argument is required. + #[structopt(short = "d", long = "root", parse(from_os_str))] pub root: PathBuf, + /// A string containing the address to bind to. This defaults to "0.0.0.0:5000". + #[structopt(short = "b", long = "bind", default_value = "0.0.0.0:5000")] pub bind: String, + /// If a hook is specified here, it will be triggered manually exactly once and then the + /// program will exit rather than running as a server. + #[structopt(short = "h", long = "hook")] pub hook: Option, } diff --git a/src/lib.rs b/src/lib.rs index f080ece..23fb59c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -10,6 +10,8 @@ extern crate serde_json; #[macro_use] extern crate lazy_static; extern crate notify; +#[macro_use] +extern crate structopt; extern crate regex; extern crate toml; extern crate walkdir; diff --git a/src/main.rs b/src/main.rs index 27290ba..88a3586 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,32 +1,12 @@ -#[macro_use] -extern crate structopt; extern crate dip; extern crate failure; - -use std::path::PathBuf; +extern crate structopt; use dip::Config; use failure::Error; use structopt::StructOpt; -#[derive(Debug, StructOpt)] -struct Opt { - /// The root configuration directory for dip. This argument is required. - #[structopt(short = "d", long = "root", parse(from_os_str))] - root: PathBuf, - /// A string containing the address to bind to. This defaults to "0.0.0.0:5000". - #[structopt(short = "b", long = "bind")] - bind: Option, - /// If a hook is specified here, it will be triggered manually exactly once and then the - /// program will exit rather than running as a server. - #[structopt(short = "h", long = "hook")] - hook: Option, -} - fn main() -> Result<(), Error> { - let opt = Opt::from_args(); - println!("{:?}", opt); - - let config = Config::new(opt.root).bind(opt.bind).hook(opt.hook); + let config = Config::from_args(); dip::run(&config) } From e3ed6d85174aa4715996091085781bfdcd532f5e Mon Sep 17 00:00:00 2001 From: Michael Date: Thu, 16 Aug 2018 15:18:45 +0000 Subject: [PATCH 03/12] opt to skip the check for now --- examples/github.rs | 38 +++++++++++++++++++++----------------- src/lib.rs | 1 + 2 files changed, 22 insertions(+), 17 deletions(-) diff --git a/examples/github.rs b/examples/github.rs index d0c1119..68d0b96 100644 --- a/examples/github.rs +++ b/examples/github.rs @@ -29,10 +29,11 @@ struct Opt { pub config: String, } -#[derive(Serialize, Deserialize)] +#[derive(Debug, Serialize, Deserialize)] struct Config { secret: String, outdir: PathBuf, + disable_hmac_verify: bool, } #[derive(Serialize, Deserialize)] @@ -44,6 +45,7 @@ struct Payload { fn main() -> Result<(), Error> { let args = Opt::from_args(); let config: Config = serde_json::from_str(&args.config)?; + println!("{:?}", config); let mut payload = String::new(); io::stdin().read_to_string(&mut payload)?; @@ -51,24 +53,26 @@ fn main() -> Result<(), Error> { let payload: Payload = serde_json::from_str(&payload)?; println!("processed payload: {}", payload.body); - let secret = GenericArray::from_iter(config.secret.bytes()); - let mut mac = Hmac::::new(&secret); - mac.input(payload.body.as_bytes()); - let signature = mac.result() - .code() - .into_iter() - .map(|b| format!("{:02x}", b)) - .collect::>() - .join(""); + if !config.disable_hmac_verify { + let secret = GenericArray::from_iter(config.secret.bytes()); + let mut mac = Hmac::::new(&secret); + mac.input(payload.body.as_bytes()); + let signature = mac.result() + .code() + .into_iter() + .map(|b| format!("{:02x}", b)) + .collect::>() + .join(""); - let auth = payload - .headers - .get("x-hub-signature") - .ok_or(err_msg("Missing auth header"))?; + let auth = payload + .headers + .get("x-hub-signature") + .ok_or(err_msg("Missing auth header"))?; - let left = SecStr::from(format!("sha1={}", signature)); - let right = SecStr::from(auth.bytes().collect::>()); - assert!(left == right, "HMAC signature didn't match"); + let left = SecStr::from(format!("sha1={}", signature)); + let right = SecStr::from(auth.bytes().collect::>()); + assert!(left == right, "HMAC signature didn't match"); + } println!("gonna clone it to {:?}", config.outdir); Ok(()) diff --git a/src/lib.rs b/src/lib.rs index 23fb59c..3f312dc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -119,6 +119,7 @@ where } } } + println!("Done loading config."); } fn watch

(root: P) -> notify::Result<()> From 77afb5158241be138d47b5bbbd5d7afbe778985f Mon Sep 17 00:00:00 2001 From: Michael Date: Thu, 16 Aug 2018 15:23:04 +0000 Subject: [PATCH 04/12] slim down lib.rs --- src/config.rs | 75 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 76 ++------------------------------------------------- 2 files changed, 78 insertions(+), 73 deletions(-) diff --git a/src/config.rs b/src/config.rs index e56942d..fd8c5a5 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,5 +1,11 @@ use std::path::{Path, PathBuf}; +use failure::{err_msg, Error}; +use walkdir::WalkDir; + +use Hook; +use {HOOKS, PROGRAMS}; + #[derive(Debug, StructOpt)] pub struct Config { /// The root configuration directory for dip. This argument is required. @@ -34,3 +40,72 @@ impl Config { return self; } } + +pub fn load_config

(root: P) +where + P: AsRef, +{ + println!("Reloading config..."); + // hold on to the lock while config is being reloaded + { + let mut programs = PROGRAMS.lock().unwrap(); + // TODO: some kind of smart diff + programs.clear(); + let programs_dir = { + let mut p = root.as_ref().to_path_buf(); + p.push("handlers"); + p + }; + if programs_dir.exists() { + for entry in WalkDir::new(programs_dir) { + let path = match entry.as_ref().map(|e| e.path()) { + Ok(path) => path, + _ => continue, + }; + if !path.is_file() { + continue; + } + match path.file_name() + .and_then(|s| s.to_str()) + .ok_or(err_msg("???")) + .map(|s| { + let filename = s.to_owned(); + programs.insert(filename, path.to_path_buf()) + }) { + _ => (), // don't care + } + } + } + } + { + let mut hooks = HOOKS.lock().unwrap(); + hooks.clear(); + let hooks_dir = { + let mut p = root.as_ref().to_path_buf(); + p.push("hooks"); + p + }; + if hooks_dir.exists() { + for entry in WalkDir::new(hooks_dir) { + let path = match entry.as_ref().map(|e| e.path()) { + Ok(path) => path, + _ => continue, + }; + if !path.is_file() { + continue; + } + match (|path: &Path| -> Result<(), Error> { + let hook = Hook::from_file(path)?; + let name = hook.get_name(); + hooks.insert(name, hook); + Ok(()) + })(path) + { + Ok(_) => (), + Err(err) => eprintln!("Failed to read config from {:?}: {}", path, err), + } + } + } + } + println!("Done loading config."); +} diff --git a/src/lib.rs b/src/lib.rs index 3f312dc..8088266 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -29,13 +29,12 @@ use std::sync::{mpsc, Mutex}; use std::thread; use std::time::Duration; -use failure::{err_msg, Error}; +use failure::Error; use hyper::rt::Future; use hyper::service::service_fn; use hyper::Server; use notify::{RecommendedWatcher, RecursiveMode, Watcher}; use regex::Regex; -use walkdir::WalkDir; pub use config::Config; pub use handler::*; @@ -53,75 +52,6 @@ lazy_static! { const NOTFOUND: &str = "

Looks like you took a wrong turn!

There's nothing to see here.

"; -fn load_config

(root: P) -where - P: AsRef, -{ - println!("Reloading config..."); - // hold on to the lock while config is being reloaded - { - let mut programs = PROGRAMS.lock().unwrap(); - // TODO: some kind of smart diff - programs.clear(); - let programs_dir = { - let mut p = root.as_ref().to_path_buf(); - p.push("handlers"); - p - }; - if programs_dir.exists() { - for entry in WalkDir::new(programs_dir) { - let path = match entry.as_ref().map(|e| e.path()) { - Ok(path) => path, - _ => continue, - }; - if !path.is_file() { - continue; - } - match path.file_name() - .and_then(|s| s.to_str()) - .ok_or(err_msg("???")) - .map(|s| { - let filename = s.to_owned(); - programs.insert(filename, path.to_path_buf()) - }) { - _ => (), // don't care - } - } - } - } - { - let mut hooks = HOOKS.lock().unwrap(); - hooks.clear(); - let hooks_dir = { - let mut p = root.as_ref().to_path_buf(); - p.push("hooks"); - p - }; - if hooks_dir.exists() { - for entry in WalkDir::new(hooks_dir) { - let path = match entry.as_ref().map(|e| e.path()) { - Ok(path) => path, - _ => continue, - }; - if !path.is_file() { - continue; - } - match (|path: &Path| -> Result<(), Error> { - let hook = Hook::from_file(path)?; - let name = hook.get_name(); - hooks.insert(name, hook); - Ok(()) - })(path) - { - Ok(_) => (), - Err(err) => eprintln!("Failed to read config from {:?}: {}", path, err), - } - } - } - } - println!("Done loading config."); -} - fn watch

(root: P) -> notify::Result<()> where P: AsRef, @@ -135,7 +65,7 @@ where Ok(_) => { // for now, naively reload entire config every time // TODO: don't do this - load_config(root.as_ref()) + config::load_config(root.as_ref()) } Err(e) => println!("watch error: {:?}", e), } @@ -144,7 +74,7 @@ where /// Main entry point of the entire application. pub fn run(config: &Config) -> Result<(), Error> { - load_config(&config.root); + config::load_config(&config.root); let v = config.root.clone(); thread::spawn(|| watch(v)); From f7d015fab57746c609c4d45766aa26d75ddaa1ee Mon Sep 17 00:00:00 2001 From: Michael Date: Thu, 16 Aug 2018 16:49:29 +0000 Subject: [PATCH 05/12] service futures + hmac checking working --- examples/github.rs | 3 +- src/handler.rs | 4 +- src/lib.rs | 6 +-- src/service.rs | 100 +++++++++++++++++++++++++++------------------ 4 files changed, 67 insertions(+), 46 deletions(-) diff --git a/examples/github.rs b/examples/github.rs index 68d0b96..33d5459 100644 --- a/examples/github.rs +++ b/examples/github.rs @@ -33,6 +33,7 @@ struct Opt { struct Config { secret: String, outdir: PathBuf, + #[serde(default)] disable_hmac_verify: bool, } @@ -71,7 +72,7 @@ fn main() -> Result<(), Error> { let left = SecStr::from(format!("sha1={}", signature)); let right = SecStr::from(auth.bytes().collect::>()); - assert!(left == right, "HMAC signature didn't match"); + assert!(left == right, "HMAC signature didn't match",); } println!("gonna clone it to {:?}", config.outdir); diff --git a/src/handler.rs b/src/handler.rs index b01b3b6..0f8a9b6 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -64,8 +64,8 @@ impl Handler { "'{:?}' returned with a non-zero status code: {}\nstdout:\n{}\nstderr:\n{}", self.exec, output.status, - String::from_utf8(output.stdout).unwrap(), - String::from_utf8(output.stderr).unwrap() + String::from_utf8(output.stdout).unwrap_or_else(|_| String::new()), + String::from_utf8(output.stderr).unwrap_or_else(|_| String::new()) ))); } Ok(json!({})) diff --git a/src/lib.rs b/src/lib.rs index 8088266..721b09d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -25,7 +25,7 @@ use std::collections::HashMap; use std::net::SocketAddrV4; use std::path::{Path, PathBuf}; use std::str::FromStr; -use std::sync::{mpsc, Mutex}; +use std::sync::{mpsc, Arc, Mutex}; use std::thread; use std::time::Duration; @@ -47,10 +47,10 @@ lazy_static! { static ref URIPATTERN: Regex = Regex::new(URIPATTERN_STR).unwrap(); // static ref HANDLERS: Mutex>> = Mutex::new(HashMap::new()); static ref PROGRAMS: Mutex> = Mutex::new(HashMap::new()); - static ref HOOKS: Mutex> = Mutex::new(HashMap::new()); + static ref HOOKS: Arc>> = Arc::new(Mutex::new(HashMap::new())); } -const NOTFOUND: &str = "

Looks like you took a wrong turn!

There's nothing to see here.

"; +// const NOTFOUND: &str = "

Looks like you took a wrong turn!

There's nothing to see here.

"; fn watch

(root: P) -> notify::Result<()> where diff --git a/src/service.rs b/src/service.rs index b81011b..e72ef37 100644 --- a/src/service.rs +++ b/src/service.rs @@ -1,52 +1,71 @@ use std::collections::HashMap; -use failure::{err_msg, Error}; -use futures::{future, Future}; -use hyper::{Body, Request, Response, StatusCode}; +use futures::{future, Future, Stream}; +use hyper::{Body, Error, Request, Response, StatusCode}; -use {HOOKS, NOTFOUND, URIPATTERN}; +use {HOOKS, URIPATTERN}; -fn service_fn(req: Request) -> Result, Error> { +pub fn dip_service(req: Request) -> Box, Error = Error> + Send> { let path = req.uri().path().to_owned(); - let captures = URIPATTERN - .captures(path.as_ref()) - .ok_or(err_msg("Did not match url pattern"))?; - let name = captures - .name("name") - .ok_or(err_msg("Missing name"))? - .as_str(); - let hooks = HOOKS.lock().unwrap(); - let hook = hooks - .get(name) - .ok_or(err_msg(format!("Hook '{}' doesn't exist", name)))?; - - let req_obj = { - let headers = req.headers() - .clone() - .into_iter() - .filter_map(|(k, v)| { - let key = k.unwrap().as_str().to_owned(); - v.to_str().map(|value| (key, value.to_owned())).ok() - }) - .collect::>(); - let method = req.method().as_str().to_owned(); - // probably not idiomatically the best way to do it - // i was just trying to get something working - let body = "wip".to_owned(); - json!({ - "body": body, - "headers": headers, - "method": method, - }) + let captures = match URIPATTERN.captures(path.as_ref()) { + Some(value) => value, + None => { + return Box::new(future::ok( + Response::builder() + .status(StatusCode::NOT_FOUND) + .body(Body::from("not found")) + .unwrap(), + )) + } }; - hook.iter() - .fold(Ok(req_obj), |prev, handler| { - prev.and_then(|val| handler.run(val)) + let name = match captures.name("name") { + Some(value) => value.as_str().to_owned(), + None => { + return Box::new(future::ok( + Response::builder() + .status(StatusCode::NOT_FOUND) + .body(Body::from("not found")) + .unwrap(), + )) + } + }; + + // TODO: filter by method as well + + let headers = req.headers() + .clone() + .into_iter() + .filter_map(|(k, v)| { + let key = k.unwrap().as_str().to_owned(); + v.to_str().map(|value| (key, value.to_owned())).ok() }) - .map(|_| Response::new(Body::from("success"))) + .collect::>(); + let method = req.method().as_str().to_owned(); + // probably not idiomatically the best way to do it + // i was just trying to get something working + let body = req.into_body(); + Box::new(body.concat2().map(move |body| { + let req_obj = json!({ + "body": String::from_utf8(body.to_vec()).unwrap(), + "headers": headers, + "method": method, + }); + let hooks = HOOKS.lock().unwrap(); + let hook = hooks.get(&name).unwrap(); + let (code, msg) = hook.iter() + .fold(Ok(req_obj), |prev, handler| { + prev.and_then(|val| handler.run(val)) + }) + .map(|_| (StatusCode::ACCEPTED, "success".to_owned())) + .unwrap_or_else(|err| (StatusCode::BAD_REQUEST, format!("Error: {}", err))); + Response::builder() + .status(code) + .body(Body::from(msg)) + .unwrap_or_else(|err| Response::new(Body::from(format!("{}", err)))) + })) } -pub fn dip_service( +/* pub fn dip_service( req: Request, ) -> Box, Error = String> + Send> { let uri = req.uri().path().to_owned(); @@ -58,3 +77,4 @@ pub fn dip_service( .unwrap() }))) } +*/ From 87a8486c3742416eafd99550103c72d914564c8d Mon Sep 17 00:00:00 2001 From: Michael Date: Thu, 16 Aug 2018 17:10:55 +0000 Subject: [PATCH 06/12] basic support for running commands directly --- src/handler.rs | 102 ++++++++++++++++++++++++++++++++----------------- 1 file changed, 68 insertions(+), 34 deletions(-) diff --git a/src/handler.rs b/src/handler.rs index 0f8a9b6..fd511d2 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -11,7 +11,12 @@ use PROGRAMS; pub struct Handler { config: TomlValue, - exec: PathBuf, + program: Program, +} + +pub enum Program { + Command(String), + Exec(PathBuf), } impl Handler { @@ -21,15 +26,26 @@ impl Handler { .ok_or(err_msg("No 'type' found."))? .as_str() .ok_or(err_msg("'type' is not a string."))?; - let exec = { - let programs = PROGRAMS.lock().unwrap(); - programs - .get(handler) - .ok_or(err_msg(format!("'{}' is not a valid executable", handler))) - .map(|value| value.clone())? + let program = match handler { + "script" => { + let command = config + .get("command") + .ok_or(err_msg("No 'command' found"))? + .as_str() + .ok_or(err_msg("'command' is not a string."))?; + Program::Command(command.to_owned()) + } + handler => { + let programs = PROGRAMS.lock().unwrap(); + let program = programs + .get(handler) + .ok_or(err_msg(format!("'{}' is not a valid executable", handler))) + .map(|value| value.clone())?; + Program::Exec(program) + } }; let config = config.clone(); - Ok(Handler { config, exec }) + Ok(Handler { config, program }) } pub fn run(&self, input: JsonValue) -> Result { let config = { @@ -41,33 +57,51 @@ impl Handler { String::from_utf8(buf).unwrap() }; - let mut child = Command::new(&self.exec) - .env("DIP_ROOT", "") - .arg("--config") - .arg(config) - .stdin(Stdio::piped()) - .stdout(Stdio::piped()) - .stderr(Stdio::piped()) - .spawn()?; - { - match child.stdin { - Some(ref mut stdin) => { - write!(stdin, "{}", input)?; + match &self.program { + Program::Command(ref cmd) => { + // TODO: allow some kind of simple variable replacement + let output = Command::new("/bin/bash").arg("-c").arg(cmd).output()?; + if !output.status.success() { + // TODO: get rid of unwraps + return Err(err_msg(format!( + "Command '{}' returned with a non-zero status code: {}\nstdout:\n{}\nstderr:\n{}", + cmd, + output.status, + String::from_utf8(output.stdout).unwrap_or_else(|_| String::new()), + String::from_utf8(output.stderr).unwrap_or_else(|_| String::new()) + ))); } - None => bail!("done fucked"), - }; - } - let output = child.wait_with_output()?; - if !output.status.success() { - // TODO: get rid of unwraps - return Err(err_msg(format!( - "'{:?}' returned with a non-zero status code: {}\nstdout:\n{}\nstderr:\n{}", - self.exec, - output.status, - String::from_utf8(output.stdout).unwrap_or_else(|_| String::new()), - String::from_utf8(output.stderr).unwrap_or_else(|_| String::new()) - ))); - } + } + Program::Exec(ref path) => { + let mut child = Command::new(&path) + .env("DIP_ROOT", "") + .arg("--config") + .arg(config) + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn()?; + { + match child.stdin { + Some(ref mut stdin) => { + write!(stdin, "{}", input)?; + } + None => bail!("done fucked"), + }; + } + let output = child.wait_with_output()?; + if !output.status.success() { + // TODO: get rid of unwraps + return Err(err_msg(format!( + "'{:?}' returned with a non-zero status code: {}\nstdout:\n{}\nstderr:\n{}", + path, + output.status, + String::from_utf8(output.stdout).unwrap_or_else(|_| String::new()), + String::from_utf8(output.stderr).unwrap_or_else(|_| String::new()) + ))); + } + } + }; Ok(json!({})) } } From a71eaff19e68fc4eb472342eeb3852fc9a13c7c1 Mon Sep 17 00:00:00 2001 From: Michael Date: Thu, 16 Aug 2018 17:15:07 +0000 Subject: [PATCH 07/12] readme example --- README.md | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index b225758..d055b33 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,18 @@ Dip Configurable webhook server. -Read documentation at `cargo doc`. +Express your webhooks in terms of composable blocks such as: + +```toml +[[handlers]] +type = "github" +secret = "hunter2" +outdir = "/home/michael/dip" + +[[handlers]] +type = "script" +command = "cargo build" +``` Contact ------- From 92e5168e3eae421b765f336eba9f182c38292605 Mon Sep 17 00:00:00 2001 From: Michael Date: Thu, 16 Aug 2018 17:38:11 +0000 Subject: [PATCH 08/12] rename to action --- src/handler.rs | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/src/handler.rs b/src/handler.rs index fd511d2..96b0acb 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -11,10 +11,10 @@ use PROGRAMS; pub struct Handler { config: TomlValue, - program: Program, + action: Action, } -pub enum Program { +pub enum Action { Command(String), Exec(PathBuf), } @@ -26,14 +26,14 @@ impl Handler { .ok_or(err_msg("No 'type' found."))? .as_str() .ok_or(err_msg("'type' is not a string."))?; - let program = match handler { - "script" => { + let action = match handler { + "command" => { let command = config .get("command") .ok_or(err_msg("No 'command' found"))? .as_str() .ok_or(err_msg("'command' is not a string."))?; - Program::Command(command.to_owned()) + Action::Command(command.to_owned()) } handler => { let programs = PROGRAMS.lock().unwrap(); @@ -41,11 +41,11 @@ impl Handler { .get(handler) .ok_or(err_msg(format!("'{}' is not a valid executable", handler))) .map(|value| value.clone())?; - Program::Exec(program) + Action::Exec(program) } }; let config = config.clone(); - Ok(Handler { config, program }) + Ok(Handler { config, action }) } pub fn run(&self, input: JsonValue) -> Result { let config = { @@ -57,10 +57,14 @@ impl Handler { String::from_utf8(buf).unwrap() }; - match &self.program { - Program::Command(ref cmd) => { + match &self.action { + Action::Command(ref cmd) => { // TODO: allow some kind of simple variable replacement - let output = Command::new("/bin/bash").arg("-c").arg(cmd).output()?; + let output = Command::new("/bin/bash") + .env("DIP_ROOT", "lol") + .arg("-c") + .arg(cmd) + .output()?; if !output.status.success() { // TODO: get rid of unwraps return Err(err_msg(format!( @@ -72,7 +76,7 @@ impl Handler { ))); } } - Program::Exec(ref path) => { + Action::Exec(ref path) => { let mut child = Command::new(&path) .env("DIP_ROOT", "") .arg("--config") From 046594bec787abff7ec122a0d460517ad7da2572 Mon Sep 17 00:00:00 2001 From: Michael Date: Thu, 16 Aug 2018 17:51:33 +0000 Subject: [PATCH 09/12] forward stdout and stderr into the response body --- src/handler.rs | 11 +++++++++-- src/service.rs | 11 ++++++++++- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/src/handler.rs b/src/handler.rs index 96b0acb..4f713b2 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -57,7 +57,7 @@ impl Handler { String::from_utf8(buf).unwrap() }; - match &self.action { + let output = match &self.action { Action::Command(ref cmd) => { // TODO: allow some kind of simple variable replacement let output = Command::new("/bin/bash") @@ -75,6 +75,7 @@ impl Handler { String::from_utf8(output.stderr).unwrap_or_else(|_| String::new()) ))); } + output } Action::Exec(ref path) => { let mut child = Command::new(&path) @@ -104,8 +105,14 @@ impl Handler { String::from_utf8(output.stderr).unwrap_or_else(|_| String::new()) ))); } + output } }; - Ok(json!({})) + let stdout = String::from_utf8(output.stdout).unwrap_or_else(|_| String::new()); + let stderr = String::from_utf8(output.stderr).unwrap_or_else(|_| String::new()); + Ok(json!({ + "stdout": stdout, + "stderr": stderr, + })) } } diff --git a/src/service.rs b/src/service.rs index e72ef37..b5ccd2d 100644 --- a/src/service.rs +++ b/src/service.rs @@ -56,7 +56,16 @@ pub fn dip_service(req: Request) -> Box, Erro .fold(Ok(req_obj), |prev, handler| { prev.and_then(|val| handler.run(val)) }) - .map(|_| (StatusCode::ACCEPTED, "success".to_owned())) + .map(|res| { + ( + StatusCode::ACCEPTED, + format!( + "stdout:\n{}\n\nstderr:\n{}", + res.get("stdout").and_then(|v| v.as_str()).unwrap_or(""), + res.get("stderr").and_then(|v| v.as_str()).unwrap_or(""), + ), + ) + }) .unwrap_or_else(|err| (StatusCode::BAD_REQUEST, format!("Error: {}", err))); Response::builder() .status(code) From 4dbdcd67974dc6ab4498e660497e8bad55086b5c Mon Sep 17 00:00:00 2001 From: Michael Date: Thu, 16 Aug 2018 20:42:28 +0000 Subject: [PATCH 10/12] use tempdir to have a consistent filesystem during build --- Cargo.lock | 37 +++++++++++++++++++++++++++++++++++ Cargo.toml | 1 + examples/github.rs | 28 +++++++++++++++++++++++---- src/handler.rs | 19 ++++++++++++++---- src/lib.rs | 1 + src/service.rs | 48 +++++++++++++++++++++++++++------------------- 6 files changed, 106 insertions(+), 28 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3c9ca43..7c41c95 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -192,6 +192,7 @@ dependencies = [ "hmac 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)", "hyper 0.12.8 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "mktemp 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", "notify 4.0.4 (registry+https://github.com/rust-lang/crates.io-index)", "regex 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", "secstr 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -504,6 +505,14 @@ dependencies = [ "ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "mktemp" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "uuid 0.1.18 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "net2" version = "0.2.33" @@ -569,6 +578,16 @@ dependencies = [ "proc-macro2 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "rand" +version = "0.3.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)", + "rand 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "rand" version = "0.4.2" @@ -617,6 +636,11 @@ name = "rustc-demangle" version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "rustc-serialize" +version = "0.3.24" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "rustc_version" version = "0.2.3" @@ -960,6 +984,15 @@ name = "utf8-ranges" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "uuid" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "rand 0.3.22 (registry+https://github.com/rust-lang/crates.io-index)", + "rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "vec_map" version = "0.8.1" @@ -1090,6 +1123,7 @@ dependencies = [ "checksum mio 0.6.15 (registry+https://github.com/rust-lang/crates.io-index)" = "4fcfcb32d63961fb6f367bfd5d21e4600b92cd310f71f9dca25acae196eb1560" "checksum miow 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "3e690c5df6b2f60acd45d56378981e827ff8295562fc8d34f573deb267a59cd1" "checksum miow 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "8c1f2f3b1cf331de6896aabf6e9d55dca90356cc9960cca7eaaf408a355ae919" +"checksum mktemp 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "77001ceb9eed65439f3dc2a2543f9ba1417d912686bf224a7738d0966e6dcd69" "checksum net2 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)" = "42550d9fb7b6684a6d404d9fa7250c2eb2646df731d1c06afc06dcee9e1bcf88" "checksum nix 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "bfb3ddedaa14746434a02041940495bf11325c22f6d36125d3bdd56090d50a79" "checksum nodrop 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)" = "9a2228dca57108069a5262f2ed8bd2e82496d2e074a06d1ccc7ce1687b6ae0a2" @@ -1097,12 +1131,14 @@ dependencies = [ "checksum num_cpus 1.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c51a3322e4bca9d212ad9a158a02abc6934d005490c054a2778df73a70aa0a30" "checksum proc-macro2 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)" = "762eea716b821300a86da08870a64b597304866ceb9f54a11d67b4cf56459c6a" "checksum quote 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)" = "ed7d650913520df631972f21e104a4fa2f9c82a14afc65d17b388a2e29731e7c" +"checksum rand 0.3.22 (registry+https://github.com/rust-lang/crates.io-index)" = "15a732abf9d20f0ad8eeb6f909bf6868722d9a06e1e50802b6a70351f40b4eb1" "checksum rand 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "eba5f8cb59cc50ed56be8880a5c7b496bfd9bd26394e176bc67884094145c2c5" "checksum redox_syscall 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)" = "c214e91d3ecf43e9a4e41e578973adeb14b474f2bee858742d127af75a0112b1" "checksum redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7e891cfe48e9100a70a3b6eb652fef28920c117d366339687bd5576160db0f76" "checksum regex 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "5bbbea44c5490a1e84357ff28b7d518b4619a159fed5d25f6c1de2d19cc42814" "checksum regex-syntax 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "747ba3b235651f6e2f67dfa8bcdcd073ddb7c243cb21c442fc12395dfcac212d" "checksum rustc-demangle 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "bcfe5b13211b4d78e5c2cadfebd7769197d95c639c35a50057eb4c05de811395" +"checksum rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)" = "dcf128d1287d2ea9d80910b5f1120d0b8eede3fbf1abe91c40d39ea7d51e6fda" "checksum rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" "checksum ryu 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "16aa12da69951804cddf5f74d96abcc414a31b064e610dc81e37c1536082f491" "checksum same-file 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "cfb6eded0b06a0b512c8ddbcf04089138c9b4362c2f696f3c3d76039d68f3637" @@ -1144,6 +1180,7 @@ dependencies = [ "checksum unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" "checksum unreachable 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "382810877fe448991dfc7f0dd6e3ae5d58088fd0ea5e35189655f84e6814fa56" "checksum utf8-ranges 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "662fab6525a98beff2921d7f61a39e7d59e0b425ebc7d0d9e66d316e55124122" +"checksum uuid 0.1.18 (registry+https://github.com/rust-lang/crates.io-index)" = "78c590b5bd79ed10aad8fb75f078a59d8db445af6c743e55c4a53227fc01c13f" "checksum vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "05c78687fb1a80548ae3250346c3db86a80a7cdd77bda190189f2d0a0987c81a" "checksum version_check 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "7716c242968ee87e5542f8021178248f267f295a5c4803beae8b8b7fd9bc6051" "checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" diff --git a/Cargo.toml b/Cargo.toml index 38924da..b0c4e6d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,6 +16,7 @@ sha-1 = "0.7" failure = "0.1" futures = "0.1" hyper = "0.12" +mktemp = "0.3" lazy_static = "1.1" notify = "4.0" regex = "1.0" diff --git a/examples/github.rs b/examples/github.rs index 33d5459..8be1917 100644 --- a/examples/github.rs +++ b/examples/github.rs @@ -11,9 +11,11 @@ extern crate generic_array; extern crate structopt; use std::collections::HashMap; +use std::env; use std::io::{self, Read}; use std::iter::FromIterator; use std::path::PathBuf; +use std::process::Command; use failure::{err_msg, Error}; use generic_array::GenericArray; @@ -32,7 +34,6 @@ struct Opt { #[derive(Debug, Serialize, Deserialize)] struct Config { secret: String, - outdir: PathBuf, #[serde(default)] disable_hmac_verify: bool, } @@ -43,6 +44,16 @@ struct Payload { headers: HashMap, } +#[derive(Debug, Serialize, Deserialize)] +struct RepositoryInfo { + clone_url: String, +} + +#[derive(Debug, Serialize, Deserialize)] +struct GithubPayload { + repository: RepositoryInfo, +} + fn main() -> Result<(), Error> { let args = Opt::from_args(); let config: Config = serde_json::from_str(&args.config)?; @@ -50,9 +61,7 @@ fn main() -> Result<(), Error> { let mut payload = String::new(); io::stdin().read_to_string(&mut payload)?; - println!("raw payload: {}", payload); let payload: Payload = serde_json::from_str(&payload)?; - println!("processed payload: {}", payload.body); if !config.disable_hmac_verify { let secret = GenericArray::from_iter(config.secret.bytes()); @@ -75,6 +84,17 @@ fn main() -> Result<(), Error> { assert!(left == right, "HMAC signature didn't match",); } - println!("gonna clone it to {:?}", config.outdir); + let payload: GithubPayload = serde_json::from_str(&payload.body)?; + let mut target_path = PathBuf::from(env::var("DIP_WORKDIR")?); + target_path.push("repository"); + println!("{:?}", &target_path); + Command::new("git") + .arg("clone") + .arg(&payload.repository.clone_url) + .arg("--recursive") + .arg("--depth") + .arg("1") + .arg(&target_path) + .output()?; Ok(()) } diff --git a/src/handler.rs b/src/handler.rs index 4f713b2..4ef61e8 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -40,6 +40,11 @@ impl Handler { let program = programs .get(handler) .ok_or(err_msg(format!("'{}' is not a valid executable", handler))) + .and_then(|value| { + value + .canonicalize() + .map_err(|_| err_msg("failed to canonicalize the path")) + }) .map(|value| value.clone())?; Action::Exec(program) } @@ -47,7 +52,8 @@ impl Handler { let config = config.clone(); Ok(Handler { config, action }) } - pub fn run(&self, input: JsonValue) -> Result { + + pub fn run(&self, temp_path: &PathBuf, input: JsonValue) -> Result { let config = { let mut buf: Vec = Vec::new(); { @@ -61,7 +67,9 @@ impl Handler { Action::Command(ref cmd) => { // TODO: allow some kind of simple variable replacement let output = Command::new("/bin/bash") + .current_dir(&temp_path) .env("DIP_ROOT", "lol") + .env("DIP_WORKDIR", temp_path) .arg("-c") .arg(cmd) .output()?; @@ -79,7 +87,9 @@ impl Handler { } Action::Exec(ref path) => { let mut child = Command::new(&path) + .current_dir(&temp_path) .env("DIP_ROOT", "") + .env("DIP_WORKDIR", temp_path) .arg("--config") .arg(config) .stdin(Stdio::piped()) @@ -108,11 +118,12 @@ impl Handler { output } }; + let stdout = String::from_utf8(output.stdout).unwrap_or_else(|_| String::new()); let stderr = String::from_utf8(output.stderr).unwrap_or_else(|_| String::new()); Ok(json!({ - "stdout": stdout, - "stderr": stderr, - })) + "stdout": stdout, + "stderr": stderr, + })) } } diff --git a/src/lib.rs b/src/lib.rs index 721b09d..047a6aa 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,6 +4,7 @@ extern crate failure; extern crate futures; extern crate hyper; +extern crate mktemp; extern crate serde; #[macro_use] extern crate serde_json; diff --git a/src/service.rs b/src/service.rs index b5ccd2d..47d36d7 100644 --- a/src/service.rs +++ b/src/service.rs @@ -2,6 +2,7 @@ use std::collections::HashMap; use futures::{future, Future, Stream}; use hyper::{Body, Error, Request, Response, StatusCode}; +use mktemp::Temp; use {HOOKS, URIPATTERN}; @@ -51,26 +52,33 @@ pub fn dip_service(req: Request) -> Box, Erro "method": method, }); let hooks = HOOKS.lock().unwrap(); - let hook = hooks.get(&name).unwrap(); - let (code, msg) = hook.iter() - .fold(Ok(req_obj), |prev, handler| { - prev.and_then(|val| handler.run(val)) - }) - .map(|res| { - ( - StatusCode::ACCEPTED, - format!( - "stdout:\n{}\n\nstderr:\n{}", - res.get("stdout").and_then(|v| v.as_str()).unwrap_or(""), - res.get("stderr").and_then(|v| v.as_str()).unwrap_or(""), - ), - ) - }) - .unwrap_or_else(|err| (StatusCode::BAD_REQUEST, format!("Error: {}", err))); - Response::builder() - .status(code) - .body(Body::from(msg)) - .unwrap_or_else(|err| Response::new(Body::from(format!("{}", err)))) + { + let mut temp_dir = Temp::new_dir().unwrap(); + let temp_path = temp_dir.to_path_buf(); + assert!(temp_path.exists()); + + let hook = hooks.get(&name).unwrap(); + let (code, msg) = hook.iter() + .fold(Ok(req_obj), |prev, handler| { + prev.and_then(|val| handler.run(&temp_path, val)) + }) + .map(|res| { + ( + StatusCode::ACCEPTED, + format!( + "stdout:\n{}\n\nstderr:\n{}", + res.get("stdout").and_then(|v| v.as_str()).unwrap_or(""), + res.get("stderr").and_then(|v| v.as_str()).unwrap_or(""), + ), + ) + }) + .unwrap_or_else(|err| (StatusCode::BAD_REQUEST, format!("Error: {:?}", err))); + temp_dir.release(); + Response::builder() + .status(code) + .body(Body::from(msg)) + .unwrap_or_else(|err| Response::new(Body::from(format!("{}", err)))) + } })) } From 2a25ad669b63fb7aef729cfa974c7ea833bbdef5 Mon Sep 17 00:00:00 2001 From: Michael Date: Thu, 16 Aug 2018 21:06:23 +0000 Subject: [PATCH 11/12] spawn thread --- examples/github.rs | 9 +++- src/handler.rs | 3 ++ src/service.rs | 101 +++++++++++++++++++++++---------------------- 3 files changed, 62 insertions(+), 51 deletions(-) diff --git a/examples/github.rs b/examples/github.rs index 8be1917..2574d9f 100644 --- a/examples/github.rs +++ b/examples/github.rs @@ -36,6 +36,8 @@ struct Config { secret: String, #[serde(default)] disable_hmac_verify: bool, + #[serde(default = "default_path")] + path: PathBuf, } #[derive(Serialize, Deserialize)] @@ -54,6 +56,10 @@ struct GithubPayload { repository: RepositoryInfo, } +fn default_path() -> PathBuf { + PathBuf::from(".") +} + fn main() -> Result<(), Error> { let args = Opt::from_args(); let config: Config = serde_json::from_str(&args.config)?; @@ -86,8 +92,7 @@ fn main() -> Result<(), Error> { let payload: GithubPayload = serde_json::from_str(&payload.body)?; let mut target_path = PathBuf::from(env::var("DIP_WORKDIR")?); - target_path.push("repository"); - println!("{:?}", &target_path); + target_path.push(&config.path); Command::new("git") .arg("clone") .arg(&payload.repository.clone_url) diff --git a/src/handler.rs b/src/handler.rs index 4ef61e8..f63b040 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -20,6 +20,9 @@ pub enum Action { } impl Handler { + pub fn config(&self) -> &TomlValue { + &self.config + } pub fn from(config: &TomlValue) -> Result { let handler = config .get("type") diff --git a/src/service.rs b/src/service.rs index 47d36d7..70891f6 100644 --- a/src/service.rs +++ b/src/service.rs @@ -1,4 +1,5 @@ use std::collections::HashMap; +use std::thread; use futures::{future, Future, Stream}; use hyper::{Body, Error, Request, Response, StatusCode}; @@ -42,56 +43,58 @@ pub fn dip_service(req: Request) -> Box, Erro }) .collect::>(); let method = req.method().as_str().to_owned(); - // probably not idiomatically the best way to do it - // i was just trying to get something working - let body = req.into_body(); - Box::new(body.concat2().map(move |body| { - let req_obj = json!({ - "body": String::from_utf8(body.to_vec()).unwrap(), - "headers": headers, - "method": method, - }); - let hooks = HOOKS.lock().unwrap(); - { - let mut temp_dir = Temp::new_dir().unwrap(); - let temp_path = temp_dir.to_path_buf(); - assert!(temp_path.exists()); - let hook = hooks.get(&name).unwrap(); - let (code, msg) = hook.iter() - .fold(Ok(req_obj), |prev, handler| { - prev.and_then(|val| handler.run(&temp_path, val)) - }) - .map(|res| { - ( - StatusCode::ACCEPTED, - format!( - "stdout:\n{}\n\nstderr:\n{}", - res.get("stdout").and_then(|v| v.as_str()).unwrap_or(""), - res.get("stderr").and_then(|v| v.as_str()).unwrap_or(""), - ), - ) - }) - .unwrap_or_else(|err| (StatusCode::BAD_REQUEST, format!("Error: {:?}", err))); - temp_dir.release(); - Response::builder() - .status(code) - .body(Body::from(msg)) - .unwrap_or_else(|err| Response::new(Body::from(format!("{}", err)))) - } - })) -} + // spawn job + thread::spawn(move || { + req.into_body().concat2().map(move |body| { + let body = String::from_utf8(body.to_vec()).unwrap(); + let req_obj = json!({ + "body": body, + "headers": headers, + "method": method, + }); + let hooks = HOOKS.lock().unwrap(); + { + let mut temp_dir = Temp::new_dir().unwrap(); + let temp_path = temp_dir.to_path_buf(); + assert!(temp_path.exists()); -/* pub fn dip_service( - req: Request, -) -> Box, Error = String> + Send> { - let uri = req.uri().path().to_owned(); - Box::new(future::ok(service_fn(req).unwrap_or_else(|err| { - eprintln!("Error from '{}': {}", uri, err); + let hook = hooks.get(&name).unwrap(); + let (code, msg) = hook.iter() + .fold(Ok(req_obj), |prev, handler| { + prev.and_then(|val| { + println!("Running {}...", handler.config()); + let result = handler.run(&temp_path, val); + println!("{:?}", result); + result + }) + }) + .map(|res| { + ( + StatusCode::ACCEPTED, + format!( + "stdout:\n{}\n\nstderr:\n{}", + res.get("stdout").and_then(|v| v.as_str()).unwrap_or(""), + res.get("stderr").and_then(|v| v.as_str()).unwrap_or(""), + ), + ) + }) + .unwrap_or_else(|err| (StatusCode::BAD_REQUEST, format!("Error: {:?}", err))); + + temp_dir.release(); + Response::builder() + .status(code) + .body(Body::from(msg)) + .unwrap_or_else(|err| Response::new(Body::from(format!("{}", err)))) + } + }) + }); + + Box::new(future::ok( Response::builder() - .status(StatusCode::NOT_FOUND) - .body(Body::from(NOTFOUND)) - .unwrap() - }))) + .status(StatusCode::ACCEPTED) + // TODO: assign job a uuid and do some logging + .body(Body::from(format!("job {} started", -1))) + .unwrap(), + )) } -*/ From d009f0b81af1ed220fcca165cc6a3211aa55120f Mon Sep 17 00:00:00 2001 From: Michael Date: Thu, 16 Aug 2018 22:39:58 +0000 Subject: [PATCH 12/12] move hook iter impl to hook --- README.md | 3 +-- src/handler.rs | 8 ++++++-- src/hook.rs | 32 ++++++++++++++++++++++++++++---- src/service.rs | 21 +-------------------- 4 files changed, 36 insertions(+), 28 deletions(-) diff --git a/README.md b/README.md index d055b33..0a502c9 100644 --- a/README.md +++ b/README.md @@ -12,10 +12,9 @@ Express your webhooks in terms of composable blocks such as: [[handlers]] type = "github" secret = "hunter2" -outdir = "/home/michael/dip" [[handlers]] -type = "script" +type = "command" command = "cargo build" ``` diff --git a/src/handler.rs b/src/handler.rs index f63b040..b96035a 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -69,13 +69,17 @@ impl Handler { let output = match &self.action { Action::Command(ref cmd) => { // TODO: allow some kind of simple variable replacement - let output = Command::new("/bin/bash") + let child = Command::new("/bin/bash") .current_dir(&temp_path) .env("DIP_ROOT", "lol") .env("DIP_WORKDIR", temp_path) .arg("-c") .arg(cmd) - .output()?; + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn()?; + let output = child.wait_with_output()?; if !output.status.success() { // TODO: get rid of unwraps return Err(err_msg(format!( diff --git a/src/hook.rs b/src/hook.rs index f1676a8..15673fb 100644 --- a/src/hook.rs +++ b/src/hook.rs @@ -1,10 +1,11 @@ use std::fs::File; use std::io::Read; -use std::path::Path; +use std::path::{Path, PathBuf}; use std::slice::Iter; use failure::{err_msg, Error}; -use hyper::{Body, Request, Response}; +use hyper::StatusCode; +use serde_json::Value as JsonValue; use toml::Value; use Handler; @@ -51,7 +52,30 @@ impl Hook { pub fn iter(&self) -> Iter { self.handlers.iter() } - pub fn handle(&self, _payload: &Request) -> Result, Error> { - Ok(Response::new(Body::from("lol"))) + pub fn handle( + &self, + req: JsonValue, + temp_path: &PathBuf, + ) -> Result<(StatusCode, String), Error> { + Ok(self.iter() + .fold(Ok(req), |prev, handler| { + prev.and_then(|val| { + println!("Running {}...", handler.config()); + let result = handler.run(&temp_path, val); + println!("{:?}", result); + result + }) + }) + .map(|res| { + ( + StatusCode::ACCEPTED, + format!( + "stdout:\n{}\n\nstderr:\n{}", + res.get("stdout").and_then(|v| v.as_str()).unwrap_or(""), + res.get("stderr").and_then(|v| v.as_str()).unwrap_or(""), + ), + ) + }) + .unwrap_or_else(|err| (StatusCode::BAD_REQUEST, format!("Error: {:?}", err)))) } } diff --git a/src/service.rs b/src/service.rs index 70891f6..8afaa00 100644 --- a/src/service.rs +++ b/src/service.rs @@ -60,26 +60,7 @@ pub fn dip_service(req: Request) -> Box, Erro assert!(temp_path.exists()); let hook = hooks.get(&name).unwrap(); - let (code, msg) = hook.iter() - .fold(Ok(req_obj), |prev, handler| { - prev.and_then(|val| { - println!("Running {}...", handler.config()); - let result = handler.run(&temp_path, val); - println!("{:?}", result); - result - }) - }) - .map(|res| { - ( - StatusCode::ACCEPTED, - format!( - "stdout:\n{}\n\nstderr:\n{}", - res.get("stdout").and_then(|v| v.as_str()).unwrap_or(""), - res.get("stderr").and_then(|v| v.as_str()).unwrap_or(""), - ), - ) - }) - .unwrap_or_else(|err| (StatusCode::BAD_REQUEST, format!("Error: {:?}", err))); + let (code, msg) = hook.handle(req_obj, &temp_path).unwrap(); temp_dir.release(); Response::builder()