From bcd55f33c033b219cab70996cd1c487e1f5b185e Mon Sep 17 00:00:00 2001 From: Mo8it Date: Tue, 11 Oct 2022 20:39:10 +0200 Subject: [PATCH] Added logs in a database --- .gitignore | 4 +- Cargo.lock | 89 ++++++++++-- Cargo.toml | 5 + diesel.toml | 8 ++ .../2022-10-11-151321_create_history/down.sql | 1 + .../2022-10-11-151321_create_history/up.sql | 9 ++ src/config.rs | 57 ++++++++ src/db.rs | 40 ++++++ src/main.rs | 129 ++++++++---------- src/models.rs | 24 ++++ src/schema.rs | 13 ++ 11 files changed, 298 insertions(+), 81 deletions(-) create mode 100644 diesel.toml create mode 100644 migrations/2022-10-11-151321_create_history/down.sql create mode 100644 migrations/2022-10-11-151321_create_history/up.sql create mode 100644 src/config.rs create mode 100644 src/db.rs create mode 100644 src/models.rs create mode 100644 src/schema.rs diff --git a/.gitignore b/.gitignore index cf417ee..af49066 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,3 @@ -/target - /config.json +/db/ +/target/ diff --git a/Cargo.lock b/Cargo.lock index 9865937..f2be8df 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -308,6 +308,28 @@ dependencies = [ "syn", ] +[[package]] +name = "diesel" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68c186a7418a2aac330bb76cde82f16c36b03a66fb91db32d20214311f9f6545" +dependencies = [ + "diesel_derives", + "libsqlite3-sys", +] + +[[package]] +name = "diesel_derives" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "143b758c91dbc3fe1fdcb0dba5bd13276c6a66422f2ef5795b58488248a310aa" +dependencies = [ + "proc-macro-error", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "digest" version = "0.10.5" @@ -477,6 +499,7 @@ name = "git-webhook-client" version = "0.1.0" dependencies = [ "cached", + "diesel", "hex", "hmac", "rocket", @@ -662,9 +685,19 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.134" +version = "0.2.135" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "329c933548736bc49fd575ee68c89e8be4d260064184389a5b77517cddd99ffb" +checksum = "68783febc7782c6c5cb401fbda4de5a9898be1762314da0bb2c10ced61f18b0c" + +[[package]] +name = "libsqlite3-sys" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f0455f2c1bc9a7caa792907026e469c1d91761fb0ea37cbb16427c77280cf35" +dependencies = [ + "pkg-config", + "vcpkg", +] [[package]] name = "lock_api" @@ -864,6 +897,12 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "pkg-config" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1df8c4ec4b0627e53bdf214615ad287367e482558cf84b109250b37464dc03ae" + [[package]] name = "polyval" version = "0.6.0" @@ -882,6 +921,30 @@ version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + [[package]] name = "proc-macro2" version = "1.0.46" @@ -954,18 +1017,18 @@ dependencies = [ [[package]] name = "ref-cast" -version = "1.0.9" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed13bcd201494ab44900a96490291651d200730904221832b9547d24a87d332b" +checksum = "b8ebf632f3e32bf35133f620cf481f29c99ae0fb01450fd3d85eee0225274ec1" dependencies = [ "ref-cast-impl", ] [[package]] name = "ref-cast-impl" -version = "1.0.9" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5234cd6063258a5e32903b53b1b6ac043a0541c8adc1f610f67b0326c7a578fa" +checksum = "caab98faa75ce294d40512ce514a46b15eafe78d72c9397a68ea45b3a88201b6" dependencies = [ "proc-macro2", "quote", @@ -1132,9 +1195,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.85" +version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e55a28e3aaef9d5ce0506d0a14dbba8054ddc7e499ef522dd8b26859ec9d4a44" +checksum = "41feea4228a6f1cd09ec7a3593a682276702cd67b5273544757dae23c096f074" dependencies = [ "itoa", "ryu", @@ -1335,9 +1398,9 @@ dependencies = [ [[package]] name = "tokio-stream" -version = "0.1.10" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6edf2d6bc038a43d31353570e27270603f4648d18f5ed10c0e179abe43255af" +checksum = "d660770404473ccd7bc9f8b28494a811bc18542b915c0855c51e8f419d5223ce" dependencies = [ "futures-core", "pin-project-lite", @@ -1494,6 +1557,12 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + [[package]] name = "version_check" version = "0.9.4" diff --git a/Cargo.toml b/Cargo.toml index f3e81cd..ffd815c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,6 +8,11 @@ license-file = "LICENSE" [dependencies] cached = "0.39.0" +diesel = { version = "2.0.2", features = [ + "sqlite", + "returning_clauses_for_sqlite_3_35", + "without-deprecated", +] } hex = "0.4.3" hmac = "0.12.1" rocket = "0.5.0-rc.2" diff --git a/diesel.toml b/diesel.toml new file mode 100644 index 0000000..35a12ff --- /dev/null +++ b/diesel.toml @@ -0,0 +1,8 @@ +# For documentation on how to configure this file, +# see https://diesel.rs/guides/configuring-diesel-cli + +[print_schema] +file = "src/schema.rs" + +[migrations_directory] +dir = "migrations" diff --git a/migrations/2022-10-11-151321_create_history/down.sql b/migrations/2022-10-11-151321_create_history/down.sql new file mode 100644 index 0000000..c619401 --- /dev/null +++ b/migrations/2022-10-11-151321_create_history/down.sql @@ -0,0 +1 @@ +DROP TABLE hooklog; diff --git a/migrations/2022-10-11-151321_create_history/up.sql b/migrations/2022-10-11-151321_create_history/up.sql new file mode 100644 index 0000000..0c2204b --- /dev/null +++ b/migrations/2022-10-11-151321_create_history/up.sql @@ -0,0 +1,9 @@ +CREATE TABLE hooklog ( + id INTEGER NOT NULL PRIMARY KEY, + repo_url VARCHAR(300) NOT NULL, + command_with_args TEXT NOT NULL, + current_dir VARCHAR(300) NOT NULL, + stdout TEXT NOT NULL, + stderr TEXT NOT NULL, + status_code INTEGER CHECK (status_code >= 0) +); diff --git a/src/config.rs b/src/config.rs new file mode 100644 index 0000000..567ce57 --- /dev/null +++ b/src/config.rs @@ -0,0 +1,57 @@ +use cached::proc_macro::once; +use serde::Deserialize; +use std::fs::File; +use std::io::BufReader; +use std::path::Path; + +#[derive(Deserialize, Clone)] +pub struct Hook { + pub repo_url: String, + pub current_dir: String, + pub command: String, + pub args: Vec, +} + +#[derive(Deserialize, Clone)] +struct Config { + secret: String, + base_url: String, + hooks: Vec, +} + +#[once] +fn get_config() -> Config { + let config_path = Path::new("config.json"); + let config_file = File::open(config_path).unwrap(); + let config_reader = BufReader::new(config_file); + let config: Config = serde_json::from_reader(config_reader).unwrap(); + + config +} + +#[once] +pub fn get_secret() -> Vec { + let config = get_config(); + + config.secret.as_bytes().to_owned() +} + +#[once] +pub fn get_hooks() -> Vec { + let config = get_config(); + + config.hooks +} + +#[once] +pub fn get_base_url() -> String { + let config = get_config(); + + config.base_url +} + +pub fn get_hook(clone_url: &str) -> Option { + let hooks = get_hooks(); + + hooks.into_iter().find(|hook| hook.repo_url == clone_url) +} diff --git a/src/db.rs b/src/db.rs new file mode 100644 index 0000000..aebdb9b --- /dev/null +++ b/src/db.rs @@ -0,0 +1,40 @@ +use crate::config::Hook; +use crate::models::{HookLog, NewHookLog}; +use crate::schema::hooklog; +use diesel::prelude::*; +use std::process::Output; + +fn establish_connection() -> SqliteConnection { + let database_url = "db/db.sqlite"; + SqliteConnection::establish(database_url).unwrap() +} + +pub fn add_hook_log(hook: &Hook, output: &Output) -> i32 { + let connection = &mut establish_connection(); + + let command_with_args = hook.command.to_owned() + " " + &hook.args.join(" "); + + let new_hook_log = NewHookLog { + repo_url: &hook.repo_url, + command_with_args: &command_with_args, + current_dir: &hook.current_dir, + stdout: std::str::from_utf8(&output.stdout).unwrap(), + stderr: std::str::from_utf8(&output.stderr).unwrap(), + status_code: output.status.code(), + }; + + let result: HookLog = diesel::insert_into(hooklog::table) + .values(&new_hook_log) + .get_result(connection) + .expect("Error saving hook log!"); + + result.id +} + +pub fn get_hook_log(id: i32) -> HookLog { + use hooklog::dsl::hooklog; + + let connection = &mut establish_connection(); + + hooklog.find(id).first(connection).unwrap() +} diff --git a/src/main.rs b/src/main.rs index 03b6ddd..b56ee2c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,72 +1,25 @@ #[macro_use] extern crate rocket; +mod config; +mod db; +mod models; +mod schema; + +use hmac::{Hmac, Mac}; use rocket::data::{self, Data, FromData, Limits, Outcome}; use rocket::http::Status; use rocket::request::{self, Request}; - -use hmac::{Hmac, Mac}; -use sha2::Sha256; - -use serde::Deserialize; use serde_json::Value; -use std::fs::File; -use std::io::BufReader; -use std::path::Path; - -use cached::proc_macro::once; - -#[derive(Deserialize, Clone)] -struct Hook { - repo_url: String, - commands: Vec, -} - -#[derive(Deserialize, Clone)] -struct Config { - secret: String, - hooks: Vec, -} +use sha2::Sha256; +use std::process::Command; struct Repo<'r> { - name: &'r str, clone_url: &'r str, } -#[once] -fn get_config() -> Config { - let config_path = Path::new("config.json"); - let config_file = File::open(config_path).unwrap(); - let config_reader = BufReader::new(config_file); - let config: Config = serde_json::from_reader(config_reader).unwrap(); - config -} - -#[once] -fn get_secret() -> Vec { - let config = get_config(); - config.secret.as_bytes().to_owned() -} - -#[once] -fn get_hooks() -> Vec { - let config = get_config(); - config.hooks -} - -fn get_hook_commands(clone_url: &str) -> Option> { - let hooks = get_hooks(); - for hook in hooks { - if hook.repo_url == clone_url { - return Some(hook.commands); - } - } - - None -} - fn is_valid_signature(received_signature: &[u8], payload: &[u8]) -> bool { - let mut mac = Hmac::::new_from_slice(&get_secret()).unwrap(); + let mut mac = Hmac::::new_from_slice(&config::get_secret()).unwrap(); mac.update(payload); let expected_signature = mac.finalize().into_bytes(); @@ -111,29 +64,67 @@ impl<'r> FromData<'r> for Repo<'r> { let json: Value = serde_json::from_slice(&payload).unwrap(); let repo = json.get("repository").unwrap(); - let name = repo.get("name").unwrap().as_str().unwrap().to_string(); let clone_url = repo.get("clone_url").unwrap().as_str().unwrap().to_string(); - let name = request::local_cache!(req, name); let clone_url = request::local_cache!(req, clone_url); - Outcome::Success(Repo { name, clone_url }) + Outcome::Success(Repo { clone_url }) } } -#[get("/")] -fn index() -> &'static str { - "Hello, world!" +#[get("/")] +fn index(id: i32) -> String { + let hook_log = db::get_hook_log(id); + format!( + "Hook log id: +{} + +Repository url: +{} + +Command with arguments: +{} + +Current directory of the command: +{} + +Standard output: +{} + +Standard error: +{} + +Status code: +{} +", + hook_log.id, + hook_log.repo_url, + hook_log.command_with_args, + hook_log.current_dir, + hook_log.stdout, + hook_log.stderr, + match hook_log.status_code { + Some(code) => code.to_string(), + None => String::from("None"), + } + ) } #[post("/trigger", format = "json", data = "")] -fn trigger(repo: Repo) -> &'static str { - println!("{:?}", repo.name); - let commands = get_hook_commands(repo.clone_url).unwrap(); - for command in commands { - println!("{:?}", command) - } - "Successful trigger!" +fn trigger(repo: Repo) -> String { + let hook = config::get_hook(repo.clone_url).unwrap(); + + let output = Command::new(&hook.command) + .args(&hook.args) + .current_dir(&hook.current_dir) + .output() + .unwrap(); + + let new_hook_log_id = db::add_hook_log(&hook, &output); + + let base_url = config::get_base_url(); + + format!("https://{base_url}/{new_hook_log_id}") } #[launch] diff --git a/src/models.rs b/src/models.rs new file mode 100644 index 0000000..93407e4 --- /dev/null +++ b/src/models.rs @@ -0,0 +1,24 @@ +use crate::schema::hooklog; +use diesel::prelude::{Insertable, Queryable}; + +#[derive(Queryable)] +pub struct HookLog { + pub id: i32, + pub repo_url: String, + pub command_with_args: String, + pub current_dir: String, + pub stdout: String, + pub stderr: String, + pub status_code: Option, +} + +#[derive(Insertable)] +#[diesel(table_name = hooklog)] +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/schema.rs b/src/schema.rs new file mode 100644 index 0000000..c94a62f --- /dev/null +++ b/src/schema.rs @@ -0,0 +1,13 @@ +// @generated automatically by Diesel CLI. + +diesel::table! { + hooklog (id) { + id -> Integer, + repo_url -> Text, + command_with_args -> Text, + current_dir -> Text, + stdout -> Text, + stderr -> Text, + status_code -> Nullable, + } +}