From 8e94435604a79a78592d6ae9d0ca284230552f4c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maxime=20=E2=80=9Cpep=E2=80=9D=20Buquet?= Date: Fri, 19 Apr 2024 20:49:31 +0200 Subject: [PATCH] Attempt at Forgejo Webhook support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Maxime “pep” Buquet --- Cargo.toml | 4 +++ forgejo-hooks/Cargo.toml | 7 +++++ forgejo-hooks/src/lib.rs | 63 ++++++++++++++++++++++++++++++++++++++++ src/bot.rs | 4 +-- src/main.rs | 8 ++--- src/web.rs | 30 ++++++++++++------- src/webhook.rs | 35 +++++++++++++++------- 7 files changed, 124 insertions(+), 27 deletions(-) create mode 100644 forgejo-hooks/Cargo.toml create mode 100644 forgejo-hooks/src/lib.rs diff --git a/Cargo.toml b/Cargo.toml index 82d2ca2..c6deaee 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,6 +8,7 @@ license = "AGPL-3.0+" [dependencies] clap = { version = "4.5", features = [ "cargo" ] } +forgejo-hooks = "*" gitlab = "0.1610" hyper = { version = "1.4", default-features = false, features = [ "http1", "server" ] } hyper-util = { version = "0.1", features = [ "tokio" ] } @@ -21,3 +22,6 @@ serde = { version = "1.0", features = [ "derive" ] } serde_json = "1.0" toml = "0.8" xmpp = "0.5" + +[patch.crates-io] +forgejo-hooks = { path = "forgejo-hooks" } diff --git a/forgejo-hooks/Cargo.toml b/forgejo-hooks/Cargo.toml new file mode 100644 index 0000000..2b8444e --- /dev/null +++ b/forgejo-hooks/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "forgejo-hooks" +version = "0.1.0" +edition = "2021" + +[dependencies] +serde = { version = "1.0", features = ["derive"] } diff --git a/forgejo-hooks/src/lib.rs b/forgejo-hooks/src/lib.rs new file mode 100644 index 0000000..c8917a7 --- /dev/null +++ b/forgejo-hooks/src/lib.rs @@ -0,0 +1,63 @@ + +use serde::Deserialize; + +#[derive(Deserialize, Debug)] +pub struct CommitAuthor { + pub name: String, + pub email: String, + pub username: String, +} + +#[derive(Deserialize, Debug)] +pub struct User { + pub id: u32, + pub login: String, + pub full_name: String, + pub email: String, + pub avatar_url: String, + pub username: String, +} + +#[derive(Deserialize, Debug)] +pub struct Commit { + pub id: String, + pub message: String, + pub url: String, + pub author: CommitAuthor, + pub committer: CommitAuthor, + pub timestamp: String, +} + +#[derive(Deserialize, Debug)] +pub struct Repository { + pub id: u32, + pub owner: User, + pub name: String, + pub full_name: String, + pub description: String, + pub private: bool, + pub fork: bool, + pub html_url: String, + pub ssh_url: String, + pub clone_url: String, + pub website: String, + pub stars_count: u32, + pub forks_count: u32, + pub watchers_count: u32, + pub open_issues_count: u32, + pub default_branch: String, + pub created_at: String, + pub updated_at: String, +} + +#[derive(Deserialize, Debug)] +pub struct Hook { + #[serde(rename(deserialize = "ref"))] + pub ref_: String, + pub before: String, + pub compare_url: String, + pub commits: Vec, + pub repository: Repository, + pub pusher: User, + pub sender: User, +} diff --git a/src/bot.rs b/src/bot.rs index 4630d2a..8de5e87 100644 --- a/src/bot.rs +++ b/src/bot.rs @@ -13,7 +13,7 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -use crate::webhook::{format_webhook, WebHook}; +use crate::webhook::{format_webhook, GitlabHook}; use log::debug; use xmpp::parsers::message::MessageType; @@ -74,7 +74,7 @@ impl XmppClient { } } - pub async fn webhook(&mut self, wh: WebHook) { + pub async fn webhook(&mut self, wh: GitlabHook) { debug!("Received Webhook"); if let Some(display) = format_webhook(&wh) { debug!("Webhook: {}", display); diff --git a/src/main.rs b/src/main.rs index e691bca..3e50df6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -24,7 +24,7 @@ mod webhook; use crate::bot::XmppClient; use crate::error::Error; use crate::web::webhooks; -use crate::webhook::WebHook; +use crate::webhook::Hook; use std::fs::File; use std::io::{Error as IoError, ErrorKind as IoErrorKind, Read}; @@ -130,7 +130,7 @@ async fn main() -> Result { } }; - let (value_tx, mut value_rx) = mpsc::unbounded_channel::(); + let (value_tx, mut value_rx) = mpsc::unbounded_channel::(); let mut client = XmppClient::new( config.jid, @@ -169,8 +169,8 @@ async fn main() -> Result { } } wh = value_rx.recv() => { - if let Some(wh) = wh { - client.webhook(wh).await + if let Some(Hook::Gitlab(hook)) = wh { + client.webhook(hook).await } } } diff --git a/src/web.rs b/src/web.rs index a4d4add..19b0e05 100644 --- a/src/web.rs +++ b/src/web.rs @@ -14,7 +14,7 @@ // along with this program. If not, see . use crate::error::Error; -use crate::webhook::WebHook; +use crate::webhook::{ForgejoHook, GitlabHook, Hook}; use std::convert::Infallible; use std::sync::{Arc, Mutex}; @@ -36,14 +36,13 @@ fn error_res(e: E) -> Result>, Infallib Ok(res) } -async fn webhooks_inner(req: Request, token: &str) -> Result { +async fn webhooks_inner(req: Request, token: &str) -> Result { match req.method() { &Method::POST => (), _ => return Err(Error::MethodMismatch), } debug!("Headers: {:?}", req.headers()); - let headers = req.headers(); if let Some(content_type) = headers.get(header::CONTENT_TYPE) && content_type != "application/json" @@ -51,27 +50,36 @@ async fn webhooks_inner(req: Request, token: &str) -> Result (), - _ => return Err(Error::InvalidToken), - } + if let Some(val) = headers.get("X-Gitlab-Token") + && token != val + { + return Err(Error::InvalidToken); + } + + if let Some(content_type) = headers.get(header::CONTENT_TYPE) + && content_type != "application/json" + { + return Err(Error::InvalidContentType); } let whole_body = req.collect().await?.aggregate(); - Ok(serde_json::from_reader(whole_body.reader())?) + let hook = serde_json::from_reader(whole_body.reader())?; + Ok(Hook::Gitlab(hook)) } pub async fn webhooks( req: Request, token: &str, - value_tx: Arc>>, + value_tx: Arc>>, ) -> Result>, Infallible> { match webhooks_inner(req, token).await { Ok(wh) => { debug!("Passed: {:?}", wh); - value_tx.lock().unwrap().send(wh).unwrap(); + match wh { + hook @ Hook::Gitlab(_) => value_tx.lock().unwrap().send(hook).unwrap(), + _ => (), + } Ok(Response::new(Full::new(Bytes::from("Hello, World!")))) } diff --git a/src/webhook.rs b/src/webhook.rs index f4a348a..ef5a2fa 100644 --- a/src/webhook.rs +++ b/src/webhook.rs @@ -13,12 +13,27 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -pub use gitlab::webhooks::{IssueAction, MergeRequestAction, WebHook, WikiPageAction}; +pub use forgejo_hooks::Hook as ForgejoHook; +pub use gitlab::webhooks::{ + IssueAction, MergeRequestAction, WebHook as GitlabHook, WikiPageAction, +}; use log::debug; -pub fn format_webhook(wh: &WebHook) -> Option { - Some(match wh { - WebHook::Push(push) => { +#[derive(Debug)] +pub enum Hook { + Forgejo(ForgejoHook), + Gitlab(GitlabHook), +} + +impl From for Hook { + fn from(hook: GitlabHook) -> Hook { + Hook::Gitlab(hook) + } +} + +pub fn format_webhook(glh: &GitlabHook) -> Option { + Some(match glh { + GitlabHook::Push(push) => { if push.ref_ != "refs/heads/main" { // Ignore: Action not on 'main' branch return None; @@ -45,7 +60,7 @@ pub fn format_webhook(wh: &WebHook) -> Option { } text } - WebHook::Issue(issue) => { + GitlabHook::Issue(issue) => { let action = match issue.object_attributes.action { Some(IssueAction::Update) => return None, Some(IssueAction::Open) => "opened", @@ -68,7 +83,7 @@ pub fn format_webhook(wh: &WebHook) -> Option { .unwrap_or("".to_owned()) ) } - WebHook::MergeRequest(merge_req) => { + GitlabHook::MergeRequest(merge_req) => { let action = match merge_req.object_attributes.action { Some(MergeRequestAction::Update) => return None, Some(MergeRequestAction::Open) => "opened", @@ -93,7 +108,7 @@ pub fn format_webhook(wh: &WebHook) -> Option { .unwrap_or("".to_owned()) ) } - WebHook::Note(note) => { + GitlabHook::Note(note) => { if let Some(_) = note.snippet { return None; } @@ -120,11 +135,11 @@ pub fn format_webhook(wh: &WebHook) -> Option { unreachable!() } } - WebHook::Build(build) => { + GitlabHook::Build(build) => { println!("Build: {:?}", build); return None; } - WebHook::WikiPage(page) => { + GitlabHook::WikiPage(page) => { let action = match page.object_attributes.action { WikiPageAction::Update => "updated", WikiPageAction::Create => "created", @@ -138,7 +153,7 @@ pub fn format_webhook(wh: &WebHook) -> Option { page.object_attributes.url, ) } - _wh => { + _glh => { debug!("Webhook not supported"); return None; }