feat: Webhook query paths include forge alias

This allows for more than one forge to be configured and for the webhook
to correctly route incoming messages.
This commit is contained in:
Paul Campbell 2024-05-29 19:22:05 +01:00
parent 17148e74b6
commit 206e64cd5b
17 changed files with 119 additions and 80 deletions

View file

@ -1,5 +1,5 @@
use crate::{ use crate::{
ApiToken, BranchName, ForgeDetails, ForgeName, ForgeType, Hostname, RepoAlias, RepoBranches, ApiToken, BranchName, ForgeAlias, ForgeDetails, ForgeType, Hostname, RepoAlias, RepoBranches,
RepoConfig, RepoConfigSource, RepoPath, User, RepoConfig, RepoConfigSource, RepoPath, User,
}; };
@ -25,8 +25,8 @@ pub fn hostname(n: u32) -> Hostname {
Hostname::new(format!("hostname-{}", n)) Hostname::new(format!("hostname-{}", n))
} }
pub fn forge_name(n: u32) -> ForgeName { pub fn forge_name(n: u32) -> ForgeAlias {
ForgeName::new(format!("forge-name-{}", n)) ForgeAlias::new(format!("forge-name-{}", n))
} }
pub fn branch_name(n: u32) -> BranchName { pub fn branch_name(n: u32) -> BranchName {

View file

@ -1,9 +1,9 @@
use crate::{ApiToken, ForgeConfig, ForgeName, ForgeType, Hostname, User}; use crate::{ApiToken, ForgeAlias, ForgeConfig, ForgeType, Hostname, User};
/// The derived information about a Forge, used to create interactions with it /// The derived information about a Forge, used to create interactions with it
#[derive(Clone, Default, Debug, derive_more::Constructor, derive_with::With)] #[derive(Clone, Default, Debug, derive_more::Constructor, derive_with::With)]
pub struct ForgeDetails { pub struct ForgeDetails {
forge_name: ForgeName, forge_alias: ForgeAlias,
forge_type: ForgeType, forge_type: ForgeType,
hostname: Hostname, hostname: Hostname,
user: User, user: User,
@ -12,8 +12,8 @@ pub struct ForgeDetails {
// Private SSH Key Path // Private SSH Key Path
} }
impl ForgeDetails { impl ForgeDetails {
pub const fn forge_name(&self) -> &ForgeName { pub const fn forge_alias(&self) -> &ForgeAlias {
&self.forge_name &self.forge_alias
} }
pub const fn forge_type(&self) -> ForgeType { pub const fn forge_type(&self) -> ForgeType {
self.forge_type self.forge_type
@ -28,10 +28,10 @@ impl ForgeDetails {
&self.token &self.token
} }
} }
impl From<(&ForgeName, &ForgeConfig)> for ForgeDetails { impl From<(&ForgeAlias, &ForgeConfig)> for ForgeDetails {
fn from(forge: (&ForgeName, &ForgeConfig)) -> Self { fn from(forge: (&ForgeAlias, &ForgeConfig)) -> Self {
Self { Self {
forge_name: forge.0.clone(), forge_alias: forge.0.clone(),
forge_type: forge.1.forge_type(), forge_type: forge.1.forge_type(),
hostname: forge.1.hostname(), hostname: forge.1.hostname(),
user: forge.1.user(), user: forge.1.user(),

View file

@ -1,10 +1,12 @@
use std::path::PathBuf; use std::path::PathBuf;
/// The name of a Forge to connect to /// The name of a Forge to connect to
#[derive(Clone, Default, Debug, PartialEq, Eq, derive_more::Constructor, derive_more::Display)] #[derive(
pub struct ForgeName(String); Clone, Default, Debug, Hash, PartialEq, Eq, derive_more::Constructor, derive_more::Display,
impl From<&ForgeName> for PathBuf { )]
fn from(value: &ForgeName) -> Self { pub struct ForgeAlias(String);
impl From<&ForgeAlias> for PathBuf {
fn from(value: &ForgeAlias) -> Self {
Self::from(&value.0) Self::from(&value.0)
} }
} }

View file

@ -24,7 +24,7 @@ pub use api_token::ApiToken;
pub use branch_name::BranchName; pub use branch_name::BranchName;
pub use forge_config::ForgeConfig; pub use forge_config::ForgeConfig;
pub use forge_details::ForgeDetails; pub use forge_details::ForgeDetails;
pub use forge_name::ForgeName; pub use forge_name::ForgeAlias;
pub use forge_type::ForgeType; pub use forge_type::ForgeType;
pub use git_dir::GitDir; pub use git_dir::GitDir;
pub use host_name::Hostname; pub use host_name::Hostname;

View file

@ -12,7 +12,7 @@ use std::{
use kxio::fs::FileSystem; use kxio::fs::FileSystem;
use tracing::info; use tracing::info;
use crate::{ForgeConfig, ForgeName}; use crate::{ForgeAlias, ForgeConfig, RepoAlias};
#[derive(Debug, derive_more::From, derive_more::Display)] #[derive(Debug, derive_more::From, derive_more::Display)]
pub enum Error { pub enum Error {
@ -43,10 +43,10 @@ impl ServerConfig {
toml::from_str(&str).map_err(Into::into) toml::from_str(&str).map_err(Into::into)
} }
pub fn forges(&self) -> impl Iterator<Item = (ForgeName, &ForgeConfig)> { pub fn forges(&self) -> impl Iterator<Item = (ForgeAlias, &ForgeConfig)> {
self.forge self.forge
.iter() .iter()
.map(|(name, forge)| (ForgeName::new(name.clone()), forge)) .map(|(alias, forge)| (ForgeAlias::new(alias.clone()), forge))
} }
pub const fn storage(&self) -> &ServerStorage { pub const fn storage(&self) -> &ServerStorage {
@ -84,8 +84,9 @@ pub struct Webhook {
url: String, url: String,
} }
impl Webhook { impl Webhook {
pub fn url(&self) -> WebhookUrl { pub fn url(&self, forge_alias: &ForgeAlias, repo_alias: &RepoAlias) -> WebhookUrl {
WebhookUrl(self.url.clone()) let base_url = &self.url;
WebhookUrl(format!("{base_url}/{forge_alias}/{repo_alias}"))
} }
} }

View file

@ -252,7 +252,7 @@ mod forge_details {
use secrecy::ExposeSecret; use secrecy::ExposeSecret;
use crate::{ApiToken, ForgeConfig, ForgeDetails, ForgeName, ForgeType, Hostname, User}; use crate::{ApiToken, ForgeAlias, ForgeConfig, ForgeDetails, ForgeType, Hostname, User};
#[test] #[test]
fn should_return_forge_name() { fn should_return_forge_name() {
@ -260,11 +260,11 @@ mod forge_details {
let hostname = Hostname::new("localhost".to_string()); let hostname = Hostname::new("localhost".to_string());
let user = User::new("bob".to_string()); let user = User::new("bob".to_string());
let token = ApiToken::new("alpha".to_string().into()); let token = ApiToken::new("alpha".to_string().into());
let forge_name = ForgeName::new("gamma".to_string()); let forge_name = ForgeAlias::new("gamma".to_string());
let forge_details = let forge_details =
ForgeDetails::new(forge_name.clone(), forge_type, hostname, user, token); ForgeDetails::new(forge_name.clone(), forge_type, hostname, user, token);
let result = forge_details.forge_name(); let result = forge_details.forge_alias();
assert_eq!(result, &forge_name); assert_eq!(result, &forge_name);
} }
@ -274,7 +274,7 @@ mod forge_details {
let hostname = Hostname::new("localhost".to_string()); let hostname = Hostname::new("localhost".to_string());
let user = User::new("bob".to_string()); let user = User::new("bob".to_string());
let token = ApiToken::new("alpha".to_string().into()); let token = ApiToken::new("alpha".to_string().into());
let forge_name = ForgeName::new("gamma".to_string()); let forge_name = ForgeAlias::new("gamma".to_string());
let forge_details = ForgeDetails::new(forge_name, forge_type, hostname, user, token); let forge_details = ForgeDetails::new(forge_name, forge_type, hostname, user, token);
let result = forge_details.forge_type(); let result = forge_details.forge_type();
@ -287,7 +287,7 @@ mod forge_details {
let hostname = Hostname::new("localhost".to_string()); let hostname = Hostname::new("localhost".to_string());
let user = User::new("bob".to_string()); let user = User::new("bob".to_string());
let token = ApiToken::new("alpha".to_string().into()); let token = ApiToken::new("alpha".to_string().into());
let forge_name = ForgeName::new("gamma".to_string()); let forge_name = ForgeAlias::new("gamma".to_string());
let forge_details = let forge_details =
ForgeDetails::new(forge_name, forge_type, hostname.clone(), user, token); ForgeDetails::new(forge_name, forge_type, hostname.clone(), user, token);
@ -301,7 +301,7 @@ mod forge_details {
let hostname = Hostname::new("localhost".to_string()); let hostname = Hostname::new("localhost".to_string());
let user = User::new("bob".to_string()); let user = User::new("bob".to_string());
let token = ApiToken::new("alpha".to_string().into()); let token = ApiToken::new("alpha".to_string().into());
let forge_name = ForgeName::new("gamma".to_string()); let forge_name = ForgeAlias::new("gamma".to_string());
let forge_details = let forge_details =
ForgeDetails::new(forge_name, forge_type, hostname, user.clone(), token); ForgeDetails::new(forge_name, forge_type, hostname, user.clone(), token);
@ -315,7 +315,7 @@ mod forge_details {
let hostname = Hostname::new("localhost".to_string()); let hostname = Hostname::new("localhost".to_string());
let user = User::new("bob".to_string()); let user = User::new("bob".to_string());
let token = ApiToken::new("alpha".to_string().into()); let token = ApiToken::new("alpha".to_string().into());
let forge_name = ForgeName::new("gamma".to_string()); let forge_name = ForgeAlias::new("gamma".to_string());
let forge_details = let forge_details =
ForgeDetails::new(forge_name, forge_type, hostname, user, token.clone()); ForgeDetails::new(forge_name, forge_type, hostname, user, token.clone());
@ -329,7 +329,7 @@ mod forge_details {
let hostname = Hostname::new("localhost".to_string()); let hostname = Hostname::new("localhost".to_string());
let user = User::new("bob".to_string()); let user = User::new("bob".to_string());
let token = ApiToken::new("alpha".to_string().into()); let token = ApiToken::new("alpha".to_string().into());
let forge_name = ForgeName::new("gamma".to_string()); let forge_name = ForgeAlias::new("gamma".to_string());
let forge_details = ForgeDetails::new(forge_name, forge_type, hostname, user, token); let forge_details = ForgeDetails::new(forge_name, forge_type, hostname, user, token);
let result = forge_details.with_hostname(Hostname::new("remotehost".to_string())); let result = forge_details.with_hostname(Hostname::new("remotehost".to_string()));
@ -342,7 +342,7 @@ mod forge_details {
let hostname = Hostname::new("localhost".to_string()); let hostname = Hostname::new("localhost".to_string());
let user = User::new("bob".to_string()); let user = User::new("bob".to_string());
let token = ApiToken::new("alpha".to_string().into()); let token = ApiToken::new("alpha".to_string().into());
let forge_name = ForgeName::new("gamma".to_string()); let forge_alias = ForgeAlias::new("gamma".to_string());
let forge_config = ForgeConfig::new( let forge_config = ForgeConfig::new(
forge_type, forge_type,
"localhost".to_string(), "localhost".to_string(),
@ -351,9 +351,9 @@ mod forge_details {
BTreeMap::new(), BTreeMap::new(),
); );
let forge_details = ForgeDetails::from((&forge_name, &forge_config)); let forge_details = ForgeDetails::from((&forge_alias, &forge_config));
assert_eq!(forge_details.forge_name(), &forge_name); assert_eq!(forge_details.forge_alias(), &forge_alias);
assert_eq!(forge_details.hostname(), &hostname); assert_eq!(forge_details.hostname(), &hostname);
assert_eq!(forge_details.user(), &user); assert_eq!(forge_details.user(), &user);
assert_eq!(forge_details.token().expose_secret(), token.expose_secret()); assert_eq!(forge_details.token().expose_secret(), token.expose_secret());
@ -362,13 +362,13 @@ mod forge_details {
mod forge_name { mod forge_name {
use std::path::PathBuf; use std::path::PathBuf;
use crate::ForgeName; use crate::ForgeAlias;
#[test] #[test]
fn should_convert_to_pathbuf() { fn should_convert_to_pathbuf() {
let forge_name = ForgeName::new("alpha".to_string()); let forge_alias = ForgeAlias::new("alpha".to_string());
let pathbuf: PathBuf = (&forge_name).into(); let pathbuf: PathBuf = (&forge_alias).into();
assert_eq!(pathbuf, PathBuf::new().join("alpha")); assert_eq!(pathbuf, PathBuf::new().join("alpha"));
} }

View file

@ -15,7 +15,7 @@ impl ForgeJo {
} }
#[async_trait::async_trait] #[async_trait::async_trait]
impl git::ForgeLike for ForgeJo { impl git::ForgeLike for ForgeJo {
fn name(&self) -> String { fn forge_alias(&self) -> String {
"forgejo".to_string() "forgejo".to_string()
} }

View file

@ -13,7 +13,7 @@ impl MockForgeEnv {
} }
#[async_trait::async_trait] #[async_trait::async_trait]
impl git::ForgeLike for MockForgeEnv { impl git::ForgeLike for MockForgeEnv {
fn name(&self) -> String { fn forge_alias(&self) -> String {
"mock".to_string() "mock".to_string()
} }

View file

@ -10,7 +10,7 @@ mod github;
#[test] #[test]
fn test_mock_name() { fn test_mock_name() {
let forge = Forge::new_mock(); let forge = Forge::new_mock();
assert_eq!(forge.name(), "mock"); assert_eq!(forge.forge_alias(), "mock");
} }
#[test] #[test]
@ -30,5 +30,5 @@ fn test_forgejo_name() {
config::GitDir::new(fs.base()), config::GitDir::new(fs.base()),
); );
let forge = Forge::new_forgejo(repo_details, net); let forge = Forge::new_forgejo(repo_details, net);
assert_eq!(forge.name(), "forgejo"); assert_eq!(forge.forge_alias(), "forgejo");
} }

View file

@ -2,7 +2,7 @@ use crate as git;
#[async_trait::async_trait] #[async_trait::async_trait]
pub trait ForgeLike { pub trait ForgeLike {
fn name(&self) -> String; fn forge_alias(&self) -> String;
/// Checks the results of any (e.g. CI) status checks for the commit. /// Checks the results of any (e.g. CI) status checks for the commit.
async fn commit_status(&self, commit: &git::Commit) -> git::commit::Status; async fn commit_status(&self, commit: &git::Commit) -> git::commit::Status;

View file

@ -1,5 +1,5 @@
use git_next_config::{ use git_next_config::{
BranchName, ForgeConfig, ForgeDetails, ForgeName, GitDir, RepoAlias, RepoConfig, RepoPath, BranchName, ForgeAlias, ForgeConfig, ForgeDetails, GitDir, RepoAlias, RepoConfig, RepoPath,
ServerRepoConfig, ServerRepoConfig,
}; };
@ -8,7 +8,7 @@ use super::{Generation, GitRemote};
/// The derived information about a repo, used to interact with it /// The derived information about a repo, used to interact with it
#[derive(Clone, Default, Debug, derive_more::Display, derive_with::With)] #[derive(Clone, Default, Debug, derive_more::Display, derive_with::With)]
#[display("gen-{}:{}:{}/{}:{}@{}/{}@{}", generation, forge.forge_type(), #[display("gen-{}:{}:{}/{}:{}@{}/{}@{}", generation, forge.forge_type(),
forge.forge_name(), repo_alias, forge.user(), forge.hostname(), repo_path, forge.forge_alias(), repo_alias, forge.user(), forge.hostname(), repo_path,
branch)] branch)]
pub struct RepoDetails { pub struct RepoDetails {
pub generation: Generation, pub generation: Generation,
@ -24,7 +24,7 @@ impl RepoDetails {
generation: Generation, generation: Generation,
repo_alias: &RepoAlias, repo_alias: &RepoAlias,
server_repo_config: &ServerRepoConfig, server_repo_config: &ServerRepoConfig,
forge_name: &ForgeName, forge_alias: &ForgeAlias,
forge_config: &ForgeConfig, forge_config: &ForgeConfig,
gitdir: GitDir, gitdir: GitDir,
) -> Self { ) -> Self {
@ -36,7 +36,7 @@ impl RepoDetails {
branch: server_repo_config.branch(), branch: server_repo_config.branch(),
gitdir, gitdir,
forge: ForgeDetails::new( forge: ForgeDetails::new(
forge_name.clone(), forge_alias.clone(),
forge_config.forge_type(), forge_config.forge_type(),
forge_config.hostname(), forge_config.hostname(),
forge_config.user(), forge_config.user(),

View file

@ -92,7 +92,7 @@ mod repo_details {
use std::{collections::BTreeMap, path::PathBuf}; use std::{collections::BTreeMap, path::PathBuf};
use git_next_config::{ use git_next_config::{
ForgeConfig, ForgeName, ForgeType, GitDir, Hostname, RepoAlias, RepoPath, ServerRepoConfig, ForgeAlias, ForgeConfig, ForgeType, GitDir, Hostname, RepoAlias, RepoPath, ServerRepoConfig,
}; };
use secrecy::ExposeSecret; use secrecy::ExposeSecret;
@ -111,7 +111,7 @@ mod repo_details {
None, None,
None, None,
), ),
&ForgeName::new("default".to_string()), &ForgeAlias::new("default".to_string()),
&ForgeConfig::new( &ForgeConfig::new(
ForgeType::MockForge, ForgeType::MockForge,
"host".to_string(), "host".to_string(),
@ -140,7 +140,7 @@ mod repo_details {
None, None,
None, None,
), ),
&ForgeName::new("default".to_string()), &ForgeAlias::new("default".to_string()),
&ForgeConfig::new( &ForgeConfig::new(
ForgeType::MockForge, ForgeType::MockForge,
"host".to_string(), "host".to_string(),

View file

@ -20,7 +20,7 @@ use kxio::network::Network;
use tracing::{debug, info, warn, Instrument}; use tracing::{debug, info, warn, Instrument};
#[derive(Debug, derive_more::Display)] #[derive(Debug, derive_more::Display)]
#[display("{}:{}:{}", generation, repo_details.forge.forge_name(), repo_details.repo_alias)] #[display("{}:{}:{}", generation, repo_details.forge.forge_alias(), repo_details.repo_alias)]
pub struct RepoActor { pub struct RepoActor {
generation: git::Generation, generation: git::Generation,
message_token: MessageToken, message_token: MessageToken,

View file

@ -1,7 +1,7 @@
use actix::prelude::*; use actix::prelude::*;
use git_next_config::{ use git_next_config::{
server::{Webhook, WebhookUrl}, server::{Webhook, WebhookUrl},
BranchName, RepoAlias, RepoBranches, BranchName, ForgeAlias, RepoAlias, RepoBranches,
}; };
use git_next_git as git; use git_next_git as git;
use kxio::network::{self, json}; use kxio::network::{self, json};
@ -75,7 +75,9 @@ pub async fn register(
return; return;
}; };
let webhook_url = webhook.url(); let forge_alias = repo_details.forge.forge_alias();
let repo_alias = &repo_details.repo_alias;
let webhook_url = webhook.url(forge_alias, repo_alias);
// remove any lingering webhooks for the same URL // remove any lingering webhooks for the same URL
let existing_webhook_ids = find_existing_webhooks(&repo_details, &webhook_url, &net).await; let existing_webhook_ids = find_existing_webhooks(&repo_details, &webhook_url, &net).await;
for webhook_id in existing_webhook_ids { for webhook_id in existing_webhook_ids {
@ -89,7 +91,6 @@ pub async fn register(
let url = network::NetUrl::new(format!( let url = network::NetUrl::new(format!(
"https://{hostname}/api/v1/repos/{repo_path}/hooks?token={token}" "https://{hostname}/api/v1/repos/{repo_path}/hooks?token={token}"
)); ));
let repo_alias = &repo_details.repo_alias;
let headers = network::NetRequestHeaders::new().with("Content-Type", "application/json"); let headers = network::NetRequestHeaders::new().with("Content-Type", "application/json");
let authorisation = WebhookAuth::generate(); let authorisation = WebhookAuth::generate();
let body = json!({ let body = json!({
@ -98,7 +99,7 @@ pub async fn register(
"branch_filter": format!("{{{},{},{}}}", repo_config.branches().main(), repo_config.branches().next(), repo_config.branches().dev()), "branch_filter": format!("{{{},{},{}}}", repo_config.branches().main(), repo_config.branches().next(), repo_config.branches().dev()),
"config": { "config": {
"content_type": "json", "content_type": "json",
"url": format!("{}/{}", webhook_url.as_ref(), repo_alias), "url": webhook_url.as_ref(),
}, },
"events": [ "push" ], "events": [ "push" ],
"type": "forgejo" "type": "forgejo"
@ -156,7 +157,7 @@ async fn find_existing_webhooks(
} }
for hook in list { for hook in list {
if let Some(existing_url) = hook.config.get("url") { if let Some(existing_url) = hook.config.get("url") {
if existing_url.starts_with(webhook_url.as_ref()) { if existing_url == webhook_url.as_ref() {
ids.push(hook.id()); ids.push(hook.id());
} }
} }
@ -312,12 +313,15 @@ struct HeadCommit {
#[derive(Message, Debug, Clone, derive_more::Constructor)] #[derive(Message, Debug, Clone, derive_more::Constructor)]
#[rtype(result = "()")] #[rtype(result = "()")]
pub struct WebhookMessage { pub struct WebhookMessage {
// forge // TODO: (#58) differentiate between multiple forges forge_alias: ForgeAlias,
repo_alias: RepoAlias, repo_alias: RepoAlias,
authorisation: WebhookAuth, authorisation: WebhookAuth,
body: Body, body: Body,
} }
impl WebhookMessage { impl WebhookMessage {
pub const fn forge_alias(&self) -> &ForgeAlias {
&self.forge_alias
}
pub const fn repo_alias(&self) -> &RepoAlias { pub const fn repo_alias(&self) -> &RepoAlias {
&self.repo_alias &self.repo_alias
} }

View file

@ -5,7 +5,7 @@ use actix::prelude::*;
use config::server::{ServerConfig, ServerStorage, Webhook}; use config::server::{ServerConfig, ServerStorage, Webhook};
use git_next_config::{ use git_next_config::{
self as config, ForgeConfig, ForgeName, GitDir, RepoAlias, ServerRepoConfig, self as config, ForgeAlias, ForgeConfig, GitDir, RepoAlias, ServerRepoConfig,
}; };
use git_next_git::{Generation, RepoDetails, Repository}; use git_next_git::{Generation, RepoDetails, Repository};
use git_next_repo_actor::{CloneRepo, RepoActor}; use git_next_repo_actor::{CloneRepo, RepoActor};
@ -99,11 +99,13 @@ impl Handler<ServerConfig> for Server {
let webhook = server_config.webhook(); let webhook = server_config.webhook();
// Forge Actors // Forge Actors
for (forge_name, forge_config) in server_config.forges() { for (forge_alias, forge_config) in server_config.forges() {
self.create_forge_repos(forge_config, forge_name.clone(), server_storage, webhook) self.create_forge_repos(forge_config, forge_alias.clone(), server_storage, webhook)
.into_iter() .into_iter()
.map(|a| self.start_actor(a)) .map(|a| self.start_actor(a))
.map(|(alias, addr)| AddWebhookRecipient(alias, addr.recipient())) .map(|(repo_alias, addr)| {
AddWebhookRecipient::new(forge_alias.clone(), repo_alias, addr.recipient())
})
.for_each(|msg| webhook_router.do_send(msg)); .for_each(|msg| webhook_router.do_send(msg));
} }
@ -146,10 +148,10 @@ impl Server {
fn create_forge_repos( fn create_forge_repos(
&self, &self,
forge_config: &ForgeConfig, forge_config: &ForgeConfig,
forge_name: ForgeName, forge_name: ForgeAlias,
server_storage: &ServerStorage, server_storage: &ServerStorage,
webhook: &Webhook, webhook: &Webhook,
) -> Vec<(ForgeName, RepoAlias, RepoActor)> { ) -> Vec<(ForgeAlias, RepoAlias, RepoActor)> {
let span = let span =
tracing::info_span!("create_forge_repos", name = %forge_name, config = %forge_config); tracing::info_span!("create_forge_repos", name = %forge_name, config = %forge_config);
@ -170,11 +172,11 @@ impl Server {
fn create_actor( fn create_actor(
&self, &self,
forge_name: ForgeName, forge_name: ForgeAlias,
forge_config: ForgeConfig, forge_config: ForgeConfig,
server_storage: &ServerStorage, server_storage: &ServerStorage,
webhook: &Webhook, webhook: &Webhook,
) -> impl Fn((RepoAlias, &ServerRepoConfig)) -> (ForgeName, RepoAlias, RepoActor) { ) -> impl Fn((RepoAlias, &ServerRepoConfig)) -> (ForgeAlias, RepoAlias, RepoActor) {
let server_storage = server_storage.clone(); let server_storage = server_storage.clone();
let webhook = webhook.clone(); let webhook = webhook.clone();
let net = self.net.clone(); let net = self.net.clone();
@ -219,7 +221,7 @@ impl Server {
fn start_actor( fn start_actor(
&self, &self,
actor: (ForgeName, RepoAlias, RepoActor), actor: (ForgeAlias, RepoAlias, RepoActor),
) -> (RepoAlias, Addr<RepoActor>) { ) -> (RepoAlias, Addr<RepoActor>) {
let (forge_name, repo_alias, actor) = actor; let (forge_name, repo_alias, actor) = actor;
let span = tracing::info_span!("start_actor", forge = %forge_name, repo = %repo_alias); let span = tracing::info_span!("start_actor", forge = %forge_name, repo = %repo_alias);

View file

@ -2,20 +2,21 @@
use std::collections::HashMap; use std::collections::HashMap;
use actix::prelude::*; use actix::prelude::*;
use git_next_config::RepoAlias; use derive_more::Constructor;
use git_next_config::{ForgeAlias, RepoAlias};
use git_next_repo_actor::webhook::WebhookMessage; use git_next_repo_actor::webhook::WebhookMessage;
use tracing::{debug, info}; use tracing::{debug, info};
pub struct WebhookRouter { pub struct WebhookRouter {
span: tracing::Span, span: tracing::Span,
repos: HashMap<RepoAlias, Recipient<WebhookMessage>>, recipients: HashMap<ForgeAlias, HashMap<RepoAlias, Recipient<WebhookMessage>>>,
} }
impl WebhookRouter { impl WebhookRouter {
pub fn new() -> Self { pub fn new() -> Self {
let span = tracing::info_span!("WebhookRouter"); let span = tracing::info_span!("WebhookRouter");
Self { Self {
span, span,
repos: Default::default(), recipients: Default::default(),
} }
} }
} }
@ -28,24 +29,39 @@ impl Handler<WebhookMessage> for WebhookRouter {
fn handle(&mut self, msg: WebhookMessage, _ctx: &mut Self::Context) -> Self::Result { fn handle(&mut self, msg: WebhookMessage, _ctx: &mut Self::Context) -> Self::Result {
let _gaurd = self.span.enter(); let _gaurd = self.span.enter();
let forge_alias = msg.forge_alias();
let repo_alias = msg.repo_alias(); let repo_alias = msg.repo_alias();
debug!(repo = %repo_alias, "Router..."); debug!(forge = %forge_alias, repo = %repo_alias, "Router...");
if let Some(recipient) = self.repos.get(repo_alias) { let Some(forge_repos) = self.recipients.get(forge_alias) else {
return;
};
let Some(recipient) = forge_repos.get(repo_alias) else {
return;
};
info!(repo = %repo_alias, "Sending to Recipient"); info!(repo = %repo_alias, "Sending to Recipient");
recipient.do_send(msg); recipient.do_send(msg);
} }
} }
}
#[derive(Message)] #[derive(Message, Constructor)]
#[rtype(result = "()")] #[rtype(result = "()")]
pub struct AddWebhookRecipient(pub RepoAlias, pub Recipient<WebhookMessage>); pub struct AddWebhookRecipient {
pub forge_alias: ForgeAlias,
pub repo_alias: RepoAlias,
pub recipient: Recipient<WebhookMessage>,
}
impl Handler<AddWebhookRecipient> for WebhookRouter { impl Handler<AddWebhookRecipient> for WebhookRouter {
type Result = (); type Result = ();
fn handle(&mut self, msg: AddWebhookRecipient, _ctx: &mut Self::Context) -> Self::Result { fn handle(&mut self, msg: AddWebhookRecipient, _ctx: &mut Self::Context) -> Self::Result {
let _gaurd = self.span.enter(); let _gaurd = self.span.enter();
info!(repo = %msg.0, "Register Recipient"); info!(forge = %msg.forge_alias, repo = %msg.repo_alias, "Register Recipient");
self.repos.insert(msg.0, msg.1); if !self.recipients.contains_key(&msg.forge_alias) {
self.recipients
.insert(msg.forge_alias.clone(), HashMap::new());
}
self.recipients
.get_mut(&msg.forge_alias)
.map(|repos| repos.insert(msg.repo_alias, msg.recipient));
} }
} }

View file

@ -3,7 +3,7 @@ use std::net::SocketAddr;
use actix::prelude::*; use actix::prelude::*;
use git_next_config::RepoAlias; use git_next_config::{ForgeAlias, RepoAlias};
use git_next_repo_actor::webhook::{self, WebhookAuth, WebhookMessage}; use git_next_repo_actor::webhook::{self, WebhookAuth, WebhookMessage};
use tracing::{info, warn}; use tracing::{info, warn};
use warp::reject::Rejection; use warp::reject::Rejection;
@ -15,17 +15,19 @@ pub async fn start(socket_addr: SocketAddr, address: actix::prelude::Recipient<W
let route = warp::post() let route = warp::post()
.map(move || address.clone()) .map(move || address.clone())
.and(warp::path::param()) .and(warp::path::param())
// .and(warp::query::raw()) .and(warp::path::param())
.and(warp::header::headers_cloned()) .and(warp::header::headers_cloned())
.and(warp::body::bytes()) .and(warp::body::bytes())
.and_then( .and_then(
|recipient: Recipient<WebhookMessage>, |recipient: Recipient<WebhookMessage>,
path: String, forge_alias: String,
repo_alias: String,
// query: String, // query: String,
headers: warp::http::HeaderMap, headers: warp::http::HeaderMap,
body: bytes::Bytes| async move { body: bytes::Bytes| async move {
info!("POST received"); info!("POST received");
let repo_alias = RepoAlias::new(path); let forge_alias = ForgeAlias::new(forge_alias);
let repo_alias = RepoAlias::new(repo_alias);
let bytes = body.to_vec(); let bytes = body.to_vec();
let body = webhook::Body::new(String::from_utf8_lossy(&bytes).to_string()); let body = webhook::Body::new(String::from_utf8_lossy(&bytes).to_string());
headers.get("Authorization").map_or_else( headers.get("Authorization").map_or_else(
@ -34,10 +36,22 @@ pub async fn start(socket_addr: SocketAddr, address: actix::prelude::Recipient<W
Err(warp::reject()) Err(warp::reject())
}, },
|authorisation_header| { |authorisation_header| {
info!(?repo_alias, ?authorisation_header, "Received webhook",); info!(
forge = %forge_alias,
repo = %repo_alias,
?authorisation_header,
"Received webhook",
);
// TODO: (#86) Authorization isn't presented consistently, allow each forge
// to parse the authorization from the request
match parse_auth(authorisation_header) { match parse_auth(authorisation_header) {
Ok(authorisation) => { Ok(authorisation) => {
let message = WebhookMessage::new(repo_alias, authorisation, body); let message = WebhookMessage::new(
forge_alias,
repo_alias,
authorisation,
body,
);
recipient recipient
.try_send(message) .try_send(message)
.map(|_| { .map(|_| {