extern crate git2; extern crate iron; extern crate mount; extern crate serde_json; extern crate staticfile; // This example serves the docs from target/doc/staticfile at /doc/ // // Run `cargo doc && cargo run --example doc_server`, then // point your browser to http://127.0.0.1:3000/doc/ use std::path::{Path, PathBuf}; use std::error::Error; use iron::prelude::{Chain, Request, Response}; use iron::{status, Iron, IronResult}; use mount::Mount; use staticfile::Static; use git2::Repository; struct WebHookConfig { secret: String, repo_root: PathBuf, repo_prefixes: Vec, } fn redeploy_repo(url: &str, path: &Path, name: &str) -> Result<(), git2::Error> { let mut new_path = path.to_path_buf(); new_path.push(name); eprintln!("Cloning {} into {:?} ({:?} / {})", url, new_path, path, name); let repo = match Repository::open(&new_path) { Ok(repo) => repo, Err(e) => { eprintln!("can't open repo, going to try cloning it: {}", e.description()); match Repository::clone(url, &new_path) { Ok(repo) => repo, Err(e) => panic!("failed to clone: {}", e), } } }; repo.set_head("FETCH_HEAD")?; // repo.checkout_tree(&branch.into_reference(), Some(git2::build::CheckoutBuilder::new().force().use_theirs(true)))?; repo.checkout_head(Some(git2::build::CheckoutBuilder::new().force().use_theirs(true)))?; Ok(()) } fn webhook(req: &mut Request, config: &WebHookConfig) -> IronResult { eprintln!("Webhook Request: {:?}", req); let wh: serde_json::Value = serde_json::from_reader(&mut req.body).unwrap(); // Ensure we have a "Secret" parameter, and that it matches the config. let found_secret = wh.get("secret"); if found_secret.is_none() { return Ok(Response::with(status::Unauthorized)); } let found_secret = found_secret.unwrap(); if found_secret != &config.secret { return Ok(Response::with(status::Unauthorized)); } // Ensure the repo is valid. let repository = wh.get("repository"); if repository.is_none() { eprintln!("No 'repository' key found"); return Ok(Response::with(status::PreconditionRequired)); } let repository = repository.unwrap(); // Ensure there's an HTML URL that's a valid string let html_url = match repository.get("html_url") { None => { eprintln!("No HTML URL found"); return Ok(Response::with(status::PreconditionRequired)); } Some(s) => match s.as_str() { None => { eprintln!("HTML URL isn't a string"); return Ok(Response::with(status::PreconditionRequired)); } Some(s) => s, }, }; // Check to make sure the repo prefix is one we recognize let mut found_match = false; for prefix in &config.repo_prefixes { if html_url.starts_with(prefix) { found_match = true; break; } } if !found_match { eprintln!("URL doesn't start with match"); return Ok(Response::with(status::PreconditionRequired)); } // And ensure the URL is a string let website_url = match repository.get("website") { None => { eprintln!("The website isn't configured for this repo"); return Ok(Response::with(status::PreconditionRequired)); } Some(s) => match s.as_str() { None => { eprintln!("Website URL isn't a string"); return Ok(Response::with(status::PreconditionRequired)); } Some(s) => match iron::Url::parse(s) { Ok(u) => u, Err(e) => { eprintln!("Unable to parse URL: {:?}", e); return Ok(Response::with(status::PreconditionFailed)); } }, }, }; let target_path = match website_url.path().last() { Some(s) => { if s.is_empty() { eprintln!("No website URL path was found -- defaulting to \"current\""); "current".to_owned() } else { s.to_string() } } None => { eprintln!("No website URL path was found"); return Ok(Response::with(status::PreconditionFailed)); } }; eprintln!("Final path: {:?} / {:?}", config.repo_root, target_path); if let Err(e) = redeploy_repo(html_url, &config.repo_root, &target_path) { eprintln!("unable to clone/update repo: {}", e.description()); return Ok(Response::with(status::NotModified)); } Ok(Response::with(status::Ok)) } fn main() { let mut mount = Mount::new(); let hostaddr = "0.0.0.0:9119".to_owned(); let config = WebHookConfig { secret: "1234".to_owned(), repo_root: Path::new("D:\\Code\\talkserved").to_path_buf(), repo_prefixes: vec!["https://git.xobs.io/xobs".to_owned()], }; // Serve the shared JS/CSS at / mount.mount("/", Static::new(config.repo_root.clone())); // Listen for calls to "/webhook" and process accordingly mount.mount( "/webhook", Chain::new(move |req: &mut Request| webhook(req, &config)), ); println!("Doc server running on http://{}", hostaddr); Iron::new(mount).http(hostaddr).unwrap(); }