zzz
This commit is contained in:
parent
9ae78d506e
commit
6b02463308
11 changed files with 725 additions and 872 deletions
1131
Cargo.lock
generated
1131
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
49
Cargo.toml
49
Cargo.toml
|
@ -1,28 +1,33 @@
|
||||||
[package]
|
[package]
|
||||||
name = "dip"
|
name = "dip"
|
||||||
description = "Configurable webhook server."
|
description = "Configurable webhook server."
|
||||||
version = "0.1.4"
|
version = "0.2.0"
|
||||||
authors = ["Michael Zhang <failed.down@gmail.com>"]
|
authors = ["Michael Zhang <mail@mzhang.io>"]
|
||||||
|
edition = "2018"
|
||||||
|
|
||||||
|
[features]
|
||||||
|
github = []
|
||||||
|
default = ["github"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
failure = "0.1"
|
anyhow = "1.0.34"
|
||||||
futures = "0.1"
|
futures = "0.3.7"
|
||||||
generic-array = "0.9"
|
generic-array = "0.14.4"
|
||||||
hmac = "0.6"
|
hmac = "0.10.1"
|
||||||
hyper = "0.12"
|
hyper = { version = "0.13", features = ["stream"] }
|
||||||
mktemp = "0.3"
|
lazy_static = "1.4.0"
|
||||||
lazy_static = "1.1"
|
mktemp = "0.4.0"
|
||||||
notify = "4.0"
|
notify = "4.0.15"
|
||||||
owning_ref = "0.3"
|
owning_ref = "0.4.1"
|
||||||
regex = "1.0"
|
parking_lot = "0.11.0"
|
||||||
secstr = "0.3"
|
regex = "1.4.2"
|
||||||
serde = "1.0"
|
secstr = "0.4.0"
|
||||||
serde_derive = "1.0"
|
serde = "1.0.117"
|
||||||
serde_json = "1.0"
|
serde_derive = "1.0.117"
|
||||||
sha-1 = "0.7"
|
serde_json = "1.0.59"
|
||||||
structopt = "0.2"
|
sha-1 = "0.9.1"
|
||||||
tokio = "0.1"
|
structopt = "0.3.20"
|
||||||
tokio-process = "0.2"
|
tokio = { version = "0.3", features = ["full"] }
|
||||||
toml = "0.4"
|
toml = "0.5.7"
|
||||||
walkdir = "2.2"
|
walkdir = "2.3.1"
|
||||||
|
|
||||||
|
|
13
src/builtins/github.rs
Normal file
13
src/builtins/github.rs
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
use serde_json::Value;
|
||||||
|
use anyhow::Result;
|
||||||
|
|
||||||
|
use super::Builtin;
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct Github;
|
||||||
|
|
||||||
|
impl Builtin for Github {
|
||||||
|
fn handle(&self, env: (), config: (), input: ()) -> Result<Value> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
10
src/builtins/mod.rs
Normal file
10
src/builtins/mod.rs
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
mod github;
|
||||||
|
|
||||||
|
use serde_json::Value;
|
||||||
|
use anyhow::Result;
|
||||||
|
|
||||||
|
pub use self::github::Github;
|
||||||
|
|
||||||
|
pub trait Builtin {
|
||||||
|
fn handle(&self, env: (), config: (), input: ()) -> Result<Value>;
|
||||||
|
}
|
|
@ -6,7 +6,7 @@ use std::path::{Path, PathBuf};
|
||||||
use std::sync::mpsc;
|
use std::sync::mpsc;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use failure::{err_msg, Error};
|
use anyhow::Result;
|
||||||
use notify::{self, RecommendedWatcher, RecursiveMode, Watcher};
|
use notify::{self, RecommendedWatcher, RecursiveMode, Watcher};
|
||||||
use walkdir::WalkDir;
|
use walkdir::WalkDir;
|
||||||
|
|
||||||
|
@ -19,10 +19,12 @@ pub struct Config {
|
||||||
/// The root configuration directory for dip. This argument is required.
|
/// The root configuration directory for dip. This argument is required.
|
||||||
#[structopt(short = "d", long = "root", parse(from_os_str))]
|
#[structopt(short = "d", long = "root", parse(from_os_str))]
|
||||||
pub root: PathBuf,
|
pub root: PathBuf,
|
||||||
|
|
||||||
/// A string containing the address to bind to. This defaults to
|
/// A string containing the address to bind to. This defaults to
|
||||||
/// "0.0.0.0:5000".
|
/// "0.0.0.0:5000".
|
||||||
#[structopt(short = "b", long = "bind", default_value = "0.0.0.0:5000")]
|
#[structopt(short = "b", long = "bind", default_value = "0.0.0.0:5000")]
|
||||||
pub bind: String,
|
pub bind: String,
|
||||||
|
|
||||||
/// If a hook is specified here, it will be triggered manually exactly once
|
/// 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.
|
/// and then the program will exit rather than running as a server.
|
||||||
#[structopt(short = "h", long = "hook")]
|
#[structopt(short = "h", long = "hook")]
|
||||||
|
@ -69,13 +71,7 @@ where
|
||||||
println!("Reloading config...");
|
println!("Reloading config...");
|
||||||
// hold on to the lock while config is being reloaded
|
// hold on to the lock while config is being reloaded
|
||||||
{
|
{
|
||||||
let mut programs = match PROGRAMS.lock() {
|
let mut programs = PROGRAMS.lock();
|
||||||
Ok(programs) => programs,
|
|
||||||
Err(err) => {
|
|
||||||
eprintln!("Could not acquire programs lock: {}", err);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
// TODO: some kind of smart diff
|
// TODO: some kind of smart diff
|
||||||
programs.clear();
|
programs.clear();
|
||||||
|
|
||||||
|
@ -96,7 +92,7 @@ where
|
||||||
match path
|
match path
|
||||||
.file_name()
|
.file_name()
|
||||||
.and_then(|s| s.to_str())
|
.and_then(|s| s.to_str())
|
||||||
.ok_or(err_msg("???"))
|
.ok_or(anyhow!("???"))
|
||||||
.map(|s| {
|
.map(|s| {
|
||||||
let filename = s.to_owned();
|
let filename = s.to_owned();
|
||||||
programs.insert(filename, path.to_path_buf())
|
programs.insert(filename, path.to_path_buf())
|
||||||
|
@ -106,14 +102,9 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
let mut hooks = match HOOKS.lock() {
|
let mut hooks = HOOKS.lock();
|
||||||
Ok(hooks) => hooks,
|
|
||||||
Err(err) => {
|
|
||||||
eprintln!("Could not acquire hooks lock: {}", err);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
hooks.clear();
|
hooks.clear();
|
||||||
let hooks_dir = {
|
let hooks_dir = {
|
||||||
let mut p = root.as_ref().to_path_buf();
|
let mut p = root.as_ref().to_path_buf();
|
||||||
|
@ -129,7 +120,7 @@ where
|
||||||
if !path.is_file() {
|
if !path.is_file() {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
match (|path: &Path| -> Result<(), Error> {
|
match (|path: &Path| -> Result<()> {
|
||||||
let hook = Hook::from_file(path)?;
|
let hook = Hook::from_file(path)?;
|
||||||
let name = hook.get_name();
|
let name = hook.get_name();
|
||||||
hooks.insert(name, hook);
|
hooks.insert(name, hook);
|
||||||
|
|
|
@ -3,9 +3,9 @@ use std::iter::FromIterator;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
|
|
||||||
use failure::{err_msg, Error};
|
use anyhow::Result;
|
||||||
use generic_array::GenericArray;
|
use generic_array::GenericArray;
|
||||||
use hmac::{Hmac, Mac};
|
use hmac::{Hmac, Mac, NewMac};
|
||||||
use secstr::*;
|
use secstr::*;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use serde_json::{self, Serializer as JsonSerializer, Value as JsonValue};
|
use serde_json::{self, Serializer as JsonSerializer, Value as JsonValue};
|
||||||
|
@ -55,7 +55,7 @@ pub(crate) fn main(
|
||||||
env: &Environment,
|
env: &Environment,
|
||||||
config: &TomlValue,
|
config: &TomlValue,
|
||||||
input: &JsonValue,
|
input: &JsonValue,
|
||||||
) -> Result<JsonValue, Error> {
|
) -> Result<JsonValue> {
|
||||||
let config_str = {
|
let config_str = {
|
||||||
let mut buf: Vec<u8> = Vec::new();
|
let mut buf: Vec<u8> = Vec::new();
|
||||||
{
|
{
|
||||||
|
@ -72,10 +72,10 @@ pub(crate) fn main(
|
||||||
if !config.disable_hmac_verify {
|
if !config.disable_hmac_verify {
|
||||||
let secret = GenericArray::from_iter(config.secret.bytes());
|
let secret = GenericArray::from_iter(config.secret.bytes());
|
||||||
let mut mac = Hmac::<Sha1>::new(&secret);
|
let mut mac = Hmac::<Sha1>::new(&secret);
|
||||||
mac.input(payload.body.as_bytes());
|
mac.update(payload.body.as_bytes());
|
||||||
let signature = mac
|
let signature = mac
|
||||||
.result()
|
.finalize()
|
||||||
.code()
|
.into_bytes()
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|b| format!("{:02x}", b))
|
.map(|b| format!("{:02x}", b))
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
|
@ -84,7 +84,7 @@ pub(crate) fn main(
|
||||||
let auth = payload
|
let auth = payload
|
||||||
.headers
|
.headers
|
||||||
.get("x-hub-signature")
|
.get("x-hub-signature")
|
||||||
.ok_or(err_msg("Missing auth header"))
|
.ok_or(anyhow!("Missing auth header"))
|
||||||
.expect("Missing auth header");
|
.expect("Missing auth header");
|
||||||
|
|
||||||
let left = SecStr::from(format!("sha1={}", signature));
|
let left = SecStr::from(format!("sha1={}", signature));
|
||||||
|
|
149
src/handler.rs
149
src/handler.rs
|
@ -1,36 +1,31 @@
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::process::{Command, Stdio};
|
use std::sync::Arc;
|
||||||
|
use std::process::Stdio;
|
||||||
|
|
||||||
use failure::{err_msg, Error};
|
use anyhow::Result;
|
||||||
use futures::{
|
|
||||||
future::{self, Either},
|
|
||||||
Future,
|
|
||||||
};
|
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use serde_json::{Serializer as JsonSerializer, Value as JsonValue};
|
use serde_json::{Serializer as JsonSerializer, Value as JsonValue};
|
||||||
use tokio::io::write_all;
|
use tokio::{io::AsyncWriteExt, process::Command};
|
||||||
use tokio_process::CommandExt;
|
|
||||||
use toml::Value as TomlValue;
|
use toml::Value as TomlValue;
|
||||||
|
|
||||||
use crate::github;
|
use crate::builtins::{Builtin, Github};
|
||||||
|
|
||||||
/// A single instance of handler as defined by the config.
|
/// A single instance of handler as defined by the config.
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
pub struct Handler {
|
pub struct Handler {
|
||||||
pub(crate) config: TomlValue,
|
pub(crate) config: TomlValue,
|
||||||
pub(crate) action: Action,
|
pub(crate) action: Action,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Describes an action that a hook can take.
|
/// Describes an action that a hook can take.
|
||||||
#[derive(Clone)]
|
|
||||||
pub enum Action {
|
pub enum Action {
|
||||||
/// A builtin function (for example, the Github handler).
|
/// A builtin function (for example, the Github handler).
|
||||||
Builtin(
|
Builtin(Box<dyn Builtin + Send>),
|
||||||
fn(&Environment, &TomlValue, &JsonValue) -> Result<JsonValue, Error>,
|
// Fn(&Environment, &TomlValue, &JsonValue) -> Result<JsonValue>>),
|
||||||
),
|
|
||||||
/// A command represents a string to be executed by `bash -c`.
|
/// A command represents a string to be executed by `bash -c`.
|
||||||
Command(String),
|
Command(String),
|
||||||
|
|
||||||
/// A program represents one of the handlers specified in the `handlers`
|
/// A program represents one of the handlers specified in the `handlers`
|
||||||
/// directory.
|
/// directory.
|
||||||
Program(String),
|
Program(String),
|
||||||
|
@ -53,22 +48,23 @@ impl fmt::Debug for Action {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Handler {
|
impl Handler {
|
||||||
pub(crate) fn from(config: &TomlValue) -> Result<Self, Error> {
|
pub(crate) fn from(config: &TomlValue) -> Result<Self> {
|
||||||
let handler = config
|
let handler = config
|
||||||
.get("type")
|
.get("type")
|
||||||
.ok_or(err_msg("No 'type' found."))?
|
.ok_or(anyhow!("No 'type' found."))?
|
||||||
.as_str()
|
.as_str()
|
||||||
.ok_or(err_msg("'type' is not a string."))?;
|
.ok_or(anyhow!("'type' is not a string."))?;
|
||||||
|
|
||||||
let action = match handler {
|
let action = match handler {
|
||||||
"command" => {
|
"command" => {
|
||||||
let command = config
|
let command = config
|
||||||
.get("command")
|
.get("command")
|
||||||
.ok_or(err_msg("No 'command' found"))?
|
.ok_or(anyhow!("No 'command' found"))?
|
||||||
.as_str()
|
.as_str()
|
||||||
.ok_or(err_msg("'command' is not a string."))?;
|
.ok_or(anyhow!("'command' is not a string."))?;
|
||||||
Action::Command(command.to_owned())
|
Action::Command(command.to_owned())
|
||||||
}
|
}
|
||||||
"github" => Action::Builtin(github::main),
|
"github" => Action::Builtin(Box::new(Github::default())),
|
||||||
handler => Action::Program(handler.to_owned()),
|
handler => Action::Program(handler.to_owned()),
|
||||||
};
|
};
|
||||||
let config = config.clone();
|
let config = config.clone();
|
||||||
|
@ -76,12 +72,12 @@ impl Handler {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Runs the given [action](Action) and produces a [Future](Future).
|
/// Runs the given [action](Action) and produces a [Future](Future).
|
||||||
pub fn run(
|
pub async fn run(
|
||||||
config: TomlValue,
|
config: TomlValue,
|
||||||
action: Action,
|
action: Action,
|
||||||
temp_path: PathBuf,
|
temp_path: PathBuf,
|
||||||
input: JsonValue,
|
input: JsonValue,
|
||||||
) -> impl Future<Item = (PathBuf, JsonValue), Error = Error> {
|
) -> Result<(PathBuf, JsonValue)> {
|
||||||
let temp_path_cp = temp_path.clone();
|
let temp_path_cp = temp_path.clone();
|
||||||
let config_str = {
|
let config_str = {
|
||||||
let mut buf: Vec<u8> = Vec::new();
|
let mut buf: Vec<u8> = Vec::new();
|
||||||
|
@ -102,72 +98,57 @@ impl Handler {
|
||||||
.stderr(Stdio::piped());
|
.stderr(Stdio::piped());
|
||||||
};
|
};
|
||||||
|
|
||||||
let output: Box<dyn Future<Item = JsonValue, Error = Error> + Send> =
|
let output = match action {
|
||||||
match action {
|
Action::Builtin(ref builtin) => {
|
||||||
Action::Builtin(ref func) => {
|
let workdir = temp_path_cp.clone();
|
||||||
let workdir = temp_path_cp.clone();
|
let env = Environment { workdir };
|
||||||
let env = Environment { workdir };
|
builtin.handle(&env, &config, &input)
|
||||||
let result = func(&env, &config, &input);
|
}
|
||||||
Box::new(future::result(result))
|
Action::Command(ref cmd) => {
|
||||||
}
|
// TODO: allow some kind of simple variable replacement
|
||||||
Action::Command(ref cmd) => {
|
let mut command = Command::new("/bin/bash");
|
||||||
// TODO: allow some kind of simple variable replacement
|
command_helper(&mut command);
|
||||||
let mut command = Command::new("/bin/bash");
|
let child = command.arg("-c").arg(cmd);
|
||||||
command_helper(&mut command);
|
let output = child.output().await?;
|
||||||
let child = command.arg("-c").arg(cmd);
|
|
||||||
let result = child
|
|
||||||
.output_async()
|
|
||||||
.map_err(|err| {
|
|
||||||
err_msg(format!("failed to spawn child: {}", err))
|
|
||||||
})
|
|
||||||
.and_then(|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());
|
|
||||||
future::ok(json!({
|
|
||||||
"stdout": stdout,
|
|
||||||
"stderr": stderr,
|
|
||||||
}))
|
|
||||||
});
|
|
||||||
Box::new(result)
|
|
||||||
}
|
|
||||||
Action::Program(ref path) => {
|
|
||||||
let mut command = Command::new(&path);
|
|
||||||
command_helper(&mut command);
|
|
||||||
let mut child = command
|
|
||||||
.arg("--config")
|
|
||||||
.arg(config_str)
|
|
||||||
.spawn_async()
|
|
||||||
.expect("could not spawn child");
|
|
||||||
|
|
||||||
let stdin = child.stdin().take().unwrap();
|
let stdout = String::from_utf8(output.stdout)?;
|
||||||
|
let stderr = String::from_utf8(output.stderr)?;
|
||||||
|
|
||||||
let input = format!("{}", input);
|
Ok(json!({
|
||||||
let result = write_all(stdin, input)
|
"stdout": stdout,
|
||||||
.and_then(|_| child.wait_with_output())
|
"stderr": stderr,
|
||||||
.map_err(|err| err_msg(format!("error: {}", err)))
|
}))
|
||||||
.and_then(|output| {
|
}
|
||||||
let stdout = String::from_utf8(output.stdout)
|
Action::Program(ref path) => {
|
||||||
.unwrap_or_else(|_| String::new());
|
let mut command = Command::new(&path);
|
||||||
let stderr = String::from_utf8(output.stderr)
|
command_helper(&mut command);
|
||||||
.unwrap_or_else(|_| String::new());
|
let mut child =
|
||||||
if output.status.success() {
|
command.arg("--config").arg(config_str).spawn()?;
|
||||||
Either::A(future::ok(json!({
|
let mut stdin = child.stdin.take().unwrap();
|
||||||
"stdout": stdout,
|
|
||||||
"stderr": stderr,
|
|
||||||
})))
|
|
||||||
} else {
|
|
||||||
Either::B(future::err(err_msg(format!(
|
|
||||||
"Failed, stdout: '{}', stderr: '{}'",
|
|
||||||
stdout, stderr
|
|
||||||
))))
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
Box::new(result)
|
let input = format!("{}", input);
|
||||||
|
stdin.write_all(input.as_bytes()).await?;
|
||||||
|
|
||||||
|
let output = child.wait_with_output().await?;
|
||||||
|
|
||||||
|
let stdout = String::from_utf8(output.stdout)?;
|
||||||
|
let stderr = String::from_utf8(output.stderr)?;
|
||||||
|
|
||||||
|
if output.status.success() {
|
||||||
|
Ok(json!({
|
||||||
|
"stdout": stdout,
|
||||||
|
"stderr": stderr,
|
||||||
|
}))
|
||||||
|
} else {
|
||||||
|
Err(anyhow!(
|
||||||
|
"Failed, stdout: '{}', stderr: '{}'",
|
||||||
|
stdout,
|
||||||
|
stderr
|
||||||
|
))
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
};
|
||||||
|
|
||||||
output.map(|x| (temp_path_cp, x))
|
output.map(|x| (temp_path_cp, x))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
43
src/hook.rs
43
src/hook.rs
|
@ -4,10 +4,8 @@ use std::fs::File;
|
||||||
use std::io::Read;
|
use std::io::Read;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
use failure::{err_msg, Error};
|
use anyhow::Result;
|
||||||
use futures::{stream, Future};
|
|
||||||
use serde_json::Value as JsonValue;
|
use serde_json::Value as JsonValue;
|
||||||
use tokio::{self, prelude::*};
|
|
||||||
use toml::Value;
|
use toml::Value;
|
||||||
|
|
||||||
use crate::Handler;
|
use crate::Handler;
|
||||||
|
@ -20,16 +18,13 @@ pub struct Hook {
|
||||||
|
|
||||||
impl Hook {
|
impl Hook {
|
||||||
/// Creates a hook from a (name, config) pair.
|
/// Creates a hook from a (name, config) pair.
|
||||||
pub fn from(
|
pub fn from(name: impl Into<String>, config: &Value) -> Result<Self> {
|
||||||
name: impl Into<String>,
|
|
||||||
config: &Value,
|
|
||||||
) -> Result<Self, Error> {
|
|
||||||
let name = name.into();
|
let name = name.into();
|
||||||
let handlers = config
|
let handlers = config
|
||||||
.get("handlers")
|
.get("handlers")
|
||||||
.ok_or(err_msg("No 'handlers' found."))?
|
.ok_or(anyhow!("No 'handlers' found."))?
|
||||||
.as_array()
|
.as_array()
|
||||||
.ok_or(err_msg("'handlers' is not an array."))?
|
.ok_or(anyhow!("'handlers' is not an array."))?
|
||||||
.iter()
|
.iter()
|
||||||
.map(|value: &Value| Handler::from(value))
|
.map(|value: &Value| Handler::from(value))
|
||||||
.collect::<Result<Vec<_>, _>>()?;
|
.collect::<Result<Vec<_>, _>>()?;
|
||||||
|
@ -37,16 +32,16 @@ impl Hook {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a hook from a configuration file.
|
/// Creates a hook from a configuration file.
|
||||||
pub fn from_file<P>(path: P) -> Result<Hook, Error>
|
pub fn from_file<P>(path: P) -> Result<Hook>
|
||||||
where
|
where
|
||||||
P: AsRef<Path>,
|
P: AsRef<Path>,
|
||||||
{
|
{
|
||||||
let filename = path
|
let filename = path
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.file_name()
|
.file_name()
|
||||||
.ok_or(err_msg("what the fuck bro"))?
|
.ok_or(anyhow!("what the fuck bro"))?
|
||||||
.to_str()
|
.to_str()
|
||||||
.ok_or(err_msg("???"))?
|
.ok_or(anyhow!("???"))?
|
||||||
.to_owned();
|
.to_owned();
|
||||||
let mut file = File::open(path)?;
|
let mut file = File::open(path)?;
|
||||||
let mut contents = String::new();
|
let mut contents = String::new();
|
||||||
|
@ -62,25 +57,25 @@ impl Hook {
|
||||||
self.name.clone()
|
self.name.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn handle(
|
pub(crate) async fn handle(
|
||||||
&self,
|
&self,
|
||||||
req: JsonValue,
|
req: JsonValue,
|
||||||
temp_path: PathBuf,
|
temp_path: PathBuf,
|
||||||
) -> Result<String, String> {
|
) -> Result<String> {
|
||||||
let handlers = self
|
let handlers = self
|
||||||
.handlers
|
.handlers
|
||||||
.iter()
|
.iter()
|
||||||
.map(|handler| (handler.config.clone(), handler.action.clone()))
|
.map(|handler| (handler.config, handler.action))
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
let st = stream::iter_ok::<_, Error>(handlers.into_iter())
|
|
||||||
.fold((temp_path, req), |(path, prev), (config, action)| {
|
let mut path = temp_path;
|
||||||
Handler::run(config, action, path, prev)
|
let mut prev = req;
|
||||||
})
|
for (config, action) in handlers {
|
||||||
.map(|_| ())
|
let result = Handler::run(config, action, path, prev).await?;
|
||||||
.map_err(|err: Error| {
|
path = result.0;
|
||||||
println!("Error from stream: {}", err);
|
prev = result.1;
|
||||||
});
|
}
|
||||||
tokio::executor::spawn(st);
|
|
||||||
Ok("success".to_owned())
|
Ok("success".to_owned())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
47
src/lib.rs
47
src/lib.rs
|
@ -6,31 +6,18 @@ The configurable webhook server. Latest stable binary releases for Linux are ava
|
||||||
|
|
||||||
#![deny(missing_docs)]
|
#![deny(missing_docs)]
|
||||||
|
|
||||||
extern crate hmac;
|
#[macro_use]
|
||||||
extern crate secstr;
|
extern crate anyhow;
|
||||||
extern crate sha1;
|
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate serde_derive;
|
extern crate serde_derive;
|
||||||
extern crate failure;
|
|
||||||
extern crate futures;
|
|
||||||
extern crate generic_array;
|
|
||||||
extern crate hyper;
|
|
||||||
extern crate mktemp;
|
|
||||||
extern crate owning_ref;
|
|
||||||
extern crate serde;
|
|
||||||
extern crate tokio;
|
|
||||||
extern crate tokio_process;
|
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate serde_json;
|
extern crate serde_json;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate lazy_static;
|
extern crate lazy_static;
|
||||||
extern crate notify;
|
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate structopt;
|
extern crate structopt;
|
||||||
extern crate regex;
|
|
||||||
extern crate toml;
|
|
||||||
extern crate walkdir;
|
|
||||||
|
|
||||||
|
mod builtins;
|
||||||
pub mod config;
|
pub mod config;
|
||||||
mod github;
|
mod github;
|
||||||
mod handler;
|
mod handler;
|
||||||
|
@ -41,13 +28,15 @@ use std::collections::HashMap;
|
||||||
use std::net::SocketAddrV4;
|
use std::net::SocketAddrV4;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::Arc;
|
||||||
use std::thread;
|
use std::thread;
|
||||||
|
|
||||||
use failure::Error;
|
use anyhow::{Error, Result};
|
||||||
use hyper::rt::Future;
|
use hyper::{
|
||||||
use hyper::service::service_fn;
|
service::{make_service_fn, service_fn},
|
||||||
use hyper::Server;
|
Server,
|
||||||
|
};
|
||||||
|
use parking_lot::Mutex;
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
|
|
||||||
pub use crate::config::Config;
|
pub use crate::config::Config;
|
||||||
|
@ -67,17 +56,23 @@ lazy_static! {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Main entry point of the entire application.
|
/// Main entry point of the entire application.
|
||||||
pub fn run(config: &Config) -> Result<(), Error> {
|
pub async fn run(config: &Config) -> Result<()> {
|
||||||
config::load_config(&config.root);
|
config::load_config(&config.root);
|
||||||
|
|
||||||
let v = config.root.clone();
|
let v = config.root.clone();
|
||||||
thread::spawn(|| config::watch(v));
|
thread::spawn(|| config::watch(v));
|
||||||
|
|
||||||
let addr: SocketAddrV4 = SocketAddrV4::from_str(config.bind.as_ref())?;
|
let addr: SocketAddrV4 = SocketAddrV4::from_str(config.bind.as_ref())?;
|
||||||
let server = Server::bind(&addr.into())
|
let make_svc = make_service_fn(|conn| async {
|
||||||
.serve(|| service_fn(dip_service))
|
Ok::<_, Error>(service_fn(dip_service))
|
||||||
.map_err(|e| eprintln!("server error: {}", e));
|
});
|
||||||
|
|
||||||
|
let server = Server::bind(&addr.into()).serve(make_svc);
|
||||||
println!("Listening on {:?}", addr);
|
println!("Listening on {:?}", addr);
|
||||||
hyper::rt::run(server);
|
|
||||||
|
if let Err(e) = server.await {
|
||||||
|
eprintln!("server error: {}", e);
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,9 @@
|
||||||
extern crate dip;
|
use anyhow::Result;
|
||||||
extern crate failure;
|
|
||||||
extern crate structopt;
|
|
||||||
|
|
||||||
use dip::Config;
|
use dip::Config;
|
||||||
use failure::Error;
|
|
||||||
use structopt::StructOpt;
|
use structopt::StructOpt;
|
||||||
|
|
||||||
fn main() -> Result<(), Error> {
|
#[tokio::main]
|
||||||
|
async fn main() -> Result<()> {
|
||||||
let config = Config::from_args();
|
let config = Config::from_args();
|
||||||
dip::run(&config)
|
dip::run(&config)
|
||||||
}
|
}
|
||||||
|
|
107
src/service.rs
107
src/service.rs
|
@ -1,35 +1,34 @@
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use futures::{future, Future, Stream};
|
use anyhow::Result;
|
||||||
use hyper::{Body, Error, Request, Response, StatusCode};
|
use futures::stream::TryStreamExt;
|
||||||
|
use hyper::{
|
||||||
|
body::{to_bytes, Body},
|
||||||
|
Request, Response, StatusCode,
|
||||||
|
};
|
||||||
use mktemp::Temp;
|
use mktemp::Temp;
|
||||||
|
|
||||||
use crate::{HOOKS, URIPATTERN};
|
use crate::{HOOKS, URIPATTERN};
|
||||||
|
|
||||||
pub(crate) fn dip_service(
|
pub(crate) async fn dip_service(req: Request<Body>) -> Result<Response<Body>> {
|
||||||
req: Request<Body>,
|
|
||||||
) -> Box<dyn Future<Item = Response<Body>, Error = Error> + Send> {
|
|
||||||
let path = req.uri().path().to_owned();
|
let path = req.uri().path().to_owned();
|
||||||
let captures = match URIPATTERN.captures(path.as_ref()) {
|
let captures = match URIPATTERN.captures(path.as_ref()) {
|
||||||
Some(value) => value,
|
Some(value) => value,
|
||||||
None => {
|
None => {
|
||||||
return Box::new(future::ok(
|
return Ok(Response::builder()
|
||||||
Response::builder()
|
.status(StatusCode::NOT_FOUND)
|
||||||
.status(StatusCode::NOT_FOUND)
|
.body(Body::from("not found"))
|
||||||
.body(Body::from("not found"))
|
.unwrap());
|
||||||
.unwrap(),
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let name = match captures.name("name") {
|
let name = match captures.name("name") {
|
||||||
Some(value) => value.as_str().to_owned(),
|
Some(value) => value.as_str().to_owned(),
|
||||||
None => {
|
None => {
|
||||||
return Box::new(future::ok(
|
return Ok(Response::builder()
|
||||||
Response::builder()
|
.status(StatusCode::NOT_FOUND)
|
||||||
.status(StatusCode::NOT_FOUND)
|
.body(Body::from("not found"))
|
||||||
.body(Body::from("not found"))
|
.unwrap());
|
||||||
.unwrap(),
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -47,43 +46,45 @@ pub(crate) fn dip_service(
|
||||||
let method = req.method().as_str().to_owned();
|
let method = req.method().as_str().to_owned();
|
||||||
|
|
||||||
// spawn job
|
// spawn job
|
||||||
Box::new(req.into_body().concat2().map(move |body| {
|
let body = to_bytes(req.into_body()).await?;
|
||||||
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());
|
|
||||||
|
|
||||||
let hook = match hooks.get(&name) {
|
let body = String::from_utf8(body.to_vec()).unwrap();
|
||||||
Some(hook) => hook,
|
let req_obj = json!({
|
||||||
None => {
|
"body": body,
|
||||||
temp_dir.release();
|
"headers": headers,
|
||||||
return Response::builder()
|
"method": method,
|
||||||
.status(StatusCode::NOT_FOUND)
|
});
|
||||||
.body(Body::from("not found"))
|
|
||||||
.unwrap_or_else(|_| {
|
|
||||||
Response::new(Body::from("not found"))
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let (code, msg) = match hook.handle(req_obj, temp_path) {
|
|
||||||
Ok(msg) => (StatusCode::ACCEPTED, msg),
|
|
||||||
Err(msg) => (StatusCode::BAD_REQUEST, msg),
|
|
||||||
};
|
|
||||||
|
|
||||||
temp_dir.release();
|
let mut temp_dir = Temp::new_dir().unwrap();
|
||||||
Response::builder()
|
let temp_path = temp_dir.to_path_buf();
|
||||||
.status(code)
|
assert!(temp_path.exists());
|
||||||
.body(Body::from(msg))
|
|
||||||
.unwrap_or_else(|err| {
|
let (code, msg) = {
|
||||||
Response::new(Body::from(format!("{}", err)))
|
let hooks = HOOKS.lock();
|
||||||
})
|
|
||||||
|
let hook = match hooks.get(&name) {
|
||||||
|
Some(hook) => hook,
|
||||||
|
None => {
|
||||||
|
temp_dir.release();
|
||||||
|
return Ok(Response::builder()
|
||||||
|
.status(StatusCode::NOT_FOUND)
|
||||||
|
.body(Body::from("not found"))
|
||||||
|
.unwrap_or_else(|_| {
|
||||||
|
Response::new(Body::from("not found"))
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
match hook.handle(req_obj, temp_path).await {
|
||||||
|
Ok(msg) => (StatusCode::ACCEPTED, msg),
|
||||||
|
Err(msg) => (StatusCode::BAD_REQUEST, msg.to_string()),
|
||||||
}
|
}
|
||||||
}))
|
};
|
||||||
|
|
||||||
|
temp_dir.release();
|
||||||
|
|
||||||
|
Ok(Response::builder()
|
||||||
|
.status(code)
|
||||||
|
.body(Body::from(msg))
|
||||||
|
.unwrap_or_else(|err| Response::new(Body::from(format!("{}", err)))))
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue