diff --git a/.travis.yml b/.travis.yml index 5f9ff1f..f33b033 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,28 +1,27 @@ language: rust -sudo: false +sudo: required rust: - stable - - beta - nightly os: linux matrix: fast_finish: true +services: + - docker cache: cargo script: - cargo test --all before_deploy: - - cargo build --release --all - - cargo build --release --examples + - ./ci/build-release.sh dip ${TRAVIS_TAG}-${TRAVIS_OS_NAME} deploy: - provider: releases api_key: $AUTH_TOKEN file: - - target/release/dip - - target/release/examples/github + - dip-* on: condition: $TRAVIS_RUST_VERSION = stable tags: true diff --git a/Cargo.toml b/Cargo.toml index f0b6e5c..1142570 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,26 +4,22 @@ description = "Configurable webhook server." version = "0.1.0" authors = ["Michael Zhang "] -[[example]] -name = "github" -[dev_dependencies] -generic-array = "0.9" -hmac = "0.6" -secstr = "0.3" -sha-1 = "0.7" - [dependencies] failure = "0.1" futures = "0.1" +generic-array = "0.9" +hmac = "0.6" hyper = "0.12" mktemp = "0.3" lazy_static = "1.1" notify = "4.0" owning_ref = "0.3" regex = "1.0" +secstr = "0.3" serde = "1.0" serde_derive = "1.0" serde_json = "1.0" +sha-1 = "0.7" structopt = "0.2" tokio = "0.1" tokio-process = "0.2" diff --git a/ci/build-release.sh b/ci/build-release.sh new file mode 100644 index 0000000..9eef107 --- /dev/null +++ b/ci/build-release.sh @@ -0,0 +1,34 @@ +#!/bin/bash +# +# Usage: ./build-release ${TRAVIS_TAG}-${TRAVIS_OS_NAME} +# +# The latest version of this script is available at +# https://github.com/emk/rust-musl-builder/blob/master/examples/build-release +# +# Called by `.travis.yml` to build release binaries. We use +# ekidd/rust-musl-builder to make the Linux binaries so that we can run +# them unchanged on any distro, including tiny distros like Alpine (which +# is heavily used for Docker containers). Other platforms get regular +# binaries, which will generally be dynamically linked against libc. +# +# If you have a platform which supports static linking of libc, and this +# would be generally useful, please feel free to submit patches. + +set -euo pipefail + +case `uname -s` in + Linux) + echo "Building static binaries using ekidd/rust-musl-builder" + docker build -t build-"$1"-image . + docker run -it --name build-"$1" build-"$1"-image + docker cp build-"$1":/home/rust/src/target/x86_64-unknown-linux-musl/release/"$1" "$1" + docker rm build-"$1" + docker rmi build-"$1"-image + zip "$1"-"$2".zip "$1" + ;; + *) + echo "Building standard release binaries" + cargo build --release + zip -j "$1"-"$2".zip target/release/"$1" + ;; +esac \ No newline at end of file diff --git a/src/config.rs b/src/config.rs index 1ff28a3..04a2409 100644 --- a/src/config.rs +++ b/src/config.rs @@ -51,6 +51,7 @@ where 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"); diff --git a/examples/github.rs b/src/github.rs similarity index 77% rename from examples/github.rs rename to src/github.rs index 5656e0e..7b763c7 100644 --- a/examples/github.rs +++ b/src/github.rs @@ -1,28 +1,18 @@ -extern crate dip; -extern crate hmac; -extern crate secstr; -extern crate serde_json; -extern crate sha1; -#[macro_use] -extern crate serde_derive; -extern crate failure; -extern crate generic_array; -#[macro_use] -extern crate structopt; - use std::collections::HashMap; use std::env; -use std::io::{self, Read}; use std::iter::FromIterator; use std::path::PathBuf; use std::process::Command; -use failure::err_msg; +use failure::{err_msg, Error}; use generic_array::GenericArray; use hmac::{Hmac, Mac}; use secstr::*; +use serde::Serialize; +use serde_json::{self, Serializer as JsonSerializer, Value as JsonValue}; use sha1::Sha1; use structopt::StructOpt; +use toml::Value as TomlValue; #[derive(StructOpt)] struct Opt { @@ -60,16 +50,19 @@ fn default_path() -> PathBuf { PathBuf::from(".") } -fn main() { - let args = Opt::from_args(); - let config: Config = serde_json::from_str(&args.config).expect("Could not parse config."); +pub fn main(config: &TomlValue, input: &JsonValue) -> Result { + let config_str = { + let mut buf: Vec = Vec::new(); + { + let mut serializer = JsonSerializer::new(&mut buf); + TomlValue::serialize(&config, &mut serializer).unwrap(); + } + String::from_utf8(buf).unwrap() + }; + let config: Config = serde_json::from_str(&config_str)?; - let mut payload = String::new(); - io::stdin() - .read_to_string(&mut payload) - .expect("Could not read from stdin"); - let payload: Payload = serde_json::from_str(&payload) - .expect(&format!("Could not parse stdin into json: '{}'", payload)); + let payload_str = format!("{}", input); + let payload: Payload = serde_json::from_str(&payload_str)?; if !config.disable_hmac_verify { let secret = GenericArray::from_iter(config.secret.bytes()); @@ -113,4 +106,5 @@ fn main() { .arg(&target_path) .output() .expect("Could not spawn process to clone"); + Ok(json!(1)) } diff --git a/src/handler.rs b/src/handler.rs index b2c110c..49ba14e 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -1,3 +1,4 @@ +use std::fmt; use std::path::PathBuf; use std::process::{Command, Stdio}; @@ -12,7 +13,7 @@ use tokio::io::write_all; use tokio_process::CommandExt; use toml::Value as TomlValue; -use PROGRAMS; +use github; #[derive(Clone, Debug)] pub struct Handler { @@ -20,10 +21,20 @@ pub struct Handler { pub action: Action, } -#[derive(Clone, Debug)] +#[derive(Clone)] pub enum Action { + Builtin(fn(&TomlValue, &JsonValue) -> Result), Command(String), - Exec(PathBuf), + Program(String), +} + +impl fmt::Debug for Action { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match &self { + Action::Builtin(_) => write!(f, "Builtin"), + _ => write!(f, "{:?}", self), + } + } } impl Handler { @@ -45,17 +56,18 @@ impl Handler { .ok_or(err_msg("'command' is not a string."))?; Action::Command(command.to_owned()) } + "github" => Action::Builtin(github::main), 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 programs = HANDLERS.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::Program(handler.to_owned()) } }; let config = config.clone(); @@ -69,7 +81,7 @@ impl Handler { input: JsonValue, ) -> impl Future { let temp_path_cp = temp_path.clone(); - let config = { + let config_str = { let mut buf: Vec = Vec::new(); { let mut serializer = JsonSerializer::new(&mut buf); @@ -85,6 +97,10 @@ impl Handler { }; let output: Box + Send> = match action { + Action::Builtin(ref func) => { + let result = func(&config, &input); + Box::new(future::result(result)) + } Action::Command(ref cmd) => { // TODO: allow some kind of simple variable replacement let mut command = Command::new("/bin/bash"); @@ -111,13 +127,13 @@ impl Handler { }); Box::new(result) } - Action::Exec(ref path) => { + Action::Program(ref path) => { let mut command = Command::new(&path); command_helper(&mut command); let mut child = command .env("DIP_ROOT", "") .arg("--config") - .arg(config) + .arg(config_str) .stdin(Stdio::piped()) .stdout(Stdio::piped()) .stderr(Stdio::piped()) diff --git a/src/lib.rs b/src/lib.rs index 81a1ed4..91ae790 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,7 +1,13 @@ //! # Dip +extern crate hmac; +extern crate secstr; +extern crate sha1; +#[macro_use] +extern crate serde_derive; extern crate failure; extern crate futures; +extern crate generic_array; extern crate hyper; extern crate mktemp; extern crate owning_ref; @@ -20,6 +26,7 @@ extern crate toml; extern crate walkdir; pub mod config; +pub mod github; pub mod handler; pub mod hook; pub mod service; @@ -48,9 +55,8 @@ const URIPATTERN_STR: &str = r"/webhook/(?P[A-Za-z._][A-Za-z0-9._]*)"; lazy_static! { static ref URIPATTERN: Regex = Regex::new(URIPATTERN_STR).unwrap(); - static ref HANDLERS: Arc>> = + static ref PROGRAMS: Arc>> = Arc::new(Mutex::new(HashMap::new())); - static ref PROGRAMS: Mutex> = Mutex::new(HashMap::new()); static ref HOOKS: Arc>> = Arc::new(Mutex::new(HashMap::new())); }