diff --git a/examples/lamp/src/main.rs b/examples/lamp/src/main.rs index 74df0d8..f8bc6c5 100644 --- a/examples/lamp/src/main.rs +++ b/examples/lamp/src/main.rs @@ -2,10 +2,7 @@ use harmony::{ data::Version, inventory::Inventory, maestro::Maestro, - modules::{ - lamp::{LAMPConfig, LAMPScore}, - monitoring::monitoring_alerting::{AlertChannel, MonitoringAlertingStackScore}, - }, + modules::lamp::{LAMPConfig, LAMPScore}, topology::{K8sAnywhereTopology, Url}, }; @@ -43,13 +40,7 @@ async fn main() { .await .unwrap(); - let url = url::Url::parse("https://discord.com/api/webhooks/dummy_channel/dummy_token") - .expect("invalid URL"); - - let mut monitoring_stack_score = MonitoringAlertingStackScore::new(); - monitoring_stack_score.namespace = Some(lamp_stack.config.namespace.clone()); - - maestro.register_all(vec![Box::new(lamp_stack), Box::new(monitoring_stack_score)]); + maestro.register_all(vec![Box::new(lamp_stack)]); // Here we bootstrap the CLI, this gives some nice features if you need them harmony_cli::init(maestro, None).await.unwrap(); } diff --git a/harmony/src/domain/topology/oberservability/monitoring.rs b/harmony/src/domain/topology/oberservability/monitoring.rs index 4603eba..8793400 100644 --- a/harmony/src/domain/topology/oberservability/monitoring.rs +++ b/harmony/src/domain/topology/oberservability/monitoring.rs @@ -1,14 +1,12 @@ use async_trait::async_trait; - use std::fmt::Debug; -use url::Url; use crate::interpret::InterpretError; use crate::{interpret::Outcome, topology::Topology}; /// Represents an entity responsible for collecting and organizing observability data -/// from various telemetry sources +/// from various telemetry sources such as Prometheus or Datadog /// A `Monitor` abstracts the logic required to scrape, aggregate, and structure /// monitoring data, enabling consistent processing regardless of the underlying data source. #[async_trait] @@ -16,16 +14,25 @@ pub trait Monitor: Debug + Send + Sync { async fn deploy_monitor( &self, topology: &T, - alert_receivers: Vec, + alert_receivers: Vec>, ) -> Result; - async fn delete_monitor( + fn delete_monitor( &self, topolgy: &T, - alert_receivers: Vec, + alert_receivers: Vec>, ) -> Result; } - -pub struct AlertReceiver { - pub receiver_id: String, +pub trait MonitorConfig: Debug + Send + Sync { + fn build_monitor(&self) -> Box>; +} + +pub trait AlertChannelConfig: std::fmt::Debug + Send + Sync { + fn build_alert_channel(&self) -> Box; + fn channel_type(&self) -> String; +} + +#[async_trait] +pub trait AlertChannel: Debug + Send + Sync { + async fn get_channel_id(&self) -> String; } diff --git a/harmony/src/modules/monitoring/alert_channel/discord_alert_channel.rs b/harmony/src/modules/monitoring/alert_channel/discord_alert_channel.rs new file mode 100644 index 0000000..9f7c9d0 --- /dev/null +++ b/harmony/src/modules/monitoring/alert_channel/discord_alert_channel.rs @@ -0,0 +1,52 @@ +use async_trait::async_trait; +use serde::Serialize; +use url::Url; + +use crate::{ + modules::monitoring::kube_prometheus::{ + kube_prometheus_monitor::PrometheusAlertChannel, + types::{AlertManagerGlobalConfigs, AlertManagerReceiver, AlertManagerRoute}, + }, + topology::oberservability::monitoring::{AlertChannel, AlertChannelConfig}, +}; + +#[derive(Debug, Clone)] +pub struct DiscordWebhookAlertChannel { + pub webhook_url: Url, + pub name: String, + pub send_resolved_notifications: bool, +} + +impl AlertChannelConfig for DiscordWebhookAlertChannel { + fn build_alert_channel(&self) -> Box { + Box::new(DiscordWebhookAlertChannel { + webhook_url: self.webhook_url.clone(), + name: self.name.clone(), + send_resolved_notifications: self.send_resolved_notifications.clone(), + }) + } + fn channel_type(&self) -> String { + "discord".to_string() + } +} + +#[async_trait] +impl AlertChannel for DiscordWebhookAlertChannel { + async fn get_channel_id(&self) -> String { + self.name.clone() + } +} + +impl PrometheusAlertChannel for DiscordWebhookAlertChannel { + fn get_alert_channel_global_settings(&self) -> Option { + None + } + + fn get_alert_channel_route(&self) -> AlertManagerRoute { + todo!() + } + + fn get_alert_channel_receiver(&self) -> AlertManagerReceiver { + todo!() + } +} diff --git a/harmony/src/modules/monitoring/alert_channel/mod.rs b/harmony/src/modules/monitoring/alert_channel/mod.rs new file mode 100644 index 0000000..fabc6dd --- /dev/null +++ b/harmony/src/modules/monitoring/alert_channel/mod.rs @@ -0,0 +1 @@ +pub mod discord_alert_channel; diff --git a/harmony/src/modules/monitoring/discord_alert_manager.rs b/harmony/src/modules/monitoring/discord_alert_manager.rs deleted file mode 100644 index 7765505..0000000 --- a/harmony/src/modules/monitoring/discord_alert_manager.rs +++ /dev/null @@ -1,35 +0,0 @@ -use std::str::FromStr; - -use non_blank_string_rs::NonBlankString; -use url::Url; - -use crate::modules::helm::chart::HelmChartScore; - -pub fn discord_alert_manager_score( - webhook_url: Url, - namespace: String, - name: String, -) -> HelmChartScore { - let values = format!( - r#" -environment: - - name: "DISCORD_WEBHOOK" - value: "{webhook_url}" -"#, - ); - - HelmChartScore { - namespace: Some(NonBlankString::from_str(&namespace).unwrap()), - release_name: NonBlankString::from_str(&name).unwrap(), - chart_name: NonBlankString::from_str( - "oci://hub.nationtech.io/library/alertmanager-discord", - ) - .unwrap(), - chart_version: None, - values_overrides: None, - values_yaml: Some(values.to_string()), - create_namespace: true, - install_only: true, - repository: None, - } -} diff --git a/harmony/src/modules/monitoring/discord_webhook_sender.rs b/harmony/src/modules/monitoring/discord_webhook_sender.rs deleted file mode 100644 index bad6402..0000000 --- a/harmony/src/modules/monitoring/discord_webhook_sender.rs +++ /dev/null @@ -1,55 +0,0 @@ -use async_trait::async_trait; -use serde_json::Value; -use url::Url; - -use crate::{ - interpret::{InterpretError, Outcome}, - topology::K8sAnywhereTopology, -}; - -#[derive(Debug, Clone)] -pub struct DiscordWebhookConfig { - pub webhook_url: Url, - pub name: String, - pub send_resolved_notifications: bool, -} - -pub trait DiscordWebhookReceiver { - fn deploy_discord_webhook_receiver( - &self, - _notification_adapter_id: &str, - ) -> Result; - - fn delete_discord_webhook_receiver( - &self, - _notification_adapter_id: &str, - ) -> Result; -} - -// trait used to generate alert manager config values impl Monitor for KubePrometheus -pub trait AlertManagerConfig { - fn get_alert_manager_config(&self) -> Result; -} - -#[async_trait] -impl AlertManagerConfig for DiscordWebhookConfig { - fn get_alert_manager_config(&self) -> Result { - todo!() - } -} - -#[async_trait] -impl DiscordWebhookReceiver for K8sAnywhereTopology { - fn deploy_discord_webhook_receiver( - &self, - _notification_adapter_id: &str, - ) -> Result { - todo!() - } - fn delete_discord_webhook_receiver( - &self, - _notification_adapter_id: &str, - ) -> Result { - todo!() - } -} diff --git a/harmony/src/modules/monitoring/config.rs b/harmony/src/modules/monitoring/kube_prometheus/config.rs similarity index 91% rename from harmony/src/modules/monitoring/config.rs rename to harmony/src/modules/monitoring/kube_prometheus/config.rs index 1477905..0e62c0f 100644 --- a/harmony/src/modules/monitoring/config.rs +++ b/harmony/src/modules/monitoring/kube_prometheus/config.rs @@ -1,7 +1,5 @@ use serde::Serialize; -use super::monitoring_alerting::AlertChannel; - #[derive(Debug, Clone, Serialize)] pub struct KubePrometheusConfig { pub namespace: String, @@ -21,7 +19,6 @@ pub struct KubePrometheusConfig { pub kube_proxy: bool, pub kube_state_metrics: bool, pub prometheus_operator: bool, - pub alert_channel: Vec, } impl KubePrometheusConfig { pub fn new() -> Self { @@ -30,7 +27,6 @@ impl KubePrometheusConfig { default_rules: true, windows_monitoring: false, alert_manager: true, - alert_channel: Vec::new(), grafana: true, node_exporter: false, prometheus: true, diff --git a/harmony/src/modules/monitoring/kube_prometheus.rs b/harmony/src/modules/monitoring/kube_prometheus/kube_prometheus_helm_chart_score.rs similarity index 73% rename from harmony/src/modules/monitoring/kube_prometheus.rs rename to harmony/src/modules/monitoring/kube_prometheus/kube_prometheus_helm_chart_score.rs index b694f51..708d94c 100644 --- a/harmony/src/modules/monitoring/kube_prometheus.rs +++ b/harmony/src/modules/monitoring/kube_prometheus/kube_prometheus_helm_chart_score.rs @@ -1,8 +1,7 @@ -use super::{config::KubePrometheusConfig, monitoring_alerting::AlertChannel}; +use super::config::KubePrometheusConfig; use log::info; use non_blank_string_rs::NonBlankString; -use std::{collections::HashMap, str::FromStr}; -use url::Url; +use std::str::FromStr; use crate::modules::helm::chart::HelmChartScore; @@ -145,65 +144,65 @@ prometheus: "#, ); - let alertmanager_config = alert_manager_yaml_builder(&config); - values.push_str(&alertmanager_config); - - fn alert_manager_yaml_builder(config: &KubePrometheusConfig) -> String { - let mut receivers = String::new(); - let mut routes = String::new(); - let mut global_configs = String::new(); - let alert_manager = config.alert_manager; - for alert_channel in &config.alert_channel { - match alert_channel { - AlertChannel::Discord { name, .. } => { - let (receiver, route) = discord_alert_builder(name); - info!("discord receiver: {} \nroute: {}", receiver, route); - receivers.push_str(&receiver); - routes.push_str(&route); - } - AlertChannel::Slack { - slack_channel, - webhook_url, - } => { - let (receiver, route) = slack_alert_builder(slack_channel); - info!("slack receiver: {} \nroute: {}", receiver, route); - receivers.push_str(&receiver); - - routes.push_str(&route); - let global_config = format!( - r#" - global: - slack_api_url: {webhook_url}"# - ); - - global_configs.push_str(&global_config); - } - AlertChannel::Smpt { .. } => todo!(), - } - } - info!("after alert receiver: {}", receivers); - info!("after alert routes: {}", routes); - - let alertmanager_config = format!( - r#" -alertmanager: - enabled: {alert_manager} - config: {global_configs} - route: - group_by: ['job'] - group_wait: 30s - group_interval: 5m - repeat_interval: 12h - routes: -{routes} - receivers: - - name: 'null' -{receivers}"# - ); - - info!("alert manager config: {}", alertmanager_config); - alertmanager_config - } + // let alertmanager_config = alert_manager_yaml_builder(&config); + // values.push_str(&alertmanager_config); + // + // fn alert_manager_yaml_builder(config: &KubePrometheusConfig) -> String { + // let mut receivers = String::new(); + // let mut routes = String::new(); + // let mut global_configs = String::new(); + // let alert_manager = config.alert_manager; + // for alert_channel in &config.alert_channel { + // match alert_channel { + // AlertChannel::Discord { name, .. } => { + // let (receiver, route) = discord_alert_builder(name); + // info!("discord receiver: {} \nroute: {}", receiver, route); + // receivers.push_str(&receiver); + // routes.push_str(&route); + // } + // AlertChannel::Slack { + // slack_channel, + // webhook_url, + // } => { + // let (receiver, route) = slack_alert_builder(slack_channel); + // info!("slack receiver: {} \nroute: {}", receiver, route); + // receivers.push_str(&receiver); + // + // routes.push_str(&route); + // let global_config = format!( + // r#" + // global: + // slack_api_url: {webhook_url}"# + // ); + // + // global_configs.push_str(&global_config); + // } + // AlertChannel::Smpt { .. } => todo!(), + // } + // } + // info!("after alert receiver: {}", receivers); + // info!("after alert routes: {}", routes); + // + // let alertmanager_config = format!( + // r#" + //alertmanager: + // enabled: {alert_manager} + // config: {global_configs} + // route: + // group_by: ['job'] + // group_wait: 30s + // group_interval: 5m + // repeat_interval: 12h + // routes: + //{routes} + // receivers: + // - name: 'null' + //{receivers}"# + // ); + // + // info!("alert manager config: {}", alertmanager_config); + // alertmanager_config + // } HelmChartScore { namespace: Some(NonBlankString::from_str(&config.namespace).unwrap()), diff --git a/harmony/src/modules/monitoring/kube_prometheus/kube_prometheus_monitor.rs b/harmony/src/modules/monitoring/kube_prometheus/kube_prometheus_monitor.rs new file mode 100644 index 0000000..e8cc2d1 --- /dev/null +++ b/harmony/src/modules/monitoring/kube_prometheus/kube_prometheus_monitor.rs @@ -0,0 +1,51 @@ +use async_trait::async_trait; +use serde::Serialize; + +use crate::{ + interpret::{InterpretError, Outcome}, + topology::{ + Topology, + oberservability::monitoring::{AlertChannel, Monitor, MonitorConfig}, + }, +}; + +use super::{ + config::KubePrometheusConfig, + types::{AlertManagerGlobalConfigs, AlertManagerReceiver, AlertManagerRoute}, +}; + +#[derive(Debug, Clone, Serialize)] +pub struct KubePrometheusMonitor { + pub kube_prometheus_config: KubePrometheusConfig, +} + +impl MonitorConfig for KubePrometheusMonitor { + fn build_monitor(&self) -> Box> { + Box::new(self.clone()) + } +} + +#[async_trait] +pub trait PrometheusAlertChannel: AlertChannel { + fn get_alert_channel_global_settings(&self) -> Option; + fn get_alert_channel_route(&self) -> AlertManagerRoute; + fn get_alert_channel_receiver(&self) -> AlertManagerReceiver; +} + +#[async_trait] +impl Monitor for KubePrometheusMonitor { + async fn deploy_monitor( + &self, + _topology: &T, + _alert_channels: Vec>, + ) -> Result { + todo!() + } + fn delete_monitor( + &self, + _topology: &T, + _alert_channels: Vec>, + ) -> Result { + todo!() + } +} 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..b0b9985 --- /dev/null +++ b/harmony/src/modules/monitoring/kube_prometheus/mod.rs @@ -0,0 +1,4 @@ +pub mod config; +pub mod kube_prometheus_helm_chart_score; +pub mod kube_prometheus_monitor; +pub mod types; 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..095aa55 --- /dev/null +++ b/harmony/src/modules/monitoring/kube_prometheus/types.rs @@ -0,0 +1,3 @@ +pub struct AlertManagerGlobalConfigs {} +pub struct AlertManagerReceiver {} +pub struct AlertManagerRoute {} diff --git a/harmony/src/modules/monitoring/mod.rs b/harmony/src/modules/monitoring/mod.rs index d3eb288..a84bd9f 100644 --- a/harmony/src/modules/monitoring/mod.rs +++ b/harmony/src/modules/monitoring/mod.rs @@ -1,5 +1,3 @@ -mod config; -mod discord_alert_manager; -pub mod discord_webhook_sender; -mod kube_prometheus; +pub mod alert_channel; +pub mod kube_prometheus; pub mod monitoring_alerting; diff --git a/harmony/src/modules/monitoring/monitoring_alerting.rs b/harmony/src/modules/monitoring/monitoring_alerting.rs index 6d2db38..0391bff 100644 --- a/harmony/src/modules/monitoring/monitoring_alerting.rs +++ b/harmony/src/modules/monitoring/monitoring_alerting.rs @@ -1,146 +1,73 @@ use async_trait::async_trait; -use email_address::EmailAddress; - -use log::info; -use serde::Serialize; -use url::Url; +use serde::{Serialize, Serializer, ser::SerializeStruct}; +use std::{fmt::Debug, sync::Arc}; use crate::{ data::{Id, Version}, interpret::{Interpret, InterpretError, InterpretName, InterpretStatus, Outcome}, inventory::Inventory, score::Score, - topology::{HelmCommand, Topology}, + topology::{ + HelmCommand, Topology, + oberservability::monitoring::{AlertChannelConfig, MonitorConfig}, + }, }; -use super::{ - config::KubePrometheusConfig, discord_alert_manager::discord_alert_manager_score, - kube_prometheus::kube_prometheus_helm_chart_score, +use super::kube_prometheus::{ + config::KubePrometheusConfig, kube_prometheus_monitor::KubePrometheusMonitor, }; -#[derive(Debug, Clone, Serialize)] -pub enum AlertChannel { - Discord { - name: String, - webhook_url: Url, - }, - Slack { - slack_channel: String, - webhook_url: Url, - }, - //TODO test and implement in helm chart - //currently does not work - Smpt { - email_address: EmailAddress, - service_name: String, - }, -} - -#[derive(Debug, Clone, Serialize)] -pub struct MonitoringAlertingStackScore { - pub alert_channel: Vec, +#[derive(Debug, Clone)] +pub struct MonitoringAlertingScore { + pub monitor_config: Arc>, + pub alert_channel_configs: Vec>, pub namespace: Option, } -impl MonitoringAlertingStackScore { - pub fn new() -> Self { +impl MonitoringAlertingScore { + pub fn default() -> Self { Self { - alert_channel: Vec::new(), - namespace: None, + monitor_config: Arc::new(KubePrometheusMonitor { + kube_prometheus_config: KubePrometheusConfig::new(), + }), + alert_channel_configs: Vec::new(), + namespace: Some("monitoring".to_string()), } } } -impl Score for MonitoringAlertingStackScore { +impl Score for MonitoringAlertingScore { fn create_interpret(&self) -> Box> { - Box::new(MonitoringAlertingStackInterpret { + Box::new(MonitoringAlertingInterpret { score: self.clone(), }) } + fn name(&self) -> String { - format!("MonitoringAlertingStackScore") + todo!() } } -#[derive(Debug, Clone, Serialize)] -struct MonitoringAlertingStackInterpret { - score: MonitoringAlertingStackScore, -} - -impl MonitoringAlertingStackInterpret { - async fn build_kube_prometheus_helm_chart_config(&self) -> KubePrometheusConfig { - let mut config = KubePrometheusConfig::new(); - if let Some(ns) = &self.score.namespace { - config.namespace = ns.clone(); - } - config.alert_channel = self.score.alert_channel.clone(); - config - } - - async fn deploy_kube_prometheus_helm_chart_score( - &self, - inventory: &Inventory, - topology: &T, - config: &KubePrometheusConfig, - ) -> Result { - let helm_chart = kube_prometheus_helm_chart_score(config); - helm_chart - .create_interpret() - .execute(inventory, topology) - .await - } - - async fn deploy_alert_channel_service( - &self, - inventory: &Inventory, - topology: &T, - config: &KubePrometheusConfig, - ) -> Result { - //let mut outcomes = vec![]; - - //for channel in &self.score.alert_channel { - // let outcome = match channel { - // AlertChannel::Discord { .. } => { - // discord_alert_manager_score(config) - // .create_interpret() - // .execute(inventory, topology) - // .await - // } - // AlertChannel::Slack { .. } => Ok(Outcome::success( - // "No extra configs for slack alerting".to_string(), - // )), - // AlertChannel::Smpt { .. } => { - // todo!() - // } - // }; - // outcomes.push(outcome); - //} - //for result in outcomes { - // result?; - //} - - Ok(Outcome::success("All alert channels deployed".to_string())) - } +#[derive(Debug)] +struct MonitoringAlertingInterpret { + score: MonitoringAlertingScore, } #[async_trait] -impl Interpret for MonitoringAlertingStackInterpret { +impl Interpret for MonitoringAlertingInterpret { async fn execute( &self, - inventory: &Inventory, + _inventory: &Inventory, topology: &T, ) -> Result { - let config = self.build_kube_prometheus_helm_chart_config().await; - info!("Built kube prometheus config"); - info!("Installing kube prometheus chart"); - self.deploy_kube_prometheus_helm_chart_score(inventory, topology, &config) - .await?; - info!("Installing alert channel service"); - self.deploy_alert_channel_service(inventory, topology, &config) - .await?; - Ok(Outcome::success(format!( - "succesfully deployed monitoring and alerting stack" - ))) + let monitor = self.score.monitor_config.build_monitor(); + + let mut alert_channels = Vec::new(); + for config in &self.score.alert_channel_configs { + alert_channels.push(config.build_alert_channel()); + } + + monitor.deploy_monitor(topology, alert_channels).await } fn get_name(&self) -> InterpretName { @@ -159,3 +86,21 @@ impl Interpret for MonitoringAlertingStackInterpre todo!() } } + +impl Serialize for MonitoringAlertingScore { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let mut state = serializer.serialize_struct("MonitoringAlertingScore", 3)?; + + // For now, just serialize basic info + state.serialize_field("monitor_type", "monitoring_system")?; + + let channel_count = self.alert_channel_configs.len(); + state.serialize_field("alert_channel_count", &channel_count)?; + + state.serialize_field("namespace", &self.namespace)?; + state.end() + } +}