From b5c6e1c99da64f5f89a46a2a55b3238c483dcb67 Mon Sep 17 00:00:00 2001 From: Willem Date: Mon, 26 May 2025 11:36:03 -0400 Subject: [PATCH] refactor: used AlertEndPoint trait and impl, built custom types for prometheus alert manager --- .../{ => kube_prometheus}/kube_prometheus.rs | 95 ++++++-------- .../modules/monitoring/kube_prometheus/mod.rs | 4 + .../monitoring/kube_prometheus/traits.rs | 92 ++++++++++++++ .../monitoring/kube_prometheus/types.rs | 73 +++++++++++ harmony/src/modules/monitoring/mod.rs | 120 +----------------- .../modules/monitoring/monitoring_alerting.rs | 4 +- 6 files changed, 213 insertions(+), 175 deletions(-) rename harmony/src/modules/monitoring/{ => kube_prometheus}/kube_prometheus.rs (77%) create mode 100644 harmony/src/modules/monitoring/kube_prometheus/mod.rs create mode 100644 harmony/src/modules/monitoring/kube_prometheus/traits.rs create mode 100644 harmony/src/modules/monitoring/kube_prometheus/types.rs diff --git a/harmony/src/modules/monitoring/kube_prometheus.rs b/harmony/src/modules/monitoring/kube_prometheus/kube_prometheus.rs similarity index 77% rename from harmony/src/modules/monitoring/kube_prometheus.rs rename to harmony/src/modules/monitoring/kube_prometheus/kube_prometheus.rs index 06ce054..edfd4ea 100644 --- a/harmony/src/modules/monitoring/kube_prometheus.rs +++ b/harmony/src/modules/monitoring/kube_prometheus/kube_prometheus.rs @@ -1,16 +1,15 @@ -use super::{ - AlertChannelGlobalConfig, AlertChannelReceiver, AlertChannelRoute, config::KubePrometheusConfig, +use crate::modules::monitoring::kube_prometheus::types::{ + AlertChannelReceiver, AlertChannelRoute, AlertManager, AlertManagerConfig, + AlertManagerRoute, AlertManagerValues, }; use crate::modules::{ helm::chart::HelmChartScore, - monitoring::{AlertChannel, AlertChannelConfig, AlertEndpoint}, + monitoring::{config::KubePrometheusConfig, kube_prometheus::traits::AlertEndpoint}, }; use log::info; use non_blank_string_rs::NonBlankString; -use serde::Serialize; -use serde_yaml::{self, Value, to_value}; -use std::{collections::HashMap, str::FromStr}; -use url::Url; +use serde_yaml::{self}; +use std::str::FromStr; pub fn kube_prometheus_helm_chart_score(config: &KubePrometheusConfig) -> HelmChartScore { //TODO this should be make into a rule with default formatting that can be easily passed as a vec @@ -147,35 +146,45 @@ prometheusOperator: enabled: {prometheus_operator} prometheus: enabled: {prometheus} + prometheusSpec: + maximumStartupDurationSeconds: 240 "#, ); - let alertmanager_config = build_alert_manager_config(&config); + let alert_manager_config = build_alert_manager_config(&config); - fn build_alert_manager_config(config: &KubePrometheusConfig) -> AlertManagerConfig { - let mut receivers = Vec::new(); - let mut routes = Vec::new(); - let mut global_configs = None; + fn build_alert_manager_config(config: &KubePrometheusConfig) -> AlertManagerValues { + let mut global_config = None; - let alert_channel_configs: Vec = config + let (mut receivers, mut routes): (Vec<_>, Vec<_>) = config .alert_channel .iter() .map(|s| s.build_alert_receiver()) - .collect(); + .map(|chan| { + if let Some(global) = chan.global_config { + global_config = Some(global); + } + (chan.receiver, chan.route) + }) + .unzip(); - for alert_channel_config in alert_channel_configs { - receivers.push(alert_channel_config.receiver); - routes.push(alert_channel_config.route); - if let Some(global) = alert_channel_config.global_config { - global_configs = Some(global); - } - } + receivers.push(AlertChannelReceiver { + name: "null".to_string(), + slack_configs: None, + webhook_configs: None, + }); + + routes.push(AlertChannelRoute { + receiver: "null".to_string(), + matchers: vec!["alertname=Watchdog".to_string()], + r#continue: false, + }); info!("after alert receiver: {:#?}", receivers); info!("after alert routes: {:#?}", routes); - let alertmanager_config = AlertManagerConfig { - global: global_configs, + let config = AlertManagerConfig { + global: global_config, route: AlertManagerRoute { group_by: vec!["job".to_string()], group_wait: "30s".to_string(), @@ -186,13 +195,18 @@ prometheus: receivers, }; - info!("alert manager config: {:?}", alertmanager_config); - alertmanager_config + info!("alert manager config: {:?}", config); + + AlertManagerValues { + alertmanager: AlertManager { + enabled: true, + config, + }, + } } - let alert_manager_values = AlertManagerValues{ alertmanager: AlertManager{ enabled: true, config: alertmanager_config } }; let yaml_config = - serde_yaml::to_string(&alert_manager_values).expect("Failed to serialize YAML"); + serde_yaml::to_string(&alert_manager_config).expect("Failed to serialize YAML"); values.push_str(&yaml_config); @@ -212,30 +226,3 @@ prometheus: repository: None, } } - -#[derive(Debug, Serialize)] -struct AlertManagerConfig { - global: Option, - route: AlertManagerRoute, - receivers: Vec, -} - -#[derive(Debug, Serialize)] -struct AlertManagerRoute { - group_by: Vec, - group_wait: String, - group_interval: String, - repeat_interval: String, - routes: Vec, -} - -#[derive(Debug, Serialize)] -struct AlertManagerValues { - alertmanager: AlertManager, -} - -#[derive(Debug, Serialize)] -struct AlertManager { - enabled: bool, - config: AlertManagerConfig, -} diff --git a/harmony/src/modules/monitoring/kube_prometheus/mod.rs b/harmony/src/modules/monitoring/kube_prometheus/mod.rs new file mode 100644 index 0000000..e09ec19 --- /dev/null +++ b/harmony/src/modules/monitoring/kube_prometheus/mod.rs @@ -0,0 +1,4 @@ +pub mod traits; +pub mod kube_prometheus; +pub mod types; + diff --git a/harmony/src/modules/monitoring/kube_prometheus/traits.rs b/harmony/src/modules/monitoring/kube_prometheus/traits.rs new file mode 100644 index 0000000..ca88c20 --- /dev/null +++ b/harmony/src/modules/monitoring/kube_prometheus/traits.rs @@ -0,0 +1,92 @@ +use crate::modules::monitoring::AlertChannel; + +use super::types::{AlertChannelConfig, AlertChannelGlobalConfig, AlertChannelReceiver, AlertChannelRoute, SlackConfig, WebhookConfig}; + + +pub trait AlertEndpoint { + //fn register_webhook(&self, webhook_url: Url); + fn build_alert_receiver(&self) -> AlertChannelConfig; +} + +impl AlertEndpoint for AlertChannel { + fn build_alert_receiver(&self) -> AlertChannelConfig { + match self { + AlertChannel::Discord { name, .. } => AlertChannelConfig { + receiver: AlertChannelReceiver { + name: format!("Discord-{name}"), + slack_configs: None, + webhook_configs: Some(vec![WebhookConfig { + url: url::Url::parse("http://{name}-alertmanager-discord:9094") + .expect("invalid url"), + send_resolved: true, + }]), + }, + route: AlertChannelRoute { + receiver: format!("Discord-{name}"), + matchers: vec!["alertname!=Watchdog".to_string()], + r#continue: true, + }, + global_config: None, + }, + AlertChannel::Slack { + slack_channel, + webhook_url, + } => AlertChannelConfig { + receiver: AlertChannelReceiver { + name: format!("Slack-{slack_channel}"), + slack_configs: Some(vec![SlackConfig { + channel: slack_channel.clone(), + send_resolved: true, + title: "{{ .CommonAnnotations.title }}".to_string(), + text: ">- + *Alert:* {{ .CommonLabels.alertname }} + *Severity:* {{ .CommonLabels.severity }} + *Namespace:* {{ .CommonLabels.namespace }} + *Pod:* {{ .CommonLabels.pod }} + *ExternalURL:* {{ .ExternalURL }} + + {{ range .Alerts }} + *Instance:* {{ .Labels.instance }} + *Summary:* {{ .Annotations.summary }} + *Description:* {{ .Annotations.description }} + *Starts At:* {{ .StartsAt }} + *Status:* {{ .Status }} + {{ end }}".to_string() + }]), + webhook_configs: None, + }, + route: AlertChannelRoute { + receiver: format!("Slack-{slack_channel}"), + matchers: vec!["alertname!=Watchdog".to_string()], + r#continue: true, + }, + global_config: Some(AlertChannelGlobalConfig { + slack_api_url: Some(webhook_url.clone()), + }), + }, + AlertChannel::MSTeams { + connector, .. + } => AlertChannelConfig{ + receiver: AlertChannelReceiver{ + name: format!("MSTeams-{connector}"), + slack_configs: None, + webhook_configs: Some(vec![WebhookConfig{ + url: url::Url::parse("http://prometheus-msteams-prometheus-msteams.monitoring.svc.cluster.local:2000/alertmanager").expect("invalid url"), + send_resolved: true,}]) + }, + route: AlertChannelRoute{ + receiver: format!("MSTeams-{connector}"), + matchers: vec!["alertname!=Watchdog".to_string()], + r#continue: true, + }, + global_config: None, }, + + AlertChannel::Smpt { + email_address, + service_name, + } => todo!(), + } + } +} + + diff --git a/harmony/src/modules/monitoring/kube_prometheus/types.rs b/harmony/src/modules/monitoring/kube_prometheus/types.rs new file mode 100644 index 0000000..9dbd676 --- /dev/null +++ b/harmony/src/modules/monitoring/kube_prometheus/types.rs @@ -0,0 +1,73 @@ +use serde::{Deserialize, Serialize}; +use url::Url; + +#[derive(Debug, Serialize)] +pub struct AlertManagerValues { + pub alertmanager: AlertManager, +} + +#[derive(Debug, Serialize)] +pub struct AlertManager { + pub enabled: bool, + pub config: AlertManagerConfig, +} + +#[derive(Debug)] +pub struct AlertChannelConfig { + pub receiver: AlertChannelReceiver, + pub route: AlertChannelRoute, + pub global_config: Option, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct AlertChannelReceiver { + pub name: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub slack_configs: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub webhook_configs: Option>, +} + +#[derive(Debug, Serialize)] +pub struct AlertManagerRoute { + pub group_by: Vec, + pub group_wait: String, + pub group_interval: String, + pub repeat_interval: String, + pub routes: Vec, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct AlertChannelGlobalConfig { + #[serde(skip_serializing_if = "Option::is_none")] + pub slack_api_url: Option, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct SlackConfig { + pub channel: String, + pub send_resolved: bool, + pub title: String, + pub text: String, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct WebhookConfig { + pub url: Url, + pub send_resolved: bool, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct AlertChannelRoute { + pub receiver: String, + pub matchers: Vec, + #[serde(default)] + pub r#continue: bool, +} + +#[derive(Debug, Serialize)] +pub struct AlertManagerConfig { + pub global: Option, + pub route: AlertManagerRoute, + pub receivers: Vec, +} diff --git a/harmony/src/modules/monitoring/mod.rs b/harmony/src/modules/monitoring/mod.rs index 95f95d2..245c7d4 100644 --- a/harmony/src/modules/monitoring/mod.rs +++ b/harmony/src/modules/monitoring/mod.rs @@ -1,57 +1,13 @@ use email_address::EmailAddress; -use serde::{Deserialize, Serialize}; +use serde::Serialize; use url::Url; mod config; mod discord_alert_manager; -mod kube_prometheus; +pub mod kube_prometheus; pub mod monitoring_alerting; mod prometheus_msteams; -#[derive(Debug)] -struct AlertChannelConfig { - receiver: AlertChannelReceiver, - route: AlertChannelRoute, - global_config: Option, -} - -#[derive(Debug, Serialize, Deserialize)] -struct AlertChannelReceiver { - pub name: String, - #[serde(skip_serializing_if = "Option::is_none")] - pub slack_configs: Option>, - #[serde(skip_serializing_if = "Option::is_none")] - pub webhook_configs: Option>, -} - -#[derive(Debug, Serialize, Deserialize)] -pub struct SlackConfig { - pub channel: String, - send_resolved: bool, - title: String, - text: String, -} - -#[derive(Debug, Serialize, Deserialize)] -pub struct WebhookConfig { - pub url: Url, - send_resolved: bool, -} - -#[derive(Debug, Serialize, Deserialize)] -pub struct AlertChannelRoute { - pub receiver: String, - pub matchers: Vec, - #[serde(default)] - pub continue_: bool, -} - -#[derive(Debug, Serialize, Deserialize)] -pub struct AlertChannelGlobalConfig { - #[serde(skip_serializing_if = "Option::is_none")] - pub slack_api_url: Option, -} - #[derive(Debug, Clone, Serialize)] pub enum AlertChannel { Discord { @@ -74,75 +30,3 @@ pub enum AlertChannel { }, } -trait AlertEndpoint { - //fn register_webhook(&self, webhook_url: Url); - fn build_alert_receiver(&self) -> AlertChannelConfig; -} - -impl AlertEndpoint for AlertChannel { - fn build_alert_receiver(&self) -> AlertChannelConfig { - match self { - AlertChannel::Discord { name, .. } => AlertChannelConfig { - receiver: AlertChannelReceiver { - name: format!("Discord-{name}"), - slack_configs: None, - webhook_configs: Some(vec![WebhookConfig { - url: url::Url::parse("http://{name}-alertmanager-discord:9094") - .expect("invalid url"), - send_resolved: true, - }]), - }, - route: AlertChannelRoute { - receiver: format!("Discord-{name}"), - matchers: vec!["alertname!=Watchdog".to_string()], - continue_: true, - }, - global_config: None, - }, - AlertChannel::Slack { - slack_channel, - webhook_url, - } => AlertChannelConfig { - receiver: AlertChannelReceiver { - name: format!("Slack-{slack_channel}"), - slack_configs: Some(vec![SlackConfig { - channel: slack_channel.clone(), - send_resolved: true, - title: "{{ .CommonAnnotations.title }}".to_string(), - text: "{{ .CommonAnnotations.description }}".to_string(), - }]), - webhook_configs: None, - }, - route: AlertChannelRoute { - receiver: format!("Slack-{slack_channel}"), - matchers: vec!["alertname!=Watchdog".to_string()], - continue_: true, - }, - global_config: Some(AlertChannelGlobalConfig { - slack_api_url: Some(webhook_url.clone()), - }), - }, - AlertChannel::MSTeams { - connector, .. - } => AlertChannelConfig{ - receiver: AlertChannelReceiver{ - name: format!("MSTeams-{connector}"), - slack_configs: None, - webhook_configs: Some(vec![WebhookConfig{ - url: url::Url::parse("http://prometheus-msteams-prometheus-msteams.monitoring.svc.cluster.local:2000/alertmanager").expect("invalid url"), - send_resolved: true,}]) - }, - route: AlertChannelRoute{ - receiver: format!("MSTeams-{connector}"), - matchers: vec!["alertname!=Watchdog".to_string()], - continue_: true, - }, - global_config: None, }, - - AlertChannel::Smpt { - email_address, - service_name, - } => todo!(), - } - } -} diff --git a/harmony/src/modules/monitoring/monitoring_alerting.rs b/harmony/src/modules/monitoring/monitoring_alerting.rs index 89bd1fa..db33252 100644 --- a/harmony/src/modules/monitoring/monitoring_alerting.rs +++ b/harmony/src/modules/monitoring/monitoring_alerting.rs @@ -14,9 +14,7 @@ use crate::{ }; use super::{ - config::KubePrometheusConfig, discord_alert_manager::discord_alert_manager_score, - kube_prometheus::kube_prometheus_helm_chart_score, - prometheus_msteams::prometheus_msteams_score, AlertChannel, + config::KubePrometheusConfig, discord_alert_manager::discord_alert_manager_score, kube_prometheus::kube_prometheus::kube_prometheus_helm_chart_score, prometheus_msteams::prometheus_msteams_score, AlertChannel };