1
0
Fork 0
mirror of https://codeberg.org/Mo8it/git-webhook-client synced 2024-10-18 07:22:39 +00:00

Run the command in an attached thread

This commit is contained in:
Mo 2022-10-23 00:08:28 +02:00
parent 5b1b2108bf
commit aa6ee45c2f
6 changed files with 88 additions and 36 deletions

View file

@ -4,7 +4,7 @@ CREATE TABLE hooklog (
repo_url TEXT NOT NULL, repo_url TEXT NOT NULL,
command_with_args TEXT NOT NULL, command_with_args TEXT NOT NULL,
current_dir TEXT NOT NULL, current_dir TEXT NOT NULL,
stdout TEXT NOT NULL, stdout TEXT,
stderr TEXT NOT NULL, stderr TEXT,
status_code INTEGER CHECK (status_code >= 0) status_code INTEGER CHECK (status_code >= 0)
); );

View file

@ -2,7 +2,6 @@ use chrono::Local;
use diesel::prelude::*; use diesel::prelude::*;
use diesel::r2d2::{ConnectionManager, Pool, PooledConnection}; use diesel::r2d2::{ConnectionManager, Pool, PooledConnection};
use std::env; use std::env;
use std::process::Output;
use crate::config::Hook; use crate::config::Hook;
use crate::models::{HookLog, NewHookLog}; use crate::models::{HookLog, NewHookLog};
@ -27,7 +26,7 @@ fn get_conn(
.or(Err("Could not get database pool!".to_string())) .or(Err("Could not get database pool!".to_string()))
} }
pub fn add_hook_log(pool: &DBPool, hook: &Hook, output: &Output) -> Result<i32, String> { pub fn add_hook_log(pool: &DBPool, hook: &Hook) -> Result<HookLog, String> {
let conn = &mut get_conn(pool)?; let conn = &mut get_conn(pool)?;
let command_with_args = hook.command.to_owned() + " " + &hook.args.join(" "); 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<i32,
repo_url: &hook.repo_url, repo_url: &hook.repo_url,
command_with_args: &command_with_args, command_with_args: &command_with_args,
current_dir: &hook.current_dir, current_dir: &hook.current_dir,
stdout: std::str::from_utf8(&output.stdout).unwrap_or("Could not convert stdout to str!"),
stderr: std::str::from_utf8(&output.stderr).unwrap_or("Could not convert stderr to str!"),
status_code: output.status.code(),
}; };
match diesel::insert_into(hooklog::table) diesel::insert_into(hooklog::table)
.values(&new_hook_log) .values(&new_hook_log)
.get_result::<HookLog>(conn) .get_result::<HookLog>(conn)
{ .or_else(|e| Err(e.to_string()))
Ok(hook_log) => Ok(hook_log.id),
Err(e) => Err(e.to_string()),
} }
pub fn fill_hook_log(
pool: &DBPool,
hook_log_id: i32,
stdout: &[u8],
stderr: &[u8],
status_code: Option<i32>,
) {
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(_) => return,
Err(e) => {
println!("Could not update hook log: {}", e.to_string());
return;
}
};
} }
pub fn get_hook_log(pool: &DBPool, id: i32) -> Result<HookLog, String> { pub fn get_hook_log(pool: &DBPool, id: i32) -> Result<HookLog, String> {

View file

@ -106,13 +106,8 @@ impl<'r> FromData<'r> for Repo<'r> {
} }
fn is_valid_signature(secret: &[u8], received_signature: &[u8], payload: &[u8]) -> bool { fn is_valid_signature(secret: &[u8], received_signature: &[u8], payload: &[u8]) -> bool {
let mut mac = match Hmac::<Sha256>::new_from_slice(secret) { let mut mac =
Ok(mac) => mac, Hmac::<Sha256>::new_from_slice(secret).expect("Can not generate a mac from the secret!");
Err(_) => {
println!("Can not generate a mac from the secret!");
return false;
}
};
mac.update(payload); mac.update(payload);
let expected_signature = mac.finalize().into_bytes(); let expected_signature = mac.finalize().into_bytes();

View file

@ -10,8 +10,8 @@ pub struct HookLog {
pub repo_url: String, pub repo_url: String,
pub command_with_args: String, pub command_with_args: String,
pub current_dir: String, pub current_dir: String,
pub stdout: String, pub stdout: Option<String>,
pub stderr: String, pub stderr: Option<String>,
pub status_code: Option<i32>, pub status_code: Option<i32>,
} }
@ -22,7 +22,4 @@ pub struct NewHookLog<'a> {
pub repo_url: &'a str, pub repo_url: &'a str,
pub command_with_args: &'a str, pub command_with_args: &'a str,
pub current_dir: &'a str, pub current_dir: &'a str,
pub stdout: &'a str,
pub stderr: &'a str,
pub status_code: Option<i32>,
} }

View file

@ -2,6 +2,7 @@ use rocket::response::status::BadRequest;
use rocket::{get, post, State}; use rocket::{get, post, State};
use rocket_dyn_templates::Template; use rocket_dyn_templates::Template;
use std::process::Command; use std::process::Command;
use std::thread;
use crate::db; use crate::db;
use crate::guards; use crate::guards;
@ -35,9 +36,9 @@ pub fn index(
#[post("/trigger", format = "json", data = "<repo>")] #[post("/trigger", format = "json", data = "<repo>")]
pub fn trigger( pub fn trigger(
repo: guards::Repo,
db_state: &State<states::DB>, db_state: &State<states::DB>,
config_state: &State<states::Config>, config_state: &State<states::Config>,
repo: guards::Repo,
) -> Result<String, BadRequest<String>> { ) -> Result<String, BadRequest<String>> {
let hook = match config_state.get_hook(repo.clone_url) { let hook = match config_state.get_hook(repo.clone_url) {
Some(hook) => hook, Some(hook) => hook,
@ -49,17 +50,48 @@ pub fn trigger(
} }
}; };
let output = match Command::new(&hook.command) let hook_log_id = match db::add_hook_log(&db_state.pool, hook) {
.args(&hook.args) Ok(hook_log) => hook_log.id,
.current_dir(&hook.current_dir) Err(e) => return Err(bad_req(e)),
.output()
{
Ok(output) => output,
Err(_) => return Err(bad_req("Can not run the hook command!")),
}; };
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)), // Spawn and detach a thread that runs the command and fills the output in the log.
Err(e) => Err(bad_req(e)), // 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<u8>;
let stderr: Vec<u8>;
let status_code: Option<i32>;
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))
} }

View file

@ -7,8 +7,8 @@ diesel::table! {
repo_url -> Text, repo_url -> Text,
command_with_args -> Text, command_with_args -> Text,
current_dir -> Text, current_dir -> Text,
stdout -> Text, stdout -> Nullable<Text>,
stderr -> Text, stderr -> Nullable<Text>,
status_code -> Nullable<Integer>, status_code -> Nullable<Integer>,
} }
} }