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,
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)
);

View file

@ -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<i32, String> {
pub fn add_hook_log(pool: &DBPool, hook: &Hook) -> Result<HookLog, String> {
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<i32,
repo_url: &hook.repo_url,
command_with_args: &command_with_args,
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)
.get_result::<HookLog>(conn)
{
Ok(hook_log) => Ok(hook_log.id),
Err(e) => Err(e.to_string()),
.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<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> {

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 {
let mut mac = match Hmac::<Sha256>::new_from_slice(secret) {
Ok(mac) => mac,
Err(_) => {
println!("Can not generate a mac from the secret!");
return false;
}
};
let mut mac =
Hmac::<Sha256>::new_from_slice(secret).expect("Can not generate a mac from the secret!");
mac.update(payload);
let expected_signature = mac.finalize().into_bytes();

View file

@ -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<String>,
pub stderr: Option<String>,
pub status_code: Option<i32>,
}
@ -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<i32>,
}

View file

@ -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 = "<repo>")]
pub fn trigger(
repo: guards::Repo,
db_state: &State<states::DB>,
config_state: &State<states::Config>,
repo: guards::Repo,
) -> Result<String, BadRequest<String>> {
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<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,
command_with_args -> Text,
current_dir -> Text,
stdout -> Text,
stderr -> Text,
stdout -> Nullable<Text>,
stderr -> Nullable<Text>,
status_code -> Nullable<Integer>,
}
}