mirror of
https://codeberg.org/Mo8it/git-webhook-client
synced 2024-11-21 11:06:32 +00:00
Add states
This commit is contained in:
parent
322fa93c65
commit
8347d0a391
9 changed files with 91 additions and 1765 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -1,3 +1,4 @@
|
||||||
|
/Cargo.lock
|
||||||
/config.json
|
/config.json
|
||||||
/db/
|
/db/
|
||||||
/target/
|
/target/
|
||||||
|
|
1700
Cargo.lock
generated
1700
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
@ -9,6 +9,7 @@ license-file = "LICENSE"
|
||||||
[dependencies]
|
[dependencies]
|
||||||
cached = "0.39.0"
|
cached = "0.39.0"
|
||||||
diesel = { version = "2.0.2", features = [
|
diesel = { version = "2.0.2", features = [
|
||||||
|
"r2d2",
|
||||||
"sqlite",
|
"sqlite",
|
||||||
"returning_clauses_for_sqlite_3_35",
|
"returning_clauses_for_sqlite_3_35",
|
||||||
"without-deprecated",
|
"without-deprecated",
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
use cached::proc_macro::once;
|
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::BufReader;
|
use std::io::BufReader;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
#[derive(Deserialize, Clone)]
|
#[derive(Deserialize)]
|
||||||
pub struct Hook {
|
pub struct Hook {
|
||||||
pub repo_url: String,
|
pub repo_url: String,
|
||||||
pub current_dir: String,
|
pub current_dir: String,
|
||||||
|
@ -12,46 +11,20 @@ pub struct Hook {
|
||||||
pub args: Vec<String>,
|
pub args: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Clone)]
|
#[derive(Deserialize)]
|
||||||
struct Config {
|
pub struct Config {
|
||||||
secret: String,
|
pub secret: String,
|
||||||
base_url: String,
|
pub base_url: String,
|
||||||
hooks: Vec<Hook>,
|
pub hooks: Vec<Hook>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[once]
|
impl Config {
|
||||||
fn get_config() -> Config {
|
pub fn new() -> Self {
|
||||||
let config_path = Path::new("config.json");
|
let config_path = Path::new("config.json");
|
||||||
let config_file = File::open(config_path).unwrap();
|
let config_file = File::open(config_path).unwrap();
|
||||||
let config_reader = BufReader::new(config_file);
|
let config_reader = BufReader::new(config_file);
|
||||||
let config: Config = serde_json::from_reader(config_reader).unwrap();
|
let config: Self = serde_json::from_reader(config_reader).unwrap();
|
||||||
|
|
||||||
config
|
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)
|
|
||||||
}
|
}
|
||||||
|
|
28
src/db.rs
28
src/db.rs
|
@ -1,17 +1,26 @@
|
||||||
use diesel::prelude::*;
|
use diesel::prelude::*;
|
||||||
|
use diesel::r2d2::{ConnectionManager, Pool};
|
||||||
|
use std::env;
|
||||||
use std::process::Output;
|
use std::process::Output;
|
||||||
|
|
||||||
use crate::config::Hook;
|
use crate::config::Hook;
|
||||||
use crate::models::{HookLog, NewHookLog};
|
use crate::models::{HookLog, NewHookLog};
|
||||||
use crate::schema::hooklog;
|
use crate::schema::hooklog;
|
||||||
|
|
||||||
fn establish_connection() -> SqliteConnection {
|
pub type DBPool = Pool<ConnectionManager<SqliteConnection>>;
|
||||||
let database_url = "db/db.sqlite";
|
|
||||||
SqliteConnection::establish(database_url).unwrap()
|
pub fn establish_connection_pool() -> DBPool {
|
||||||
|
let database_url =
|
||||||
|
env::var("DATABASE_URL").expect("Environment variable DATABASE_URL missing!");
|
||||||
|
let manager = ConnectionManager::<SqliteConnection>::new(&database_url);
|
||||||
|
|
||||||
|
Pool::builder()
|
||||||
|
.build(manager)
|
||||||
|
.expect("Could not build database connection pool!")
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_hook_log(hook: &Hook, output: &Output) -> i32 {
|
pub fn add_hook_log(pool: &DBPool, hook: &Hook, output: &Output) -> i32 {
|
||||||
let connection = &mut establish_connection();
|
let conn = &mut pool.get().unwrap();
|
||||||
|
|
||||||
let command_with_args = hook.command.to_owned() + " " + &hook.args.join(" ");
|
let command_with_args = hook.command.to_owned() + " " + &hook.args.join(" ");
|
||||||
|
|
||||||
|
@ -26,16 +35,15 @@ pub fn add_hook_log(hook: &Hook, output: &Output) -> i32 {
|
||||||
|
|
||||||
let result: HookLog = diesel::insert_into(hooklog::table)
|
let result: HookLog = diesel::insert_into(hooklog::table)
|
||||||
.values(&new_hook_log)
|
.values(&new_hook_log)
|
||||||
.get_result(connection)
|
.get_result(conn)
|
||||||
.expect("Error saving hook log!");
|
.expect("Error saving hook log!");
|
||||||
|
|
||||||
result.id
|
result.id
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_hook_log(id: i32) -> HookLog {
|
pub fn get_hook_log(pool: &DBPool, id: i32) -> HookLog {
|
||||||
|
let conn = &mut pool.get().unwrap();
|
||||||
use hooklog::dsl::hooklog;
|
use hooklog::dsl::hooklog;
|
||||||
|
|
||||||
let connection = &mut establish_connection();
|
hooklog.find(id).first(conn).unwrap()
|
||||||
|
|
||||||
hooklog.find(id).first(connection).unwrap()
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,7 @@ use rocket::request::{self, Request};
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
use sha2::Sha256;
|
use sha2::Sha256;
|
||||||
|
|
||||||
use crate::config;
|
use crate::states;
|
||||||
|
|
||||||
pub struct Repo<'r> {
|
pub struct Repo<'r> {
|
||||||
pub clone_url: &'r str,
|
pub clone_url: &'r str,
|
||||||
|
@ -43,7 +43,9 @@ impl<'r> FromData<'r> for Repo<'r> {
|
||||||
return Outcome::Failure((Status::BadRequest, Self::Error::MoreThatOneSignature));
|
return Outcome::Failure((Status::BadRequest, Self::Error::MoreThatOneSignature));
|
||||||
}
|
}
|
||||||
|
|
||||||
if !is_valid_signature(&received_signature, &payload) {
|
let config_state = req.rocket().state::<states::Config>().unwrap();
|
||||||
|
|
||||||
|
if !is_valid_signature(&config_state.secret, &received_signature, &payload) {
|
||||||
return Outcome::Failure((Status::BadRequest, Self::Error::InvalidSignature));
|
return Outcome::Failure((Status::BadRequest, Self::Error::InvalidSignature));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -57,8 +59,8 @@ impl<'r> FromData<'r> for Repo<'r> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_valid_signature(received_signature: &[u8], payload: &[u8]) -> bool {
|
fn is_valid_signature(secret: &[u8], received_signature: &[u8], payload: &[u8]) -> bool {
|
||||||
let mut mac = Hmac::<Sha256>::new_from_slice(&config::get_secret()).unwrap();
|
let mut mac = Hmac::<Sha256>::new_from_slice(secret).unwrap();
|
||||||
mac.update(payload);
|
mac.update(payload);
|
||||||
let expected_signature = mac.finalize().into_bytes();
|
let expected_signature = mac.finalize().into_bytes();
|
||||||
|
|
||||||
|
|
|
@ -4,10 +4,13 @@ mod guards;
|
||||||
mod models;
|
mod models;
|
||||||
mod routes;
|
mod routes;
|
||||||
mod schema;
|
mod schema;
|
||||||
|
mod states;
|
||||||
|
|
||||||
#[rocket::launch]
|
#[rocket::launch]
|
||||||
fn rocket() -> _ {
|
fn rocket() -> _ {
|
||||||
rocket::build()
|
rocket::build()
|
||||||
.mount("/", rocket::routes![routes::index])
|
.mount("/", rocket::routes![routes::index])
|
||||||
.mount("/api", rocket::routes![routes::trigger])
|
.mount("/api", rocket::routes![routes::trigger])
|
||||||
|
.manage(states::DB::new())
|
||||||
|
.manage(states::Config::new())
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
use rocket::{get, post};
|
use rocket::{get, post, State};
|
||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
|
|
||||||
use crate::config;
|
|
||||||
use crate::db;
|
use crate::db;
|
||||||
use crate::guards;
|
use crate::guards;
|
||||||
|
use crate::states;
|
||||||
|
|
||||||
#[get("/<id>")]
|
#[get("/<id>")]
|
||||||
pub fn index(id: i32) -> String {
|
pub fn index(db_state: &State<states::DB>, id: i32) -> String {
|
||||||
let hook_log = db::get_hook_log(id);
|
let hook_log = db::get_hook_log(&db_state.pool, id);
|
||||||
format!(
|
format!(
|
||||||
"Hook log id:
|
"Hook log id:
|
||||||
{}
|
{}
|
||||||
|
@ -44,8 +44,12 @@ Status code:
|
||||||
}
|
}
|
||||||
|
|
||||||
#[post("/trigger", format = "json", data = "<repo>")]
|
#[post("/trigger", format = "json", data = "<repo>")]
|
||||||
pub fn trigger(repo: guards::Repo) -> String {
|
pub fn trigger(
|
||||||
let hook = config::get_hook(repo.clone_url).unwrap();
|
db_state: &State<states::DB>,
|
||||||
|
config_state: &State<states::Config>,
|
||||||
|
repo: guards::Repo,
|
||||||
|
) -> String {
|
||||||
|
let hook = config_state.get_hook(repo.clone_url).unwrap();
|
||||||
|
|
||||||
let output = Command::new(&hook.command)
|
let output = Command::new(&hook.command)
|
||||||
.args(&hook.args)
|
.args(&hook.args)
|
||||||
|
@ -53,9 +57,7 @@ pub fn trigger(repo: guards::Repo) -> String {
|
||||||
.output()
|
.output()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let new_hook_log_id = db::add_hook_log(&hook, &output);
|
let new_hook_log_id = db::add_hook_log(&db_state.pool, &hook, &output);
|
||||||
|
|
||||||
let base_url = config::get_base_url();
|
format!("{}/{new_hook_log_id}", config_state.base_url_with_https)
|
||||||
|
|
||||||
format!("https://{base_url}/{new_hook_log_id}")
|
|
||||||
}
|
}
|
||||||
|
|
36
src/states.rs
Normal file
36
src/states.rs
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
use crate::config;
|
||||||
|
use crate::db;
|
||||||
|
|
||||||
|
pub struct DB {
|
||||||
|
pub pool: db::DBPool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DB {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
let pool = db::establish_connection_pool();
|
||||||
|
|
||||||
|
Self { pool }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Config {
|
||||||
|
pub secret: Vec<u8>,
|
||||||
|
pub base_url_with_https: String,
|
||||||
|
pub hooks: Vec<config::Hook>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Config {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
let config = config::Config::new();
|
||||||
|
|
||||||
|
Self {
|
||||||
|
secret: config.secret.as_bytes().to_owned(),
|
||||||
|
base_url_with_https: format!("https://{}", config.base_url),
|
||||||
|
hooks: config.hooks,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_hook(&self, clone_url: &str) -> Option<&config::Hook> {
|
||||||
|
self.hooks.iter().find(|&hook| hook.repo_url == clone_url)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue