2018-05-30 12:01:15 +03:00
|
|
|
// Copyright (c) 2004-present, Facebook, Inc.
|
|
|
|
// All Rights Reserved.
|
|
|
|
//
|
|
|
|
// This software may be used and distributed according to the terms of the
|
|
|
|
// GNU General Public License version 2 or any later version.
|
|
|
|
|
|
|
|
//! This implements a command line utility called 'runhook' which runs a Mononoke hook
|
|
|
|
//! against a specified changeset.
|
|
|
|
//! It's main purpose is to allow easy testing of hooks without having to run them as part of
|
|
|
|
//! a push in a Mononoke server
|
|
|
|
//! It currently supports hooks written in Lua only
|
2018-05-31 17:18:23 +03:00
|
|
|
|
|
|
|
#![deny(warnings)]
|
|
|
|
|
2018-05-30 12:01:15 +03:00
|
|
|
extern crate clap;
|
|
|
|
extern crate failure_ext as failure;
|
|
|
|
extern crate futures;
|
|
|
|
extern crate tokio_core;
|
|
|
|
|
|
|
|
extern crate blobrepo;
|
|
|
|
extern crate blobstore;
|
|
|
|
#[macro_use]
|
|
|
|
extern crate futures_ext;
|
|
|
|
extern crate manifoldblob;
|
|
|
|
extern crate mercurial_types;
|
|
|
|
|
|
|
|
extern crate mononoke_types;
|
|
|
|
#[macro_use]
|
|
|
|
extern crate slog;
|
|
|
|
extern crate slog_glog_fmt;
|
|
|
|
|
2018-06-07 18:21:14 +03:00
|
|
|
#[cfg(test)]
|
|
|
|
extern crate tempdir;
|
|
|
|
|
2018-05-30 12:01:20 +03:00
|
|
|
extern crate hooks;
|
2018-05-30 12:01:15 +03:00
|
|
|
|
2018-06-04 14:02:00 +03:00
|
|
|
use hooks::{HookChangeset, HookExecution, HookManager};
|
2018-05-31 17:18:23 +03:00
|
|
|
use hooks::lua_hook::LuaHook;
|
2018-05-30 12:01:15 +03:00
|
|
|
use std::str;
|
|
|
|
use std::str::FromStr;
|
|
|
|
use std::sync::Arc;
|
|
|
|
|
|
|
|
use blobrepo::{BlobChangeset, BlobRepo};
|
|
|
|
use clap::{App, ArgMatches};
|
|
|
|
use failure::{Error, Result};
|
2018-06-07 18:21:14 +03:00
|
|
|
use futures::Future;
|
2018-05-30 12:01:15 +03:00
|
|
|
use futures_ext::{BoxFuture, FutureExt};
|
2018-06-07 23:12:16 +03:00
|
|
|
use mercurial_types::{Changeset, HgChangesetId, RepositoryId};
|
2018-05-30 12:01:15 +03:00
|
|
|
use slog::{Drain, Level, Logger};
|
|
|
|
use slog_glog_fmt::default_drain as glog_drain;
|
|
|
|
use tokio_core::reactor::Core;
|
|
|
|
|
2018-06-07 18:21:14 +03:00
|
|
|
use std::env::args;
|
2018-05-30 12:01:15 +03:00
|
|
|
use std::fs::File;
|
|
|
|
use std::io::prelude::*;
|
|
|
|
|
|
|
|
const MAX_CONCURRENT_REQUESTS_PER_IO_THREAD: usize = 4;
|
|
|
|
|
2018-06-07 18:21:14 +03:00
|
|
|
fn run_hook(args: Vec<String>) -> Result<HookExecution> {
|
2018-05-30 12:01:15 +03:00
|
|
|
// Define command line args and parse command line
|
|
|
|
let matches = App::new("runhook")
|
|
|
|
.version("0.0.0")
|
|
|
|
.about("run a hook")
|
|
|
|
.args_from_usage(concat!(
|
|
|
|
"<HOOK_FILE> 'file containing hook code\n",
|
|
|
|
"<REV> 'revision hash'"
|
|
|
|
))
|
2018-06-07 18:21:14 +03:00
|
|
|
.get_matches_from(args);
|
2018-05-30 12:01:15 +03:00
|
|
|
|
|
|
|
let logger = {
|
|
|
|
let level = if matches.is_present("debug") {
|
|
|
|
Level::Debug
|
|
|
|
} else {
|
|
|
|
Level::Info
|
|
|
|
};
|
|
|
|
|
|
|
|
let drain = glog_drain().filter_level(level).fuse();
|
|
|
|
slog::Logger::root(drain, o![])
|
|
|
|
};
|
|
|
|
|
|
|
|
let hook_file = matches.value_of("HOOK_FILE").unwrap();
|
|
|
|
let revstr = matches.value_of("REV").unwrap();
|
|
|
|
|
|
|
|
let repo = create_blobrepo(&logger, &matches);
|
|
|
|
let future = fetch_changeset(Arc::new(repo), revstr);
|
|
|
|
let mut core = Core::new().unwrap();
|
|
|
|
let changeset = core.run(future).unwrap();
|
|
|
|
|
2018-06-07 18:21:12 +03:00
|
|
|
let author = str::from_utf8(changeset.user()).unwrap();
|
2018-05-30 12:01:15 +03:00
|
|
|
let mut file = File::open(hook_file).expect("Unable to open the hook file");
|
|
|
|
let mut code = String::new();
|
|
|
|
file.read_to_string(&mut code)
|
|
|
|
.expect("Unable to read the file");
|
2018-06-07 18:21:12 +03:00
|
|
|
println!("======= Running hook =========");
|
|
|
|
println!("Hook file is {} revision is {:?}", hook_file, revstr);
|
|
|
|
println!("Hook code is {}", code);
|
|
|
|
println!("Changeset author: {:?} ", author);
|
|
|
|
println!("==============================");
|
2018-05-30 12:01:15 +03:00
|
|
|
|
|
|
|
let files = changeset.files();
|
|
|
|
let vec_files = files
|
|
|
|
.iter()
|
|
|
|
.map(|arr| String::from_utf8_lossy(&arr.to_vec()).into_owned())
|
|
|
|
.collect();
|
2018-06-07 18:21:12 +03:00
|
|
|
let hook_cs = HookChangeset::new(author.to_string(), vec_files);
|
2018-05-31 17:18:23 +03:00
|
|
|
let mut hook_manager = HookManager::new();
|
|
|
|
let hook = LuaHook {
|
|
|
|
name: String::from("testhook"),
|
|
|
|
code,
|
|
|
|
};
|
|
|
|
hook_manager.install_hook("testhook", Arc::new(hook));
|
2018-06-07 18:21:14 +03:00
|
|
|
let fut = hook_manager
|
|
|
|
.run_hooks(Arc::new(hook_cs))
|
|
|
|
.map(|executions| executions.get("testhook").unwrap().clone());
|
|
|
|
core.run(fut)
|
2018-05-30 12:01:15 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
fn create_blobrepo(logger: &Logger, matches: &ArgMatches) -> BlobRepo {
|
|
|
|
let bucket = matches
|
|
|
|
.value_of("manifold-bucket")
|
|
|
|
.unwrap_or("mononoke_prod");
|
|
|
|
let prefix = matches.value_of("manifold-prefix").unwrap_or("");
|
|
|
|
let xdb_tier = matches
|
|
|
|
.value_of("xdb-tier")
|
|
|
|
.unwrap_or("xdb.mononoke_test_2");
|
|
|
|
let io_threads = 5;
|
|
|
|
let default_cache_size = 1000000;
|
|
|
|
BlobRepo::new_test_manifold(
|
|
|
|
logger.clone(),
|
|
|
|
bucket,
|
|
|
|
prefix,
|
|
|
|
RepositoryId::new(0),
|
|
|
|
xdb_tier,
|
|
|
|
default_cache_size,
|
|
|
|
default_cache_size,
|
|
|
|
default_cache_size,
|
2018-06-05 15:40:31 +03:00
|
|
|
None,
|
2018-05-30 12:01:15 +03:00
|
|
|
io_threads,
|
|
|
|
MAX_CONCURRENT_REQUESTS_PER_IO_THREAD,
|
|
|
|
).expect("failed to create blobrepo instance")
|
|
|
|
}
|
|
|
|
|
|
|
|
fn fetch_changeset(repo: Arc<BlobRepo>, rev: &str) -> BoxFuture<BlobChangeset, Error> {
|
2018-06-07 23:12:16 +03:00
|
|
|
let cs_id = try_boxfuture!(HgChangesetId::from_str(rev));
|
2018-05-30 12:01:15 +03:00
|
|
|
repo.get_changeset_by_changesetid(&cs_id)
|
|
|
|
}
|
|
|
|
|
|
|
|
// It all starts here
|
|
|
|
fn main() -> Result<()> {
|
2018-06-07 18:21:14 +03:00
|
|
|
let args_vec = args().collect();
|
|
|
|
match run_hook(args_vec) {
|
|
|
|
Ok(HookExecution::Accepted) => println!("Hook accepted the changeset"),
|
|
|
|
Ok(HookExecution::Rejected(rejection_info)) => {
|
|
|
|
println!("Hook rejected the changeset {}", rejection_info.description)
|
|
|
|
}
|
|
|
|
Err(e) => println!("Failed to run hook {:?}", e),
|
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod test {
|
|
|
|
use super::*;
|
|
|
|
|
|
|
|
use std::fs::File;
|
|
|
|
use tempdir::TempDir;
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_hook_accepted() {
|
|
|
|
let code = String::from(
|
|
|
|
"hook = function (info, files)\n
|
|
|
|
return info.author == \"Tim Fox <tfox@fb.com>\"\n
|
|
|
|
end",
|
|
|
|
);
|
|
|
|
let changeset_id = String::from("50f849250f42436ee0db142aab721a12b7d95672");
|
|
|
|
let f = |execution| match execution {
|
|
|
|
Ok(HookExecution::Accepted) => (),
|
|
|
|
Ok(HookExecution::Rejected(rejection_info)) => {
|
|
|
|
assert!(rejection_info.description.starts_with("iuwehuweh"))
|
|
|
|
}
|
|
|
|
_ => assert!(false),
|
|
|
|
};
|
|
|
|
test_hook(code, changeset_id, &f);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_hook_rejected() {
|
|
|
|
let code = String::from(
|
|
|
|
"hook = function (info, files)\n
|
|
|
|
return info.author == \"Mahatma Ghandi\"\n
|
|
|
|
end",
|
|
|
|
);
|
|
|
|
let changeset_id = String::from("50f849250f42436ee0db142aab721a12b7d95672");
|
|
|
|
let f = |execution| match execution {
|
|
|
|
Ok(HookExecution::Accepted) => assert!(false),
|
|
|
|
Ok(HookExecution::Rejected(rejection_info)) => {
|
|
|
|
assert!(rejection_info.description.starts_with("short desc"))
|
|
|
|
}
|
|
|
|
_ => assert!(false),
|
|
|
|
};
|
|
|
|
test_hook(code, changeset_id, &f);
|
|
|
|
}
|
|
|
|
|
|
|
|
fn test_hook(code: String, changeset_id: String, f: &Fn(Result<HookExecution>) -> ()) {
|
|
|
|
let dir = TempDir::new("runhook").unwrap();
|
|
|
|
let file_path = dir.path().join("testhook.lua");
|
|
|
|
let mut file = File::create(file_path.clone()).unwrap();
|
|
|
|
file.write(code.as_bytes()).unwrap();
|
|
|
|
let args = vec![
|
|
|
|
String::from("runhook"),
|
|
|
|
file_path.to_str().unwrap().into(),
|
|
|
|
changeset_id,
|
|
|
|
];
|
|
|
|
f(run_hook(args));
|
|
|
|
}
|
|
|
|
|
2018-05-30 12:01:15 +03:00
|
|
|
}
|