diff --git a/README.md b/README.md index 322b2d3..de586d0 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ The program looks for the configuration file configured with the environment var 1. `secret`: The secret of the webhook. 1. `base_url`: The base_url of the webhook client. 1. `hooks`: List of webhooks. -1. `repo_url`: Repository url. +1. `clone_url`: Repository url. 1. `current_dir`: The directory to run the command in. 1. `command`: The command without any arguments. 1. `args`: List of arguments separated by a comma. @@ -43,7 +43,7 @@ secret: CHANGE_ME! base_url: https://webhook.mo8it.com hooks: - repo_url: https://codeberg.org/Mo8it/git-webhook-client + clone_url: https://codeberg.org/Mo8it/git-webhook-client current_dir: . command: ls args: ["-l", "-a", "test_directory"] diff --git a/migrations/2022-10-11-151321_create_history/up.sql b/migrations/2022-10-11-151321_create_history/up.sql index 4a39a7c..8310b6a 100644 --- a/migrations/2022-10-11-151321_create_history/up.sql +++ b/migrations/2022-10-11-151321_create_history/up.sql @@ -1,7 +1,7 @@ CREATE TABLE hooklog ( id INTEGER NOT NULL PRIMARY KEY, datetime TEXT NOT NULL, - repo_url TEXT NOT NULL, + clone_url TEXT NOT NULL, command_with_args TEXT NOT NULL, current_dir TEXT NOT NULL, stdout TEXT, diff --git a/src/config.rs b/src/config.rs index f0b0814..c795ed5 100644 --- a/src/config.rs +++ b/src/config.rs @@ -28,10 +28,10 @@ pub struct Logging { pub filename: String, } -#[derive(Deserialize)] +#[derive(Clone, Deserialize)] pub struct Hook { pub name: String, - pub repo_url: String, + pub clone_url: String, pub current_dir: String, pub command: String, pub args: Vec, diff --git a/src/db.rs b/src/db.rs index 7c694ac..b11005a 100644 --- a/src/db.rs +++ b/src/db.rs @@ -51,7 +51,7 @@ pub fn add_hook_log(pool: &DBPool, hook: &Hook) -> Result { let new_hook_log = NewHookLog { datetime: &Local::now().format("%d.%m.%Y %T").to_string(), - repo_url: &hook.repo_url, + clone_url: &hook.clone_url, command_with_args: &command_with_args, current_dir: &hook.current_dir, }; diff --git a/src/mailer.rs b/src/mailer.rs index 719d9f1..3e3c5a2 100644 --- a/src/mailer.rs +++ b/src/mailer.rs @@ -44,7 +44,7 @@ impl Mailer { }) } - pub fn send(&self, hook_name: &str, hook_log_link: &str, status: &str) -> Result<()> { + pub async fn send(&self, hook_name: &str, hook_log_link: &str, status: &str) -> Result<()> { let email = self .message_builder .clone() diff --git a/src/main.rs b/src/main.rs index fa313d1..7c586fe 100644 --- a/src/main.rs +++ b/src/main.rs @@ -9,6 +9,7 @@ mod routes; mod schema; mod states; mod templates; +mod webhook; use anyhow::Result; use axum::{ diff --git a/src/models.rs b/src/models.rs index 8c1ddf8..f1e363c 100644 --- a/src/models.rs +++ b/src/models.rs @@ -7,7 +7,7 @@ use crate::schema::hooklog; pub struct HookLog { pub id: i32, pub datetime: String, - pub repo_url: String, + pub clone_url: String, pub command_with_args: String, pub current_dir: String, pub stdout: Option, @@ -19,7 +19,7 @@ pub struct HookLog { #[diesel(table_name = hooklog)] pub struct NewHookLog<'a> { pub datetime: &'a str, - pub repo_url: &'a str, + pub clone_url: &'a str, pub command_with_args: &'a str, pub current_dir: &'a str, } diff --git a/src/routes.rs b/src/routes.rs index 8561276..3054afc 100644 --- a/src/routes.rs +++ b/src/routes.rs @@ -6,10 +6,11 @@ use axum::{ }; use serde::Deserialize; use serde_json::Value; -use std::{process::Command, sync::Arc, thread}; -use tracing::{error, info}; +use std::sync::Arc; +use tokio::task; +use tracing::info; -use crate::{db, errors, extractors, mailer, states, templates}; +use crate::{db, errors, extractors, mailer, states, templates, webhook}; #[derive(Deserialize)] pub struct IndexQuery { @@ -66,55 +67,18 @@ pub async fn trigger( let hook_log_link = format!("{}/?id={}", state_config.base_url, hook_log_id); - { - // 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 webhook_task_context = webhook::TaskContext { + hook: hook.clone(), + hook_log_id, + hook_log_link: hook_log_link.clone(), + db: db.clone(), + mailer: mailer.clone(), + }; - let command = hook.command.clone(); - let args = hook.args.clone(); - let current_dir = hook.current_dir.clone(); - let db_pool = db.pool.clone(); - let clone_url = clone_url.to_string(); - let hook_name = hook.name.clone(); - let hook_log_link = hook_log_link.clone(); - - thread::spawn(move || { - info!("Running webhook for Repo: {clone_url}"); - - 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}") - .as_bytes() - .to_vec(); - status_code = Some(1); - } - }; - - let status = if status_code == Some(0) { "Ok" } else { "Err" }; - - db::fill_hook_log(&db_pool, hook_log_id, &stdout, &stderr, status_code); - - match mailer.send(&hook_name, &hook_log_link, status) { - Ok(_) => info!("Sent email with hook name {hook_name}"), - Err(e) => error!("{e:?}"), - }; - }); - } + // Spawn and detach a task 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. + task::spawn(async move { webhook::run(webhook_task_context).await }); Ok(hook_log_link.into_response()) } diff --git a/src/schema.rs b/src/schema.rs index d4b8928..b3dafac 100644 --- a/src/schema.rs +++ b/src/schema.rs @@ -4,7 +4,7 @@ diesel::table! { hooklog (id) { id -> Integer, datetime -> Text, - repo_url -> Text, + clone_url -> Text, command_with_args -> Text, current_dir -> Text, stdout -> Nullable, diff --git a/src/states.rs b/src/states.rs index e27e4db..b3230a8 100644 --- a/src/states.rs +++ b/src/states.rs @@ -34,7 +34,7 @@ impl From for StateConfig { impl StateConfig { pub fn get_hook(&self, clone_url: &str) -> Option<&config::Hook> { - self.hooks.iter().find(|&hook| hook.repo_url == clone_url) + self.hooks.iter().find(|&hook| hook.clone_url == clone_url) } } diff --git a/src/templates.rs b/src/templates.rs index 1ac77e1..623565d 100644 --- a/src/templates.rs +++ b/src/templates.rs @@ -7,7 +7,7 @@ use crate::models; pub struct HookLog { pub id: i32, pub datetime: String, - pub repo_url: String, + pub clone_url: String, pub command_with_args: String, pub current_dir: String, pub stdout: String, @@ -20,7 +20,7 @@ impl From for HookLog { Self { id: hook_log.id, datetime: hook_log.datetime, - repo_url: hook_log.repo_url, + clone_url: hook_log.clone_url, command_with_args: hook_log.command_with_args, current_dir: hook_log.current_dir, stdout: hook_log.stdout.unwrap_or_default(), diff --git a/src/webhook.rs b/src/webhook.rs new file mode 100644 index 0000000..da93bba --- /dev/null +++ b/src/webhook.rs @@ -0,0 +1,58 @@ +use std::{process::Command, sync::Arc}; +use tracing::{error, info}; + +use crate::{config, db, mailer, states}; + +pub struct TaskContext { + pub hook: config::Hook, + pub hook_log_id: i32, + pub hook_log_link: String, + pub db: Arc, + pub mailer: Arc, +} + +pub async fn run(context: TaskContext) { + info!("Running webhook for repo: {}", context.hook.clone_url); + + let stdout: Vec; + let stderr: Vec; + let status_code: Option; + + match Command::new(&context.hook.command) + .args(&context.hook.args) + .current_dir(&context.hook.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}") + .as_bytes() + .to_vec(); + status_code = Some(1); + } + }; + + let status = if status_code == Some(0) { "Ok" } else { "Err" }; + + db::fill_hook_log( + &context.db.pool, + context.hook_log_id, + &stdout, + &stderr, + status_code, + ); + + match context + .mailer + .send(&context.hook.name, &context.hook_log_link, status) + .await + { + Ok(_) => info!("Sent email with hook name {}", context.hook.name), + Err(e) => error!("{e:?}"), + }; +} diff --git a/templates/hook_log.txt b/templates/hook_log.txt index b84ff8b..d455487 100644 --- a/templates/hook_log.txt +++ b/templates/hook_log.txt @@ -4,8 +4,8 @@ Hook log id: Datetime: {{ datetime }} -Repository url: -{{ repo_url }} +Clone url: +{{ clone_url }} Command with arguments: {{ command_with_args }}