From 87b1b15d57fdc234b0506b9ea596a202df2fdf11 Mon Sep 17 00:00:00 2001 From: Willem Date: Fri, 9 May 2025 15:28:05 -0400 Subject: [PATCH] feat: added a default config structure for kubeprometheus chart --- harmony/src/domain/topology/mod.rs | 1 - .../domain/topology/monitoring_alerting.rs | 108 -------------- harmony/src/modules/helm/chart.rs | 3 +- harmony/src/modules/monitoring/config.rs | 37 +++++ .../src/modules/monitoring/kube_prometheus.rs | 33 ++++- harmony/src/modules/monitoring/mod.rs | 1 + .../modules/monitoring/monitoring_alerting.rs | 135 +++--------------- 7 files changed, 88 insertions(+), 230 deletions(-) delete mode 100644 harmony/src/domain/topology/monitoring_alerting.rs create mode 100644 harmony/src/modules/monitoring/config.rs diff --git a/harmony/src/domain/topology/mod.rs b/harmony/src/domain/topology/mod.rs index 0cbfee6..3d773ff 100644 --- a/harmony/src/domain/topology/mod.rs +++ b/harmony/src/domain/topology/mod.rs @@ -1,4 +1,3 @@ -pub mod monitoring_alerting; mod ha_cluster; mod host_binding; mod http; diff --git a/harmony/src/domain/topology/monitoring_alerting.rs b/harmony/src/domain/topology/monitoring_alerting.rs deleted file mode 100644 index 4951333..0000000 --- a/harmony/src/domain/topology/monitoring_alerting.rs +++ /dev/null @@ -1,108 +0,0 @@ -use std::sync::Arc; - -use log::warn; -use tokio::sync::OnceCell; - -use k8s_openapi::api::core::v1::Pod; -use kube::{ - Client, - api::{Api, ListParams}, -}; - -use async_trait::async_trait; - -use crate::{ - interpret::{InterpretError, Outcome}, - inventory::Inventory, - maestro::Maestro, - modules::monitoring::monitoring_alerting::MonitoringAlertingStackScore, - score::Score, -}; - -use super::{HelmCommand, K8sAnywhereTopology, Topology, k8s::K8sClient}; - -#[derive(Clone, Debug)] -struct MonitoringState { - message: String, -} - -#[derive(Debug)] -pub struct MonitoringAlertingTopology { - monitoring_state: OnceCell>, -} - -impl MonitoringAlertingTopology { - pub fn new() -> Self { - Self { - monitoring_state: OnceCell::new(), - } - } - - async fn get_monitoring_state(&self) -> Result, InterpretError> { - let client = Client::try_default() - .await - .map_err(|e| InterpretError::new(format!("Kubernetes client error: {}", e)))?; - - for ns in &["monitoring", "openshift-monitoring"] { - let pods: Api = Api::namespaced(client.clone(), ns); - //TODO hardcoding the label is a problem - //check all pods are ready - let lp = ListParams::default().labels("app.kubernetes.io/name=prometheus"); - - match pods.list(&lp).await { - Ok(pod_list) => { - for p in pod_list.items { - if let Some(status) = p.status { - if let Some(conditions) = status.conditions { - if conditions - .iter() - .any(|c| c.type_ == "Ready" && c.status == "True") - { - return Ok(Some(MonitoringState { - message: format!( - "Prometheus is ready in namespace: {}", - ns - ), - })); - } - } - } - } - } - Err(e) => { - warn!("Failed to query pods in ns {}: {}", ns, e); - } - } - } - - Ok(None) - } -} - -impl Clone for Box> { - fn clone(&self) -> Box> { - self.clone_box() - } -} - -#[async_trait] -impl Topology for MonitoringAlertingTopology { - fn name(&self) -> &str { - "MonitoringAlertingTopology" - } - - async fn ensure_ready(&self) -> Result { - if let Some(state) = self.get_monitoring_state().await? { - // Monitoring stack is already ready — stop app. - println!("{}", state.message); - std::process::exit(0); - } - - // Monitoring not found — proceed with installation. - Ok(Outcome::success( - "Monitoring stack installation started.".to_string(), - )) - } -} - -impl HelmCommand for MonitoringAlertingTopology {} diff --git a/harmony/src/modules/helm/chart.rs b/harmony/src/modules/helm/chart.rs index be15a15..3e40945 100644 --- a/harmony/src/modules/helm/chart.rs +++ b/harmony/src/modules/helm/chart.rs @@ -159,7 +159,8 @@ impl Interpret for HelmChartInterpret { self.add_repo()?; - let helm_executor = DefaultHelmExecutor::new(); + let helm_path = NonBlankString::from_str("helm").unwrap(); + let helm_executor = DefaultHelmExecutor::new_with_opts(&helm_path, None, 9000, false, false); let mut helm_options = Vec::new(); if self.score.create_namespace { diff --git a/harmony/src/modules/monitoring/config.rs b/harmony/src/modules/monitoring/config.rs new file mode 100644 index 0000000..0601cf1 --- /dev/null +++ b/harmony/src/modules/monitoring/config.rs @@ -0,0 +1,37 @@ +#[derive(Debug, Clone)] +pub struct KubePrometheusConfig { + pub namespace: String, + pub node_exporter: bool, + pub alert_manager: bool, + pub prometheus: bool, + pub grafana: bool, + pub windows_monitoring: bool, + pub kubernetes_service_monitors: bool, + pub kubelet: bool, + pub kube_controller_manager: bool, + pub kube_etcd: bool, + pub kube_proxy: bool, + pub kube_state_metrics: bool, + pub prometheus_operator: bool, +} + +impl Default for KubePrometheusConfig { + fn default() -> Self { + Self { + namespace: "monitoring".into(), + node_exporter: false, + alert_manager: false, + prometheus: true, + grafana: true, + windows_monitoring: false, + kubernetes_service_monitors: true, + kubelet: true, + kube_controller_manager: true, + kube_etcd: true, + kube_proxy: true, + kube_state_metrics: true, + prometheus_operator: true, + } + } +} + diff --git a/harmony/src/modules/monitoring/kube_prometheus.rs b/harmony/src/modules/monitoring/kube_prometheus.rs index c729c96..74b4468 100644 --- a/harmony/src/modules/monitoring/kube_prometheus.rs +++ b/harmony/src/modules/monitoring/kube_prometheus.rs @@ -1,10 +1,12 @@ -use std::str::FromStr; +use std::{collections::HashMap, str::FromStr}; use non_blank_string_rs::NonBlankString; use crate::modules::helm::chart::HelmChartScore; -pub fn kube_prometheus_score(ns: &str) -> HelmChartScore { +use super::config::KubePrometheusConfig; + +pub fn kube_prometheus_score(config: KubePrometheusConfig) -> HelmChartScore { //TODO this should be make into a rule with default formatting that can be easily passed as a vec //to the overrides or something leaving the user to deal with formatting here seems bad let values = r#" @@ -32,8 +34,31 @@ additionalPrometheusRulesMap: description: The PVC {{ $labels.persistentvolumeclaim }} in namespace {{ $labels.namespace }} is predicted to fill over 95% in less than 2 days. title: PVC {{ $labels.persistentvolumeclaim }} in namespace {{ $labels.namespace }} will fill over 95% in less than 2 days "#; + let mut values_overrides: HashMap = HashMap::new(); + + + macro_rules! insert_flag { + ($key:expr, $val:expr) => { + values_overrides.insert(NonBlankString::from_str($key).unwrap(), $val.to_string()); + }; + } + + insert_flag!("nodeExporter.enabled", config.node_exporter); + insert_flag!("windowsMonitoring.enabled", config.windows_monitoring); + insert_flag!("grafana.enabled", config.grafana); + insert_flag!("alertmanager.enabled", config.alert_manager); + insert_flag!("prometheus.enabled", config.prometheus); + insert_flag!("kubernetes_service_monitors.enabled", config.kubernetes_service_monitors); + insert_flag!("kubelet.enabled", config.kubelet); + insert_flag!("kubeControllerManager.enabled", config.kube_controller_manager); + insert_flag!("kubeProxy.enabled", config.kube_proxy); + insert_flag!("kubeEtcd.enabled", config.kube_etcd); + insert_flag!("kubeStateMetrics.enabled", config.kube_state_metrics); + insert_flag!("prometheusOperator.enabled", config.prometheus_operator); + + HelmChartScore { - namespace: Some(NonBlankString::from_str(ns).unwrap()), + namespace: Some(NonBlankString::from_str(&config.namespace).unwrap()), release_name: NonBlankString::from_str("kube-prometheus").unwrap(), chart_name: NonBlankString::from_str( "oci://ghcr.io/prometheus-community/charts/kube-prometheus-stack", //use kube prometheus chart which includes grafana, prometheus, alert @@ -41,7 +66,7 @@ additionalPrometheusRulesMap: ) .unwrap(), chart_version: None, - values_overrides: None, + values_overrides: Some(values_overrides), values_yaml: Some(values.to_string()), create_namespace: true, install_only: true, diff --git a/harmony/src/modules/monitoring/mod.rs b/harmony/src/modules/monitoring/mod.rs index dd17cc1..c965787 100644 --- a/harmony/src/modules/monitoring/mod.rs +++ b/harmony/src/modules/monitoring/mod.rs @@ -1,3 +1,4 @@ pub mod monitoring_alerting; +mod config; mod kube_prometheus; diff --git a/harmony/src/modules/monitoring/monitoring_alerting.rs b/harmony/src/modules/monitoring/monitoring_alerting.rs index acc5969..3cdd9c2 100644 --- a/harmony/src/modules/monitoring/monitoring_alerting.rs +++ b/harmony/src/modules/monitoring/monitoring_alerting.rs @@ -3,142 +3,45 @@ use log::info; use serde::Serialize; use crate::{ - data::{Id, Version}, - interpret::{Interpret, InterpretError, InterpretName, InterpretStatus, Outcome}, - inventory::Inventory, - maestro::Maestro, - score::{CloneBoxScore, Score}, - topology::{HelmCommand, Topology, monitoring_alerting::MonitoringAlertingTopology}, + interpret::Interpret, + modules::helm::chart::HelmChartScore, + score::{CloneBoxScore, Score, SerializeScore}, + topology::{HelmCommand, Topology}, }; -use super::kube_prometheus::kube_prometheus_score; +use super::{config::KubePrometheusConfig, kube_prometheus::kube_prometheus_score}; -#[derive(Debug)] -pub struct MonitoringAlertingStackScore { - //TODO add documenation to explain why its here - //keeps it open for the end user to specify which stack they want - //if it isnt default kube-prometheus - pub monitoring_stack: Vec>>, - pub namespace: String, +#[derive(Debug, Clone, Serialize)] +pub struct KubePrometheusStackScore { + pub monitoring_stack: HelmChartScore, + } -impl MonitoringAlertingStackScore { - pub fn new( - monitoring_stack: Vec>>, - namespace: String, - ) -> Self { +impl KubePrometheusStackScore { + pub fn new(monitoring_stack: HelmChartScore) -> Self { Self { monitoring_stack, - namespace, } } } -impl Default for MonitoringAlertingStackScore { +impl Default for KubePrometheusStackScore { fn default() -> Self { - let ns = "monitoring"; + let config = KubePrometheusConfig::default(); + let monitoring_stack = kube_prometheus_score(config); + Self { - monitoring_stack: vec![Box::new(kube_prometheus_score(ns))], - namespace: ns.to_string(), - } - } -} -impl Clone for MonitoringAlertingStackScore { - fn clone(&self) -> Self { - Self { - monitoring_stack: self - .monitoring_stack - .iter() - .map(|s| s.clone_box()) - .collect(), - namespace: self.namespace.clone(), + monitoring_stack } } } -impl Serialize for MonitoringAlertingStackScore { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - use serde::ser::SerializeStruct; - let mut s = serializer.serialize_struct("MonitoringAlertingStackScore", 1)?; - let monitoring_values: Vec<_> = self - .monitoring_stack - .iter() - .map(|m| m.serialize()) - .collect(); - s.serialize_field("monitoring", &monitoring_values)?; - s.end() - } -} - -impl Score for MonitoringAlertingStackScore { - fn create_interpret(&self) -> Box> { - Box::new(MonitoringAlertingStackInterpret { - score: MonitoringAlertingStackScore { - monitoring_stack: self - .monitoring_stack - .iter() - .map(|s| s.clone_box()) - .collect(), - namespace: self.namespace.clone(), - }, - }) - } - +impl Score for KubePrometheusStackScore { fn name(&self) -> String { format!("MonitoringAlertingStackScore") } -} -#[derive(Debug)] -struct MonitoringAlertingStackInterpret { - pub score: MonitoringAlertingStackScore, -} - -#[async_trait] -impl Interpret for MonitoringAlertingStackInterpret { - async fn execute( - &self, - _inventory: &Inventory, - _topology: &T, - ) -> Result { - let inventory = Inventory::autoload(); - let topology = MonitoringAlertingTopology::new(); - let maestro = match Maestro::initialize(inventory, topology).await { - Ok(m) => m, - Err(e) => { - println!("failed to initialize Maestro: {}", e); - std::process::exit(1); - } - }; - - let scores_vec = self.score.monitoring_stack.clone(); - for s in scores_vec{ - info!("Running: {}", s.name()); - maestro.interpret(s).await?; - } - - Ok(Outcome::success(format!( - "monitoring stack installed in {} namespace", - self.score.namespace - ))) - } - - fn get_name(&self) -> InterpretName { - todo!() - } - - fn get_version(&self) -> Version { - todo!() - } - - fn get_status(&self) -> InterpretStatus { - todo!() - } - - fn get_children(&self) -> Vec { - todo!() + fn create_interpret(&self) -> Box> { + self.monitoring_stack.create_interpret() } }