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
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:
parent
dfd7d32c94
commit
ec9571a182
2 changed files with 58 additions and 7 deletions
|
@ -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 {
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
Loading…
Reference in a new issue