From 17ad92402d1607c86f6e4ab759e1d5937d07b1cb Mon Sep 17 00:00:00 2001 From: Willem Date: Thu, 26 Jun 2025 10:12:18 -0400 Subject: [PATCH 1/2] feat: added webhook receiver to alertchannels --- .../modules/monitoring/alert_channel/mod.rs | 1 + .../alert_channel/webhook_receiver.rs | 119 ++++++++++++++++++ 2 files changed, 120 insertions(+) create mode 100644 harmony/src/modules/monitoring/alert_channel/webhook_receiver.rs diff --git a/harmony/src/modules/monitoring/alert_channel/mod.rs b/harmony/src/modules/monitoring/alert_channel/mod.rs index fabc6dd..1bca2bc 100644 --- a/harmony/src/modules/monitoring/alert_channel/mod.rs +++ b/harmony/src/modules/monitoring/alert_channel/mod.rs @@ -1 +1,2 @@ pub mod discord_alert_channel; +pub mod webhook_receiver; diff --git a/harmony/src/modules/monitoring/alert_channel/webhook_receiver.rs b/harmony/src/modules/monitoring/alert_channel/webhook_receiver.rs new file mode 100644 index 0000000..d1254c6 --- /dev/null +++ b/harmony/src/modules/monitoring/alert_channel/webhook_receiver.rs @@ -0,0 +1,119 @@ +use async_trait::async_trait; +use serde::Serialize; +use serde_yaml::{Mapping, Value}; + +use crate::{interpret::{InterpretError, Outcome}, modules::monitoring::kube_prometheus::{prometheus::{Prometheus, PrometheusReceiver}, types::{AlertChannelConfig, AlertManagerChannelConfig}}, topology::{oberservability::monitoring::AlertReceiver, Url}}; + + +#[derive(Debug, Clone, Serialize)] +pub struct WebhookReceiver { + pub name: String, + pub url: Url, +} + +#[async_trait] +impl AlertReceiver for WebhookReceiver{ + async fn install(&self, sender: &Prometheus) -> Result { + sender.install_receiver(self).await + } + fn clone_box(&self) -> Box> { + Box::new(self.clone()) + } +} + +#[async_trait] +impl PrometheusReceiver for WebhookReceiver { + fn name(&self) -> String { + self.name.clone() + } + async fn configure_receiver(&self) -> AlertManagerChannelConfig { + self.get_config().await + } +} + + +#[async_trait] +impl AlertChannelConfig for WebhookReceiver { + async fn get_config(&self) -> AlertManagerChannelConfig { + let channel_global_config = None; + let channel_receiver = self.alert_channel_receiver().await; + let channel_route = self.alert_channel_route().await; + + AlertManagerChannelConfig { + channel_global_config, + channel_receiver, + channel_route, + } + } +} + +impl WebhookReceiver { + async fn alert_channel_route(&self) -> serde_yaml::Value { + let mut route = Mapping::new(); + route.insert( + Value::String("receiver".to_string()), + Value::String(self.name.clone()), + ); + route.insert( + Value::String("matchers".to_string()), + Value::Sequence(vec![Value::String("alertname!=Watchdog".to_string())]), + ); + route.insert(Value::String("continue".to_string()), Value::Bool(true)); + Value::Mapping(route) + } + + async fn alert_channel_receiver(&self) -> serde_yaml::Value { + let mut receiver = Mapping::new(); + receiver.insert( + Value::String("name".to_string()), + Value::String(self.name.clone()), + ); + + let mut webhook_config = Mapping::new(); + webhook_config.insert( + Value::String("url".to_string()), + Value::String(self.url.to_string()), + ); + + receiver.insert( + Value::String("webhook_configs".to_string()), + Value::Sequence(vec![Value::Mapping(webhook_config)]), + ); + + Value::Mapping(receiver) + } +} + +#[cfg(test)] +mod tests { + use super::*; + #[tokio::test] + async fn webhook_serialize_should_match() { + let webhook_receiver = WebhookReceiver { + name: "test-webhook".to_string(), + url: Url::Url(url::Url::parse("https://webhook.i.dont.exist.com").unwrap()), + }; + + let webhook_receiver_receiver = + serde_yaml::to_string(&webhook_receiver.alert_channel_receiver().await).unwrap(); + println!("receiver \n{:#}", webhook_receiver_receiver); + let webhook_receiver_receiver_yaml = r#"name: test-webhook +webhook_configs: +- url: https://webhook.i.dont.exist.com/ +"# + .to_string(); + + let webhook_receiver_route = + serde_yaml::to_string(&webhook_receiver.alert_channel_route().await).unwrap(); + println!("route \n{:#}", webhook_receiver_route); + let webhook_receiver_route_yaml = r#"receiver: test-webhook +matchers: +- alertname!=Watchdog +continue: true +"# + .to_string(); + + assert_eq!(webhook_receiver_receiver, webhook_receiver_receiver_yaml); + assert_eq!(webhook_receiver_route, webhook_receiver_route_yaml); + } +} From 7ec89cdac5869c424f8c7a35bc0ddd576b4ec695 Mon Sep 17 00:00:00 2001 From: Willem Date: Thu, 26 Jun 2025 11:26:07 -0400 Subject: [PATCH 2/2] fix: cargo fmt --- .../monitoring/alert_channel/webhook_receiver.rs | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/harmony/src/modules/monitoring/alert_channel/webhook_receiver.rs b/harmony/src/modules/monitoring/alert_channel/webhook_receiver.rs index d1254c6..8f608d0 100644 --- a/harmony/src/modules/monitoring/alert_channel/webhook_receiver.rs +++ b/harmony/src/modules/monitoring/alert_channel/webhook_receiver.rs @@ -2,8 +2,14 @@ use async_trait::async_trait; use serde::Serialize; use serde_yaml::{Mapping, Value}; -use crate::{interpret::{InterpretError, Outcome}, modules::monitoring::kube_prometheus::{prometheus::{Prometheus, PrometheusReceiver}, types::{AlertChannelConfig, AlertManagerChannelConfig}}, topology::{oberservability::monitoring::AlertReceiver, Url}}; - +use crate::{ + interpret::{InterpretError, Outcome}, + modules::monitoring::kube_prometheus::{ + prometheus::{Prometheus, PrometheusReceiver}, + types::{AlertChannelConfig, AlertManagerChannelConfig}, + }, + topology::{Url, oberservability::monitoring::AlertReceiver}, +}; #[derive(Debug, Clone, Serialize)] pub struct WebhookReceiver { @@ -12,7 +18,7 @@ pub struct WebhookReceiver { } #[async_trait] -impl AlertReceiver for WebhookReceiver{ +impl AlertReceiver for WebhookReceiver { async fn install(&self, sender: &Prometheus) -> Result { sender.install_receiver(self).await } @@ -31,7 +37,6 @@ impl PrometheusReceiver for WebhookReceiver { } } - #[async_trait] impl AlertChannelConfig for WebhookReceiver { async fn get_config(&self) -> AlertManagerChannelConfig {