Compare commits
1 commit
master
...
fuck-rust-
Author | SHA1 | Date | |
---|---|---|---|
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