diff --git a/migrations/2022-10-11-151321_create_history/up.sql b/migrations/2022-10-11-151321_create_history/up.sql index 0e910a0..4a39a7c 100644 --- a/migrations/2022-10-11-151321_create_history/up.sql +++ b/migrations/2022-10-11-151321_create_history/up.sql @@ -4,7 +4,7 @@ CREATE TABLE hooklog ( repo_url TEXT NOT NULL, command_with_args TEXT NOT NULL, current_dir TEXT NOT NULL, - stdout TEXT NOT NULL, - stderr TEXT NOT NULL, + stdout TEXT, + stderr TEXT, status_code INTEGER CHECK (status_code >= 0) ); diff --git a/src/db.rs b/src/db.rs index 80541d8..a53f501 100644 --- a/src/db.rs +++ b/src/db.rs @@ -2,7 +2,6 @@ use chrono::Local; use diesel::prelude::*; use diesel::r2d2::{ConnectionManager, Pool, PooledConnection}; use std::env; -use std::process::Output; use crate::config::Hook; use crate::models::{HookLog, NewHookLog}; @@ -27,7 +26,7 @@ fn get_conn( .or(Err("Could not get database pool!".to_string())) } -pub fn add_hook_log(pool: &DBPool, hook: &Hook, output: &Output) -> Result { +pub fn add_hook_log(pool: &DBPool, hook: &Hook) -> Result { let conn = &mut get_conn(pool)?; let command_with_args = hook.command.to_owned() + " " + &hook.args.join(" "); @@ -37,18 +36,47 @@ pub fn add_hook_log(pool: &DBPool, hook: &Hook, output: &Output) -> Result(conn) + .or_else(|e| Err(e.to_string())) +} + +pub fn fill_hook_log( + pool: &DBPool, + hook_log_id: i32, + stdout: &[u8], + stderr: &[u8], + status_code: Option, +) { + let conn = &mut match get_conn(pool) { + Ok(pool) => pool, + Err(e) => { + println!("Error while trying to fill hook log: {e}"); + return; + } + }; + + match diesel::update(hooklog::dsl::hooklog.find(hook_log_id)) + .set(( + hooklog::dsl::stdout.eq(Some( + std::str::from_utf8(&stdout).unwrap_or("Could not convert stdout to str!"), + )), + hooklog::dsl::stderr.eq(Some( + std::str::from_utf8(stderr).unwrap_or("Could not convert stderr to str!"), + )), + hooklog::dsl::status_code.eq(status_code), + )) + .execute(conn) { - Ok(hook_log) => Ok(hook_log.id), - Err(e) => Err(e.to_string()), - } + Ok(_) => return, + Err(e) => { + println!("Could not update hook log: {}", e.to_string()); + return; + } + }; } pub fn get_hook_log(pool: &DBPool, id: i32) -> Result { diff --git a/src/guards.rs b/src/guards.rs index ce23eee..09ade9c 100644 --- a/src/guards.rs +++ b/src/guards.rs @@ -106,13 +106,8 @@ impl<'r> FromData<'r> for Repo<'r> { } fn is_valid_signature(secret: &[u8], received_signature: &[u8], payload: &[u8]) -> bool { - let mut mac = match Hmac::::new_from_slice(secret) { - Ok(mac) => mac, - Err(_) => { - println!("Can not generate a mac from the secret!"); - return false; - } - }; + let mut mac = + Hmac::::new_from_slice(secret).expect("Can not generate a mac from the secret!"); mac.update(payload); let expected_signature = mac.finalize().into_bytes(); diff --git a/src/models.rs b/src/models.rs index 9dccd03..8c1ddf8 100644 --- a/src/models.rs +++ b/src/models.rs @@ -10,8 +10,8 @@ pub struct HookLog { pub repo_url: String, pub command_with_args: String, pub current_dir: String, - pub stdout: String, - pub stderr: String, + pub stdout: Option, + pub stderr: Option, pub status_code: Option, } @@ -22,7 +22,4 @@ pub struct NewHookLog<'a> { pub repo_url: &'a str, pub command_with_args: &'a str, pub current_dir: &'a str, - pub stdout: &'a str, - pub stderr: &'a str, - pub status_code: Option, } diff --git a/src/routes.rs b/src/routes.rs index e960620..c72ca40 100644 --- a/src/routes.rs +++ b/src/routes.rs @@ -2,6 +2,7 @@ use rocket::response::status::BadRequest; use rocket::{get, post, State}; use rocket_dyn_templates::Template; use std::process::Command; +use std::thread; use crate::db; use crate::guards; @@ -35,9 +36,9 @@ pub fn index( #[post("/trigger", format = "json", data = "")] pub fn trigger( + repo: guards::Repo, db_state: &State, config_state: &State, - repo: guards::Repo, ) -> Result> { let hook = match config_state.get_hook(repo.clone_url) { Some(hook) => hook, @@ -49,17 +50,48 @@ pub fn trigger( } }; - let output = match Command::new(&hook.command) - .args(&hook.args) - .current_dir(&hook.current_dir) - .output() - { - Ok(output) => output, - Err(_) => return Err(bad_req("Can not run the hook command!")), + let hook_log_id = match db::add_hook_log(&db_state.pool, hook) { + Ok(hook_log) => hook_log.id, + Err(e) => return Err(bad_req(e)), }; - match db::add_hook_log(&db_state.pool, hook, &output) { - Ok(new_hook_log_id) => Ok(format!("{}/?id={}", config_state.base_url, new_hook_log_id)), - Err(e) => Err(bad_req(e)), + { + // Spawn and detach a thread that runs the command and fills the output in the log. + // This is useful to give a quick response to the git server in case that the command has a long + // execution time. It prevents reaching the webhook timeout of the git server. + + let command = hook.command.clone(); + let args = hook.args.clone(); + let current_dir = hook.current_dir.clone(); + let db_pool = db_state.pool.clone(); + + thread::spawn(move || { + let stdout: Vec; + let stderr: Vec; + let status_code: Option; + + match Command::new(command) + .args(args) + .current_dir(current_dir) + .output() + { + Ok(output) => { + stdout = output.stdout; + stderr = output.stderr; + status_code = output.status.code(); + } + Err(e) => { + stdout = Vec::new(); + stderr = format!("Error while running the hook command: {}", e.to_string()) + .as_bytes() + .to_vec(); + status_code = Some(1); + } + }; + + db::fill_hook_log(&db_pool, hook_log_id, &stdout, &stderr, status_code); + }); } + + Ok(format!("{}/?id={}", config_state.base_url, hook_log_id)) } diff --git a/src/schema.rs b/src/schema.rs index 30f5043..d4b8928 100644 --- a/src/schema.rs +++ b/src/schema.rs @@ -7,8 +7,8 @@ diesel::table! { repo_url -> Text, command_with_args -> Text, current_dir -> Text, - stdout -> Text, - stderr -> Text, + stdout -> Nullable, + stderr -> Nullable, status_code -> Nullable, } }