From 24cb485410e2aa54bf60b9eb31644890cdfc3dfd Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Sun, 14 Apr 2024 15:46:21 +0100 Subject: [PATCH] feat(repo/webhook): Handle messages received via webhook for ForgeJo Closes kemitix/git-next#43 --- Cargo.toml | 1 + src/server/actors/repo/mod.rs | 7 +- src/server/actors/repo/status.rs | 4 +- src/server/actors/repo/webhook.rs | 123 ++++++++++++++++++++++++++-- src/server/actors/webhook/router.rs | 6 +- src/server/actors/webhook/server.rs | 4 +- src/server/config.rs | 11 ++- 7 files changed, 141 insertions(+), 15 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 8887256..ef72959 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,6 +32,7 @@ tempfile = "3.10" # TOML parsing serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" toml = "0.8" # Secrets and Password diff --git a/src/server/actors/repo/mod.rs b/src/server/actors/repo/mod.rs index 8c6ebaf..6dd7f37 100644 --- a/src/server/actors/repo/mod.rs +++ b/src/server/actors/repo/mod.rs @@ -20,6 +20,9 @@ pub struct RepoActor { webhook: Webhook, webhook_id: Option, // INFO: if [None] then no webhook is configured webhook_auth: Option, + last_main_commit: Option, + last_next_commit: Option, + last_dev_commit: Option, net: Network, git: Git, } @@ -35,6 +38,9 @@ impl RepoActor { webhook, webhook_id: None, webhook_auth: None, + last_main_commit: None, + last_next_commit: None, + last_dev_commit: None, net, git, } @@ -81,7 +87,6 @@ impl Handler for RepoActor { info!(%self.details, %config, "Config loaded"); self.details.config.replace(config); if self.webhook_id.is_none() { - info!("lets register the webhook..."); webhook::register( self.details.clone(), self.webhook.clone(), diff --git a/src/server/actors/repo/status.rs b/src/server/actors/repo/status.rs index 3c7c157..e298f26 100644 --- a/src/server/actors/repo/status.rs +++ b/src/server/actors/repo/status.rs @@ -31,7 +31,9 @@ pub async fn check_next( Status::Pass => { addr.do_send(AdvanceMainTo(next)); } - Status::Pending => {} + Status::Pending => { + // TODO: (#48) reschedule a check after a few seconds + } Status::Fail => { warn!("Checks have failed"); } diff --git a/src/server/actors/repo/webhook.rs b/src/server/actors/repo/webhook.rs index 636ea7a..f51fcb4 100644 --- a/src/server/actors/repo/webhook.rs +++ b/src/server/actors/repo/webhook.rs @@ -1,15 +1,16 @@ use actix::prelude::*; use kxio::network::{self, json}; -use tracing::{info, warn}; +use tracing::{debug, info, warn}; use std::{fmt::Display, ops::Deref}; use crate::server::{ actors::{ - repo::{RepoActor, WebhookRegistered}, + repo::{RepoActor, ValidateRepo, WebhookRegistered}, webhook::WebhookMessage, }, - config::Webhook, + config::{RepoBranches, Webhook}, + forge, }; #[derive(Clone, Debug, PartialEq, Eq)] @@ -127,10 +128,120 @@ impl Hook { impl Handler for RepoActor { type Result = (); - fn handle(&mut self, msg: WebhookMessage, _ctx: &mut Self::Context) -> Self::Result { + #[allow(clippy::cognitive_complexity)] // TODO: (#49) reduce complexity + fn handle(&mut self, msg: WebhookMessage, ctx: &mut Self::Context) -> Self::Result { let id = msg.id(); let body = msg.body(); - info!(?id, ?body, "RepoActor received message"); - // TODO: (#43) do something with the message + debug!(?id, "RepoActor received message"); + match serde_json::from_str::(body) { + Err(err) => debug!(?err, %body, "Not a 'push'"), + Ok(push) => { + if let Some(config) = &self.details.config { + match push.branch(config.branches()) { + None => warn!( + ?push, + "Unrecognised branch, we should be filtering to only the ones we want" + ), + Some(branch) => { + match branch { + Branch::Main => { + if self.last_main_commit == Some(push.commit()) { + info!( + ?id, + "Ignoring - already aware of branch '{}' at commit '{}'", + config.branches().main(), + push.commit() + ); + return; + } + self.last_main_commit.replace(push.commit()) + } + Branch::Next => { + if self.last_next_commit == Some(push.commit()) { + info!( + ?id, + "Ignoring - already aware of branch '{}' at commit '{}'", + config.branches().next(), + push.commit() + ); + return; + } + self.last_next_commit.replace(push.commit()) + } + Branch::Dev => { + if self.last_dev_commit == Some(push.commit()) { + info!( + ?id, + "Ignoring - already aware of branch '{}' at commit '{}'", + config.branches().dev(), + push.commit() + ); + return; + } + self.last_dev_commit.replace(push.commit()) + } + }; + info!( + ?branch, + commit = %push.commit(), + "New commit for branch - assessing branch positions" + ); + ctx.address().do_send(ValidateRepo); + } + } + } + } + } + } +} + +#[derive(Debug, serde::Deserialize)] +struct Push { + #[serde(rename = "ref")] + reference: String, + after: String, + head_commit: HeadCommit, +} +impl Push { + pub fn branch(&self, repo_branches: &RepoBranches) -> Option { + if !self.reference.starts_with("refs/heads/") { + warn!(r#ref = self.reference, "Unexpected ref"); + return None; + } + let (_, branch) = self.reference.split_at(11); + if branch == *repo_branches.main() { + return Some(Branch::Main); + } + if branch == *repo_branches.next() { + return Some(Branch::Next); + } + if branch == *repo_branches.dev() { + return Some(Branch::Dev); + } + warn!(branch, "Unexpected branch"); + None + } + pub fn commit(&self) -> forge::Commit { + forge::Commit::new(&self.after, &self.head_commit.message) + } +} + +#[derive(Debug)] +enum Branch { + Main, + Next, + Dev, +} + +#[derive(Debug, serde::Deserialize)] +struct HeadCommit { + message: String, +} + +#[cfg(test)] +mod tests { + #[test] + fn splt_ref() { + assert_eq!("refs/heads/next".split_at(11), ("refs/heads/", "next")); } } diff --git a/src/server/actors/webhook/router.rs b/src/server/actors/webhook/router.rs index 4b23fa2..987b90b 100644 --- a/src/server/actors/webhook/router.rs +++ b/src/server/actors/webhook/router.rs @@ -1,7 +1,7 @@ use std::collections::HashMap; use actix::prelude::*; -use tracing::info; +use tracing::debug; use crate::server::{actors::webhook::message::WebhookMessage, config::RepoAlias}; @@ -23,9 +23,9 @@ impl Handler for WebhookRouter { fn handle(&mut self, msg: WebhookMessage, _ctx: &mut Self::Context) -> Self::Result { let repo_alias = RepoAlias(msg.path().clone()); - info!(?repo_alias, "router..."); + debug!(?repo_alias, "Router..."); if let Some(recipient) = self.repos.get(&repo_alias) { - info!("Sending to recipient"); + debug!("Sending to recipient"); recipient.do_send(msg); } } diff --git a/src/server/actors/webhook/server.rs b/src/server/actors/webhook/server.rs index fb34790..51d8c2b 100644 --- a/src/server/actors/webhook/server.rs +++ b/src/server/actors/webhook/server.rs @@ -1,6 +1,6 @@ use actix::prelude::*; -use tracing::info; +use tracing::{debug, info}; use crate::server::actors::webhook::message::WebhookMessage; @@ -23,7 +23,7 @@ pub async fn start(address: actix::prelude::Recipient WebhookUrl { WebhookUrl(self.url.clone()) } @@ -65,7 +65,7 @@ impl RepoConfig { toml::from_str(toml).map_err(OneOf::new) } - pub(crate) const fn branches(&self) -> &RepoBranches { + pub const fn branches(&self) -> &RepoBranches { &self.branches } } @@ -285,6 +285,13 @@ impl Display for BranchName { write!(f, "{}", self.0) } } +impl Deref for BranchName { + type Target = String; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} /// The derived information about a repo, used to interact with it #[derive(Clone, Debug)]