Merge branch 'master' of github:acmumn/dip
This commit is contained in:
commit
9bc9a41cd5
10 changed files with 410 additions and 218 deletions
37
Cargo.lock
generated
37
Cargo.lock
generated
|
@ -192,6 +192,7 @@ dependencies = [
|
||||||
"hmac 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
"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)",
|
"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)",
|
"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)",
|
"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)",
|
"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)",
|
"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)",
|
"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]]
|
[[package]]
|
||||||
name = "net2"
|
name = "net2"
|
||||||
version = "0.2.33"
|
version = "0.2.33"
|
||||||
|
@ -569,6 +578,16 @@ dependencies = [
|
||||||
"proc-macro2 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)",
|
"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]]
|
[[package]]
|
||||||
name = "rand"
|
name = "rand"
|
||||||
version = "0.4.2"
|
version = "0.4.2"
|
||||||
|
@ -617,6 +636,11 @@ name = "rustc-demangle"
|
||||||
version = "0.1.9"
|
version = "0.1.9"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
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]]
|
[[package]]
|
||||||
name = "rustc_version"
|
name = "rustc_version"
|
||||||
version = "0.2.3"
|
version = "0.2.3"
|
||||||
|
@ -960,6 +984,15 @@ name = "utf8-ranges"
|
||||||
version = "1.0.0"
|
version = "1.0.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
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]]
|
[[package]]
|
||||||
name = "vec_map"
|
name = "vec_map"
|
||||||
version = "0.8.1"
|
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 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.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 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 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 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"
|
"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 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 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 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 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_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 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 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 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-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 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 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"
|
"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 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 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 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 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 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"
|
"checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d"
|
||||||
|
|
|
@ -16,6 +16,7 @@ sha-1 = "0.7"
|
||||||
failure = "0.1"
|
failure = "0.1"
|
||||||
futures = "0.1"
|
futures = "0.1"
|
||||||
hyper = "0.12"
|
hyper = "0.12"
|
||||||
|
mktemp = "0.3"
|
||||||
lazy_static = "1.1"
|
lazy_static = "1.1"
|
||||||
notify = "4.0"
|
notify = "4.0"
|
||||||
regex = "1.0"
|
regex = "1.0"
|
||||||
|
|
12
README.md
12
README.md
|
@ -6,7 +6,17 @@ Dip
|
||||||
|
|
||||||
Configurable webhook server.
|
Configurable webhook server.
|
||||||
|
|
||||||
Read documentation at `cargo doc`.
|
Express your webhooks in terms of composable blocks such as:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[[handlers]]
|
||||||
|
type = "github"
|
||||||
|
secret = "hunter2"
|
||||||
|
|
||||||
|
[[handlers]]
|
||||||
|
type = "command"
|
||||||
|
command = "cargo build"
|
||||||
|
```
|
||||||
|
|
||||||
Contact
|
Contact
|
||||||
-------
|
-------
|
||||||
|
|
|
@ -11,9 +11,11 @@ extern crate generic_array;
|
||||||
extern crate structopt;
|
extern crate structopt;
|
||||||
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
use std::env;
|
||||||
use std::io::{self, Read};
|
use std::io::{self, Read};
|
||||||
use std::iter::FromIterator;
|
use std::iter::FromIterator;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
use std::process::Command;
|
||||||
|
|
||||||
use failure::{err_msg, Error};
|
use failure::{err_msg, Error};
|
||||||
use generic_array::GenericArray;
|
use generic_array::GenericArray;
|
||||||
|
@ -29,10 +31,13 @@ struct Opt {
|
||||||
pub config: String,
|
pub config: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
struct Config {
|
struct Config {
|
||||||
secret: String,
|
secret: String,
|
||||||
outdir: PathBuf,
|
#[serde(default)]
|
||||||
|
disable_hmac_verify: bool,
|
||||||
|
#[serde(default = "default_path")]
|
||||||
|
path: PathBuf,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
|
@ -41,36 +46,60 @@ struct Payload {
|
||||||
headers: HashMap<String, String>,
|
headers: HashMap<String, String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
struct RepositoryInfo {
|
||||||
|
clone_url: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
struct GithubPayload {
|
||||||
|
repository: RepositoryInfo,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn default_path() -> PathBuf {
|
||||||
|
PathBuf::from(".")
|
||||||
|
}
|
||||||
|
|
||||||
fn main() -> Result<(), Error> {
|
fn main() -> Result<(), Error> {
|
||||||
let args = Opt::from_args();
|
let args = Opt::from_args();
|
||||||
let config: Config = serde_json::from_str(&args.config)?;
|
let config: Config = serde_json::from_str(&args.config)?;
|
||||||
|
println!("{:?}", config);
|
||||||
|
|
||||||
let mut payload = String::new();
|
let mut payload = String::new();
|
||||||
io::stdin().read_to_string(&mut payload)?;
|
io::stdin().read_to_string(&mut payload)?;
|
||||||
println!("raw payload: {}", payload);
|
|
||||||
let payload: Payload = serde_json::from_str(&payload)?;
|
let payload: Payload = serde_json::from_str(&payload)?;
|
||||||
println!("processed payload: {}", payload.body);
|
|
||||||
|
|
||||||
let secret = GenericArray::from_iter(config.secret.bytes());
|
if !config.disable_hmac_verify {
|
||||||
let mut mac = Hmac::<Sha1>::new(&secret);
|
let secret = GenericArray::from_iter(config.secret.bytes());
|
||||||
mac.input(payload.body.as_bytes());
|
let mut mac = Hmac::<Sha1>::new(&secret);
|
||||||
let signature = mac
|
mac.input(payload.body.as_bytes());
|
||||||
.result()
|
let signature = mac.result()
|
||||||
.code()
|
.code()
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|b| format!("{:02x}", b))
|
.map(|b| format!("{:02x}", b))
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
.join("");
|
.join("");
|
||||||
|
|
||||||
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(err_msg("Missing auth header"))?;
|
||||||
|
|
||||||
let left = SecStr::from(format!("sha1={}", signature));
|
let left = SecStr::from(format!("sha1={}", signature));
|
||||||
let right = SecStr::from(auth.bytes().collect::<Vec<_>>());
|
let right = SecStr::from(auth.bytes().collect::<Vec<_>>());
|
||||||
assert!(left == right, "HMAC signature didn't match");
|
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(&config.path);
|
||||||
|
Command::new("git")
|
||||||
|
.arg("clone")
|
||||||
|
.arg(&payload.repository.clone_url)
|
||||||
|
.arg("--recursive")
|
||||||
|
.arg("--depth")
|
||||||
|
.arg("1")
|
||||||
|
.arg(&target_path)
|
||||||
|
.output()?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,22 @@
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
|
use failure::{err_msg, Error};
|
||||||
|
use walkdir::WalkDir;
|
||||||
|
|
||||||
|
use Hook;
|
||||||
|
use {HOOKS, PROGRAMS};
|
||||||
|
|
||||||
|
#[derive(Debug, StructOpt)]
|
||||||
pub struct Config {
|
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,
|
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,
|
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<String>,
|
pub hook: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -26,3 +40,72 @@ impl Config {
|
||||||
return self;
|
return self;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub 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 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.");
|
||||||
|
}
|
||||||
|
|
135
src/handler.rs
135
src/handler.rs
|
@ -11,27 +11,52 @@ use PROGRAMS;
|
||||||
|
|
||||||
pub struct Handler {
|
pub struct Handler {
|
||||||
config: TomlValue,
|
config: TomlValue,
|
||||||
exec: PathBuf,
|
action: Action,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum Action {
|
||||||
|
Command(String),
|
||||||
|
Exec(PathBuf),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Handler {
|
impl Handler {
|
||||||
|
pub fn config(&self) -> &TomlValue {
|
||||||
|
&self.config
|
||||||
|
}
|
||||||
pub fn from(config: &TomlValue) -> Result<Self, Error> {
|
pub fn from(config: &TomlValue) -> Result<Self, Error> {
|
||||||
let handler = config
|
let handler = config
|
||||||
.get("type")
|
.get("type")
|
||||||
.ok_or(err_msg("No 'type' found."))?
|
.ok_or(err_msg("No 'type' found."))?
|
||||||
.as_str()
|
.as_str()
|
||||||
.ok_or(err_msg("'type' is not a string."))?;
|
.ok_or(err_msg("'type' is not a string."))?;
|
||||||
let exec = {
|
let action = match handler {
|
||||||
let programs = PROGRAMS.lock().unwrap();
|
"command" => {
|
||||||
programs
|
let command = config
|
||||||
.get(handler)
|
.get("command")
|
||||||
.ok_or(err_msg(format!("'{}' is not a valid executable", handler)))
|
.ok_or(err_msg("No 'command' found"))?
|
||||||
.map(|value| value.clone())?
|
.as_str()
|
||||||
|
.ok_or(err_msg("'command' is not a string."))?;
|
||||||
|
Action::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)))
|
||||||
|
.and_then(|value| {
|
||||||
|
value
|
||||||
|
.canonicalize()
|
||||||
|
.map_err(|_| err_msg("failed to canonicalize the path"))
|
||||||
|
})
|
||||||
|
.map(|value| value.clone())?;
|
||||||
|
Action::Exec(program)
|
||||||
|
}
|
||||||
};
|
};
|
||||||
let config = config.clone();
|
let config = config.clone();
|
||||||
Ok(Handler { config, exec })
|
Ok(Handler { config, action })
|
||||||
}
|
}
|
||||||
pub fn run(&self, input: JsonValue) -> Result<JsonValue, Error> {
|
|
||||||
|
pub fn run(&self, temp_path: &PathBuf, input: JsonValue) -> Result<JsonValue, Error> {
|
||||||
let config = {
|
let config = {
|
||||||
let mut buf: Vec<u8> = Vec::new();
|
let mut buf: Vec<u8> = Vec::new();
|
||||||
{
|
{
|
||||||
|
@ -41,33 +66,71 @@ impl Handler {
|
||||||
String::from_utf8(buf).unwrap()
|
String::from_utf8(buf).unwrap()
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut child = Command::new(&self.exec)
|
let output = match &self.action {
|
||||||
.env("DIP_ROOT", "")
|
Action::Command(ref cmd) => {
|
||||||
.arg("--config")
|
// TODO: allow some kind of simple variable replacement
|
||||||
.arg(config)
|
let child = Command::new("/bin/bash")
|
||||||
.stdin(Stdio::piped())
|
.current_dir(&temp_path)
|
||||||
.stdout(Stdio::piped())
|
.env("DIP_ROOT", "lol")
|
||||||
.stderr(Stdio::piped())
|
.env("DIP_WORKDIR", temp_path)
|
||||||
.spawn()?;
|
.arg("-c")
|
||||||
{
|
.arg(cmd)
|
||||||
match child.stdin {
|
.stdin(Stdio::piped())
|
||||||
Some(ref mut stdin) => {
|
.stdout(Stdio::piped())
|
||||||
write!(stdin, "{}", input)?;
|
.stderr(Stdio::piped())
|
||||||
|
.spawn()?;
|
||||||
|
let output = child.wait_with_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"),
|
output
|
||||||
};
|
}
|
||||||
}
|
Action::Exec(ref path) => {
|
||||||
let output = child.wait_with_output()?;
|
let mut child = Command::new(&path)
|
||||||
if !output.status.success() {
|
.current_dir(&temp_path)
|
||||||
// TODO: get rid of unwraps
|
.env("DIP_ROOT", "")
|
||||||
return Err(err_msg(format!(
|
.env("DIP_WORKDIR", temp_path)
|
||||||
"'{:?}' returned with a non-zero status code: {}\nstdout:\n{}\nstderr:\n{}",
|
.arg("--config")
|
||||||
self.exec,
|
.arg(config)
|
||||||
output.status,
|
.stdin(Stdio::piped())
|
||||||
String::from_utf8(output.stdout).unwrap(),
|
.stdout(Stdio::piped())
|
||||||
String::from_utf8(output.stderr).unwrap()
|
.stderr(Stdio::piped())
|
||||||
)));
|
.spawn()?;
|
||||||
}
|
{
|
||||||
Ok(json!({}))
|
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())
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
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,
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
35
src/hook.rs
35
src/hook.rs
|
@ -1,10 +1,11 @@
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::Read;
|
use std::io::Read;
|
||||||
use std::path::Path;
|
use std::path::{Path, PathBuf};
|
||||||
use std::slice::Iter;
|
use std::slice::Iter;
|
||||||
|
|
||||||
use failure::{err_msg, Error};
|
use failure::{err_msg, Error};
|
||||||
use hyper::{Body, Request, Response};
|
use hyper::StatusCode;
|
||||||
|
use serde_json::Value as JsonValue;
|
||||||
use toml::Value;
|
use toml::Value;
|
||||||
|
|
||||||
use Handler;
|
use Handler;
|
||||||
|
@ -31,8 +32,7 @@ impl 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(err_msg("what the fuck bro"))?
|
||||||
.to_str()
|
.to_str()
|
||||||
|
@ -52,7 +52,30 @@ impl Hook {
|
||||||
pub fn iter(&self) -> Iter<Handler> {
|
pub fn iter(&self) -> Iter<Handler> {
|
||||||
self.handlers.iter()
|
self.handlers.iter()
|
||||||
}
|
}
|
||||||
pub fn handle(&self, _payload: &Request<Body>) -> Result<Response<Body>, Error> {
|
pub fn handle(
|
||||||
Ok(Response::new(Body::from("lol")))
|
&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))))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
147
src/lib.rs
147
src/lib.rs
|
@ -2,14 +2,17 @@
|
||||||
|
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate failure;
|
extern crate failure;
|
||||||
extern crate hyper;
|
|
||||||
extern crate futures;
|
extern crate futures;
|
||||||
|
extern crate hyper;
|
||||||
|
extern crate mktemp;
|
||||||
extern crate serde;
|
extern crate serde;
|
||||||
#[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;
|
extern crate notify;
|
||||||
|
#[macro_use]
|
||||||
|
extern crate structopt;
|
||||||
extern crate regex;
|
extern crate regex;
|
||||||
extern crate toml;
|
extern crate toml;
|
||||||
extern crate walkdir;
|
extern crate walkdir;
|
||||||
|
@ -17,156 +20,38 @@ extern crate walkdir;
|
||||||
pub mod config;
|
pub mod config;
|
||||||
pub mod handler;
|
pub mod handler;
|
||||||
pub mod hook;
|
pub mod hook;
|
||||||
|
pub mod service;
|
||||||
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::net::SocketAddrV4;
|
use std::net::SocketAddrV4;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use std::sync::{mpsc, Mutex};
|
use std::sync::{mpsc, Arc, Mutex};
|
||||||
use std::thread;
|
use std::thread;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use failure::{err_msg, Error};
|
use failure::Error;
|
||||||
use hyper::rt::Future;
|
use hyper::rt::Future;
|
||||||
use hyper::service::service_fn_ok;
|
use hyper::service::service_fn;
|
||||||
use hyper::{Body, Request, Response, Server, StatusCode};
|
use hyper::Server;
|
||||||
use notify::{RecommendedWatcher, RecursiveMode, Watcher};
|
use notify::{RecommendedWatcher, RecursiveMode, Watcher};
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
use walkdir::WalkDir;
|
|
||||||
|
|
||||||
pub use config::Config;
|
pub use config::Config;
|
||||||
pub use handler::*;
|
pub use handler::*;
|
||||||
use hook::*;
|
use hook::*;
|
||||||
|
use service::*;
|
||||||
|
|
||||||
const URIPATTERN_STR: &str = r"/webhook/(?P<name>[A-Za-z._][A-Za-z0-9._]*)";
|
const URIPATTERN_STR: &str = r"/webhook/(?P<name>[A-Za-z._][A-Za-z0-9._]*)";
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
static ref URIPATTERN: Regex = Regex::new(URIPATTERN_STR).unwrap();
|
static ref URIPATTERN: Regex = Regex::new(URIPATTERN_STR).unwrap();
|
||||||
static ref HANDLERS: Mutex<HashMap<String, Box<Handler>>> = Mutex::new(HashMap::new());
|
// static ref HANDLERS: Mutex<HashMap<String, Box<Handler>>> = Mutex::new(HashMap::new());
|
||||||
static ref PROGRAMS: Mutex<HashMap<String, PathBuf>> = Mutex::new(HashMap::new());
|
static ref PROGRAMS: Mutex<HashMap<String, PathBuf>> = Mutex::new(HashMap::new());
|
||||||
static ref HOOKS: Mutex<HashMap<String, Hook>> = Mutex::new(HashMap::new());
|
static ref HOOKS: Arc<Mutex<HashMap<String, Hook>>> = Arc::new(Mutex::new(HashMap::new()));
|
||||||
}
|
}
|
||||||
|
|
||||||
const NOTFOUND: &str = "<html> <head> <style> * { font-family: sans-serif; } body { padding: 20px 60px; } </style> </head> <body> <h1>Looks like you took a wrong turn!</h1> <p>There's nothing to see here.</p> </body> </html>";
|
// const NOTFOUND: &str = "<html> <head> <style> * { font-family: sans-serif; } body { padding: 20px 60px; } </style> </head> <body> <h1>Looks like you took a wrong turn!</h1> <p>There's nothing to see here.</p> </body> </html>";
|
||||||
|
|
||||||
fn service_fn(req: Request<Body>) -> Result<Response<Body>, 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::<HashMap<_, _>>();
|
|
||||||
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<Body>) -> Response<Body> {
|
|
||||||
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<P>(root: P)
|
|
||||||
where
|
|
||||||
P: AsRef<Path>,
|
|
||||||
{
|
|
||||||
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),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn watch<P>(root: P) -> notify::Result<()>
|
fn watch<P>(root: P) -> notify::Result<()>
|
||||||
where
|
where
|
||||||
|
@ -181,7 +66,7 @@ where
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
// for now, naively reload entire config every time
|
// for now, naively reload entire config every time
|
||||||
// TODO: don't do this
|
// TODO: don't do this
|
||||||
load_config(root.as_ref())
|
config::load_config(root.as_ref())
|
||||||
}
|
}
|
||||||
Err(e) => println!("watch error: {:?}", e),
|
Err(e) => println!("watch error: {:?}", e),
|
||||||
}
|
}
|
||||||
|
@ -190,14 +75,14 @@ where
|
||||||
|
|
||||||
/// Main entry point of the entire application.
|
/// Main entry point of the entire application.
|
||||||
pub fn run(config: &Config) -> Result<(), Error> {
|
pub fn run(config: &Config) -> Result<(), Error> {
|
||||||
load_config(&config.root);
|
config::load_config(&config.root);
|
||||||
|
|
||||||
let v = config.root.clone();
|
let v = config.root.clone();
|
||||||
thread::spawn(|| watch(v));
|
thread::spawn(|| 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 server = Server::bind(&addr.into())
|
||||||
.serve(|| service_fn_ok(service_fn_wrapper))
|
.serve(|| service_fn(dip_service))
|
||||||
.map_err(|e| eprintln!("server error: {}", e));
|
.map_err(|e| eprintln!("server error: {}", e));
|
||||||
println!("Listening on {:?}", addr);
|
println!("Listening on {:?}", addr);
|
||||||
hyper::rt::run(server);
|
hyper::rt::run(server);
|
||||||
|
|
24
src/main.rs
24
src/main.rs
|
@ -1,32 +1,12 @@
|
||||||
#[macro_use]
|
|
||||||
extern crate structopt;
|
|
||||||
extern crate dip;
|
extern crate dip;
|
||||||
extern crate failure;
|
extern crate failure;
|
||||||
|
extern crate structopt;
|
||||||
use std::path::PathBuf;
|
|
||||||
|
|
||||||
use dip::Config;
|
use dip::Config;
|
||||||
use failure::Error;
|
use failure::Error;
|
||||||
use structopt::StructOpt;
|
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<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")]
|
|
||||||
hook: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() -> Result<(), Error> {
|
fn main() -> Result<(), Error> {
|
||||||
let opt = Opt::from_args();
|
let config = Config::from_args();
|
||||||
println!("{:?}", opt);
|
|
||||||
|
|
||||||
let config = Config::new(opt.root).bind(opt.bind).hook(opt.hook);
|
|
||||||
dip::run(&config)
|
dip::run(&config)
|
||||||
}
|
}
|
||||||
|
|
81
src/service.rs
Normal file
81
src/service.rs
Normal file
|
@ -0,0 +1,81 @@
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::thread;
|
||||||
|
|
||||||
|
use futures::{future, Future, Stream};
|
||||||
|
use hyper::{Body, Error, Request, Response, StatusCode};
|
||||||
|
use mktemp::Temp;
|
||||||
|
|
||||||
|
use {HOOKS, URIPATTERN};
|
||||||
|
|
||||||
|
pub fn dip_service(req: Request<Body>) -> Box<Future<Item = Response<Body>, Error = Error> + Send> {
|
||||||
|
let path = req.uri().path().to_owned();
|
||||||
|
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(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
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()
|
||||||
|
})
|
||||||
|
.collect::<HashMap<_, _>>();
|
||||||
|
let method = req.method().as_str().to_owned();
|
||||||
|
|
||||||
|
// 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());
|
||||||
|
|
||||||
|
let hook = hooks.get(&name).unwrap();
|
||||||
|
let (code, msg) = hook.handle(req_obj, &temp_path).unwrap();
|
||||||
|
|
||||||
|
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::ACCEPTED)
|
||||||
|
// TODO: assign job a uuid and do some logging
|
||||||
|
.body(Body::from(format!("job {} started", -1)))
|
||||||
|
.unwrap(),
|
||||||
|
))
|
||||||
|
}
|
Loading…
Reference in a new issue