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

Added logs in a database

This commit is contained in:
Mo 2022-10-11 20:39:10 +02:00
parent b33f8e5254
commit bcd55f33c0
11 changed files with 298 additions and 81 deletions

4
.gitignore vendored
View file

@ -1,3 +1,3 @@
/target
/config.json /config.json
/db/
/target/

89
Cargo.lock generated
View file

@ -308,6 +308,28 @@ dependencies = [
"syn", "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]] [[package]]
name = "digest" name = "digest"
version = "0.10.5" version = "0.10.5"
@ -477,6 +499,7 @@ name = "git-webhook-client"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"cached", "cached",
"diesel",
"hex", "hex",
"hmac", "hmac",
"rocket", "rocket",
@ -662,9 +685,19 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.134" version = "0.2.135"
source = "registry+https://github.com/rust-lang/crates.io-index" 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]] [[package]]
name = "lock_api" name = "lock_api"
@ -864,6 +897,12 @@ version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
[[package]]
name = "pkg-config"
version = "0.3.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1df8c4ec4b0627e53bdf214615ad287367e482558cf84b109250b37464dc03ae"
[[package]] [[package]]
name = "polyval" name = "polyval"
version = "0.6.0" version = "0.6.0"
@ -882,6 +921,30 @@ version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" 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]] [[package]]
name = "proc-macro2" name = "proc-macro2"
version = "1.0.46" version = "1.0.46"
@ -954,18 +1017,18 @@ dependencies = [
[[package]] [[package]]
name = "ref-cast" name = "ref-cast"
version = "1.0.9" version = "1.0.10"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed13bcd201494ab44900a96490291651d200730904221832b9547d24a87d332b" checksum = "b8ebf632f3e32bf35133f620cf481f29c99ae0fb01450fd3d85eee0225274ec1"
dependencies = [ dependencies = [
"ref-cast-impl", "ref-cast-impl",
] ]
[[package]] [[package]]
name = "ref-cast-impl" name = "ref-cast-impl"
version = "1.0.9" version = "1.0.10"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5234cd6063258a5e32903b53b1b6ac043a0541c8adc1f610f67b0326c7a578fa" checksum = "caab98faa75ce294d40512ce514a46b15eafe78d72c9397a68ea45b3a88201b6"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -1132,9 +1195,9 @@ dependencies = [
[[package]] [[package]]
name = "serde_json" name = "serde_json"
version = "1.0.85" version = "1.0.86"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e55a28e3aaef9d5ce0506d0a14dbba8054ddc7e499ef522dd8b26859ec9d4a44" checksum = "41feea4228a6f1cd09ec7a3593a682276702cd67b5273544757dae23c096f074"
dependencies = [ dependencies = [
"itoa", "itoa",
"ryu", "ryu",
@ -1335,9 +1398,9 @@ dependencies = [
[[package]] [[package]]
name = "tokio-stream" name = "tokio-stream"
version = "0.1.10" version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6edf2d6bc038a43d31353570e27270603f4648d18f5ed10c0e179abe43255af" checksum = "d660770404473ccd7bc9f8b28494a811bc18542b915c0855c51e8f419d5223ce"
dependencies = [ dependencies = [
"futures-core", "futures-core",
"pin-project-lite", "pin-project-lite",
@ -1494,6 +1557,12 @@ version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d"
[[package]]
name = "vcpkg"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
[[package]] [[package]]
name = "version_check" name = "version_check"
version = "0.9.4" version = "0.9.4"

View file

@ -8,6 +8,11 @@ license-file = "LICENSE"
[dependencies] [dependencies]
cached = "0.39.0" cached = "0.39.0"
diesel = { version = "2.0.2", features = [
"sqlite",
"returning_clauses_for_sqlite_3_35",
"without-deprecated",
] }
hex = "0.4.3" hex = "0.4.3"
hmac = "0.12.1" hmac = "0.12.1"
rocket = "0.5.0-rc.2" rocket = "0.5.0-rc.2"

8
diesel.toml Normal file
View file

@ -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"

View file

@ -0,0 +1 @@
DROP TABLE hooklog;

View file

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

57
src/config.rs Normal file
View file

@ -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<String>,
}
#[derive(Deserialize, Clone)]
struct Config {
secret: String,
base_url: String,
hooks: Vec<Hook>,
}
#[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<u8> {
let config = get_config();
config.secret.as_bytes().to_owned()
}
#[once]
pub fn get_hooks() -> Vec<Hook> {
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<Hook> {
let hooks = get_hooks();
hooks.into_iter().find(|hook| hook.repo_url == clone_url)
}

40
src/db.rs Normal file
View file

@ -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()
}

View file

@ -1,72 +1,25 @@
#[macro_use] #[macro_use]
extern crate rocket; 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::data::{self, Data, FromData, Limits, Outcome};
use rocket::http::Status; use rocket::http::Status;
use rocket::request::{self, Request}; use rocket::request::{self, Request};
use hmac::{Hmac, Mac};
use sha2::Sha256;
use serde::Deserialize;
use serde_json::Value; use serde_json::Value;
use std::fs::File; use sha2::Sha256;
use std::io::BufReader; use std::process::Command;
use std::path::Path;
use cached::proc_macro::once;
#[derive(Deserialize, Clone)]
struct Hook {
repo_url: String,
commands: Vec<String>,
}
#[derive(Deserialize, Clone)]
struct Config {
secret: String,
hooks: Vec<Hook>,
}
struct Repo<'r> { struct Repo<'r> {
name: &'r str,
clone_url: &'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<u8> {
let config = get_config();
config.secret.as_bytes().to_owned()
}
#[once]
fn get_hooks() -> Vec<Hook> {
let config = get_config();
config.hooks
}
fn get_hook_commands(clone_url: &str) -> Option<Vec<String>> {
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 { fn is_valid_signature(received_signature: &[u8], payload: &[u8]) -> bool {
let mut mac = Hmac::<Sha256>::new_from_slice(&get_secret()).unwrap(); let mut mac = Hmac::<Sha256>::new_from_slice(&config::get_secret()).unwrap();
mac.update(payload); mac.update(payload);
let expected_signature = mac.finalize().into_bytes(); 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 json: Value = serde_json::from_slice(&payload).unwrap();
let repo = json.get("repository").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 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); let clone_url = request::local_cache!(req, clone_url);
Outcome::Success(Repo { name, clone_url }) Outcome::Success(Repo { clone_url })
} }
} }
#[get("/")] #[get("/<id>")]
fn index() -> &'static str { fn index(id: i32) -> String {
"Hello, world!" 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 = "<repo>")] #[post("/trigger", format = "json", data = "<repo>")]
fn trigger(repo: Repo) -> &'static str { fn trigger(repo: Repo) -> String {
println!("{:?}", repo.name); let hook = config::get_hook(repo.clone_url).unwrap();
let commands = get_hook_commands(repo.clone_url).unwrap();
for command in commands { let output = Command::new(&hook.command)
println!("{:?}", command) .args(&hook.args)
} .current_dir(&hook.current_dir)
"Successful trigger!" .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] #[launch]

24
src/models.rs Normal file
View file

@ -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<i32>,
}
#[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<i32>,
}

13
src/schema.rs Normal file
View file

@ -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<Integer>,
}
}