feat(repo/webhook): Replace webhook if it already exists

Closes kemitix/git-next#45
This commit is contained in:
Paul Campbell 2024-04-15 22:44:56 +01:00
parent dfd7d32c94
commit ec9571a182
2 changed files with 58 additions and 7 deletions

View file

@ -3,14 +3,14 @@ use kxio::network::{self, json};
use tracing::{debug, info, warn}; use tracing::{debug, info, warn};
use ulid::DecodeError; use ulid::DecodeError;
use std::{fmt::Display, ops::Deref, str::FromStr}; use std::{collections::HashMap, fmt::Display, ops::Deref, str::FromStr};
use crate::server::{ use crate::server::{
actors::{ actors::{
repo::{RepoActor, ValidateRepo, WebhookRegistered}, repo::{RepoActor, ValidateRepo, WebhookRegistered},
webhook::WebhookMessage, webhook::WebhookMessage,
}, },
config::{RepoBranches, Webhook}, config::{RepoBranches, Webhook, WebhookUrl},
forge, forge,
}; };
@ -75,14 +75,14 @@ pub async fn unregister(
url, url,
network::NetRequestHeaders::new(), network::NetRequestHeaders::new(),
network::RequestBody::None, network::RequestBody::None,
network::ResponseType::Json, network::ResponseType::None,
None, None,
network::NetRequestLogging::None, network::NetRequestLogging::None,
); );
let result = net.delete(request).await; let result = net.delete(request).await;
match result { match result {
Ok(_) => info!(?webhook_id, "unregistered webhook"), Ok(_) => info!(?webhook_id, "unregistered webhook"),
Err(_) => warn!(?webhook_id, "Failed to unregister webhook"), Err(err) => warn!(?webhook_id, ?err, "Failed to unregister webhook"),
} }
} }
@ -92,9 +92,17 @@ pub async fn register(
addr: actix::prelude::Addr<super::RepoActor>, addr: actix::prelude::Addr<super::RepoActor>,
net: network::Network, net: network::Network,
) { ) {
let Some(repo_config) = repo_details.config else { let Some(repo_config) = repo_details.config.clone() else {
return; return;
}; };
let webhook_url = webhook.url();
// remove any lingering webhooks for the same URL
let existing_webhook_ids = find_existing_webhooks(&repo_details, &webhook_url, &net).await;
for webhook_id in existing_webhook_ids {
unregister(webhook_id, repo_details.clone(), net.clone()).await;
}
info!("Registering webhook"); info!("Registering webhook");
let hostname = &repo_details.forge.hostname; let hostname = &repo_details.forge.hostname;
let path = repo_details.repo; let path = repo_details.repo;
@ -103,8 +111,7 @@ pub async fn register(
let url = network::NetUrl::new(format!( let url = network::NetUrl::new(format!(
"https://{hostname}/api/v1/repos/{path}/hooks?token={token}" "https://{hostname}/api/v1/repos/{path}/hooks?token={token}"
)); ));
let webhook_url = webhook.url(); let repo_alias = &repo_details.name;
let repo_alias = repo_details.name;
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!({
@ -139,9 +146,52 @@ pub async fn register(
} }
} }
async fn find_existing_webhooks(
repo_details: &crate::server::config::RepoDetails,
webhook_url: &WebhookUrl,
net: &network::Network,
) -> Vec<WebhookId> {
let mut ids: Vec<WebhookId> = vec![];
let hostname = &repo_details.forge.hostname;
let path = &repo_details.repo;
let mut page = 1;
loop {
use secrecy::ExposeSecret;
let token = &repo_details.forge.token.expose_secret();
let url = format!("https://{hostname}/api/v1/repos/{path}/hooks?page={page}&token={token}");
let net_url = network::NetUrl::new(url);
let request = network::NetRequest::new(
network::RequestMethod::Get,
net_url,
network::NetRequestHeaders::new(),
network::RequestBody::None,
network::ResponseType::Json,
None,
network::NetRequestLogging::None,
);
let result = net.get::<Vec<Hook>>(request).await;
if let Ok(response) = result {
if let Some(list) = response.response_body() {
if list.is_empty() {
return ids;
}
for hook in list {
if let Some(existing_url) = hook.config.get("url") {
if existing_url.starts_with(webhook_url.as_ref()) {
ids.push(hook.id());
}
}
}
}
}
page += 1;
}
}
#[derive(Debug, serde::Deserialize)] #[derive(Debug, serde::Deserialize)]
struct Hook { struct Hook {
id: i64, id: i64,
config: HashMap<String, String>,
} }
impl Hook { impl Hook {
fn id(&self) -> WebhookId { fn id(&self) -> WebhookId {

View file

@ -45,6 +45,7 @@ impl Webhook {
} }
/// The URL for the webhook where forges should send their updates /// The URL for the webhook where forges should send their updates
#[derive(Clone, Debug, PartialEq, Eq, Deserialize)]
pub struct WebhookUrl(String); pub struct WebhookUrl(String);
impl AsRef<str> for WebhookUrl { impl AsRef<str> for WebhookUrl {
fn as_ref(&self) -> &str { fn as_ref(&self) -> &str {