feat(repo/webhook): Replace webhook if it already exists
All checks were successful
ci/woodpecker/push/tag-created Pipeline was successful
ci/woodpecker/push/push-next Pipeline was successful
ci/woodpecker/push/cron-docker-builder Pipeline was successful
ci/woodpecker/cron/push-next Pipeline was successful
ci/woodpecker/cron/tag-created Pipeline was successful
ci/woodpecker/cron/cron-docker-builder Pipeline was successful

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 ulid::DecodeError;
use std::{fmt::Display, ops::Deref, str::FromStr};
use std::{collections::HashMap, fmt::Display, ops::Deref, str::FromStr};
use crate::server::{
actors::{
repo::{RepoActor, ValidateRepo, WebhookRegistered},
webhook::WebhookMessage,
},
config::{RepoBranches, Webhook},
config::{RepoBranches, Webhook, WebhookUrl},
forge,
};
@ -75,14 +75,14 @@ pub async fn unregister(
url,
network::NetRequestHeaders::new(),
network::RequestBody::None,
network::ResponseType::Json,
network::ResponseType::None,
None,
network::NetRequestLogging::None,
);
let result = net.delete(request).await;
match result {
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>,
net: network::Network,
) {
let Some(repo_config) = repo_details.config else {
let Some(repo_config) = repo_details.config.clone() else {
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");
let hostname = &repo_details.forge.hostname;
let path = repo_details.repo;
@ -103,8 +111,7 @@ pub async fn register(
let url = network::NetUrl::new(format!(
"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 authorisation = WebhookAuth::generate();
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)]
struct Hook {
id: i64,
config: HashMap<String, String>,
}
impl Hook {
fn id(&self) -> WebhookId {

View file

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