hook setup

This commit is contained in:
Michael Zhang 2018-08-12 23:34:49 -07:00
parent 6f81fb947f
commit 8042d2d19a
No known key found for this signature in database
GPG key ID: A1B65B603268116B
4 changed files with 100 additions and 22 deletions

16
Cargo.lock generated
View file

@ -151,6 +151,7 @@ dependencies = [
"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)",
"structopt 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)",
"toml 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
"walkdir 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
@ -579,6 +580,11 @@ name = "semver-parser"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "serde"
version = "1.0.71"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "slab"
version = "0.1.3"
@ -793,6 +799,14 @@ dependencies = [
"tokio-reactor 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "toml"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"serde 1.0.71 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "try-lock"
version = "0.2.2"
@ -965,6 +979,7 @@ dependencies = [
"checksum scopeguard 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "94258f53601af11e6a49f722422f6e3425c52b06245a5cf9bc09908b174f5e27"
"checksum semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403"
"checksum semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
"checksum serde 1.0.71 (registry+https://github.com/rust-lang/crates.io-index)" = "6dfad05c8854584e5f72fb859385ecdfa03af69c3fd0572f0da2d4c95f060bdb"
"checksum slab 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "d807fd58c4181bbabed77cb3b891ba9748241a552bcc5be698faaebefc54f46e"
"checksum slab 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "5f9776d6b986f77b35c6cf846c11ad986ff128fe0b2b63a3628e3755e8d3102d"
"checksum string 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "00caf261d6f90f588f8450b8e1230fa0d5be49ee6140fdfbcb55335aff350970"
@ -987,6 +1002,7 @@ dependencies = [
"checksum tokio-threadpool 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "24ab84f574027b0e875378f31575cf175360891919e93a3490f07e76e00e4efb"
"checksum tokio-timer 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "1c76b4e97a4f61030edff8bd272364e4f731b9f54c7307eb4eb733c3926eb96a"
"checksum tokio-udp 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "43eb534af6e8f37d43ab1b612660df14755c42bd003c5f8d2475ee78cc4600c0"
"checksum toml 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "a0263c6c02c4db6c8f7681f9fd35e90de799ebd4cfdeab77a38f4ff6b3d8c0d9"
"checksum try-lock 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e604eb7b43c06650e854be16a2a03155743d3752dd1c943f6829e26b7a36e382"
"checksum ucd-util 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "fd2be2d6639d0f8fe6cdda291ad456e23629558d466e2789d2c3e9892bda285d"
"checksum unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "882386231c45df4700b275c7ff55b6f3698780a650026380e72dabe76fa46526"

View file

@ -10,4 +10,5 @@ lazy_static = "1.1"
notify = "4.0"
regex = "1.0"
structopt = "0.2"
toml = "0.4"
walkdir = "2.2"

View file

@ -1,5 +1,22 @@
use failure::{err_msg, Error};
use hyper::{Body, Request, Response};
use toml::Value;
pub trait Hook: Send + Sync {
fn handle(&self, payload: &Request<Body>) -> Option<Response<Body>>;
pub struct Hook {
handler_type: String,
}
impl Hook {
pub fn from(config: &Value) -> Result<Self, Error> {
let handler_type = config
.get("type")
.ok_or(err_msg("Missing field 'type'"))?
.as_str()
.ok_or(err_msg("Field 'type' is not a string"))?
.to_owned();
Ok(Hook { handler_type })
}
pub fn handle(&self, payload: &Request<Body>) -> Option<Response<Body>> {
None
}
}

View file

@ -4,24 +4,27 @@ extern crate hyper;
extern crate lazy_static;
extern crate notify;
extern crate regex;
extern crate toml;
extern crate walkdir;
mod handler;
mod hook;
use std::collections::HashMap;
use std::fs::File;
use std::io::Read;
use std::path::Path;
use std::path::PathBuf;
use std::sync::mpsc;
use std::sync::{mpsc, Mutex};
use std::thread;
use std::time::Duration;
use failure::Error;
use failure::{err_msg, Error};
use hyper::rt::Future;
use hyper::service::service_fn_ok;
use hyper::{Body, Request, Response, Server};
use notify::{RecommendedWatcher, RecursiveMode, Watcher};
use regex::Regex;
use toml::Value;
use walkdir::WalkDir;
use handler::*;
@ -31,7 +34,7 @@ lazy_static! {
static ref URIPATTERN: Regex =
Regex::new(r"/webhook/(?P<name>[A-Za-z_][A-Za-z0-9_]*)").unwrap();
static ref HANDLERS: HashMap<String, Box<Handler>> = HashMap::new();
static ref HOOKS: HashMap<String, Box<Hook>> = HashMap::new();
static ref HOOKS: Mutex<HashMap<String, Hook>> = Mutex::new(HashMap::new());
}
const NOTFOUND: &str = r#"<html>
@ -56,7 +59,8 @@ fn service_fn(req: Request<Body>) -> Option<Response<Body>> {
Some(name) => name.as_str(),
None => return None,
};
let handler = match HOOKS.get(name) {
let hooks = HOOKS.lock().unwrap();
let handler = match hooks.get(name) {
Some(handler) => handler,
None => return None,
};
@ -70,6 +74,52 @@ fn service_fn_wrapper(req: Request<Body>) -> Response<Body> {
}
}
fn load_config<P>(root: P)
where
P: AsRef<Path>,
{
println!("Reloading config...");
// hold on to the lock while config is being reloaded
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 filename = path
.file_name()
.ok_or(err_msg("what the fuck bro"))?
.to_str()
.ok_or(err_msg("???"))?
.to_owned();
let mut file = File::open(path)?;
let mut contents = String::new();
file.read_to_string(&mut contents)?;
let config = contents.parse::<Value>()?;
let hook = Hook::from(&config)?;
hooks.insert(filename, hook);
Ok(())
})(path)
{
Ok(_) => (),
Err(err) => eprintln!("Failed to read config from {:?}: {}", path, err),
}
}
}
}
fn watch<P>(root: P) -> notify::Result<()>
where
P: AsRef<Path>,
@ -77,10 +127,13 @@ where
let (tx, rx) = mpsc::channel();
let mut watcher: RecommendedWatcher = Watcher::new(tx, Duration::from_secs(1))?;
println!("Watching {:?}", root.as_ref().to_path_buf());
watcher.watch(root, RecursiveMode::Recursive)?;
watcher.watch(root.as_ref(), RecursiveMode::Recursive)?;
loop {
match rx.recv() {
Ok(event) => println!("{:?}", event),
Ok(_) => {
// for now, naively reload entire config every time
load_config(root.as_ref())
}
Err(e) => println!("watch error: {:?}", e),
}
}
@ -90,26 +143,17 @@ pub fn run<P>(root: P) -> Result<(), Error>
where
P: AsRef<Path>,
{
let root = root.as_ref().to_path_buf();
let handlers_dir = {
let mut p = root.clone();
p.push("handlers");
p
};
if handlers_dir.exists() {
for entry in WalkDir::new(handlers_dir) {
let path = entry?.path().to_path_buf();
println!("{:?}", path);
}
}
load_config(&root);
thread::spawn(|| watch(root));
let v = root.as_ref().to_path_buf();
thread::spawn(|| watch(v));
let addr = ([127, 0, 0, 1], 3000).into();
let server = Server::bind(&addr)
.serve(|| service_fn_ok(service_fn_wrapper))
.map_err(|e| eprintln!("server error: {}", e));
println!("Listening on {:?}", addr);
hyper::rt::run(server);
Ok(())
}