feat: implementation of Installable for CRDPrometheusIntroduction of Grafana trait and its impl for k8sanywhereallows for CRDPrometheus to be installed via AlertingInterpret which standardizes the installation of alert receivers, alerting rules, and alert senders

This commit is contained in:
Willem 2025-10-16 14:07:23 -04:00
parent 7dff70edcf
commit fc384599a1
3 changed files with 45 additions and 93 deletions

View File

@ -1,21 +1,12 @@
use std::{ use std::{collections::BTreeMap, process::Command, sync::Arc};
collections::{BTreeMap, HashMap},
process::Command,
sync::Arc,
};
use async_trait::async_trait; use async_trait::async_trait;
use base64::{Engine, engine::general_purpose};
use k8s_openapi::api::{ use k8s_openapi::api::{
authentication::v1::{ core::v1::Secret,
BoundObjectReference, TokenRequest, TokenRequestSpec, TokenRequestStatus,
},
core::v1::{Secret, ServiceAccount},
rbac::v1::{ClusterRoleBinding, RoleRef, Subject}, rbac::v1::{ClusterRoleBinding, RoleRef, Subject},
}; };
use kube::{ use kube::api::{DynamicObject, GroupVersionKind, ObjectMeta};
Api,
api::{GroupVersionKind, ObjectMeta, PostParams},
};
use log::{debug, info, warn}; use log::{debug, info, warn};
use serde::Serialize; use serde::Serialize;
use tokio::sync::OnceCell; use tokio::sync::OnceCell;
@ -35,14 +26,11 @@ use crate::{
Grafana as GrafanaCRD, GrafanaCom, GrafanaDashboard, Grafana as GrafanaCRD, GrafanaCom, GrafanaDashboard,
GrafanaDashboardDatasource, GrafanaDashboardSpec, GrafanaDatasource, GrafanaDashboardDatasource, GrafanaDashboardSpec, GrafanaDatasource,
GrafanaDatasourceConfig, GrafanaDatasourceJsonData, GrafanaDatasourceConfig, GrafanaDatasourceJsonData,
GrafanaDatasourceSecureJsonData, GrafanaDatasourceSpec, GrafanaSecretKeyRef, GrafanaDatasourceSecureJsonData, GrafanaDatasourceSpec, GrafanaSpec,
GrafanaSpec, GrafanaValueFrom, GrafanaValueSource,
}, },
crd_prometheuses::LabelSelector, crd_prometheuses::LabelSelector,
grafana_default_dashboard::build_default_dashboard,
prometheus_operator::prometheus_operator_helm_chart_score, prometheus_operator::prometheus_operator_helm_chart_score,
rhob_alertmanager_config::RHOBObservability, rhob_alertmanager_config::RHOBObservability,
role::build_prom_service_account,
service_monitor::ServiceMonitor, service_monitor::ServiceMonitor,
}, },
}, },
@ -148,24 +136,23 @@ impl Grafana for K8sAnywhereTopology {
}; };
} }
async fn install_grafana(&self) -> Result<PreparationOutcome, PreparationError> { async fn install_grafana(&self) -> Result<PreparationOutcome, PreparationError> {
debug!("install grafana");
let ns = "grafana"; let ns = "grafana";
let mut label = BTreeMap::new(); let mut label = BTreeMap::new();
label.insert("dashboards".to_string(), "grafana".to_string()); label.insert("dashboards".to_string(), "grafana".to_string());
let label_selector = LabelSelector { let label_selector = LabelSelector {
match_labels: label.clone(), match_labels: label.clone(),
match_expressions: vec![], match_expressions: vec![],
}; };
debug!("getting client");
let client = self.k8s_client().await?; let client = self.k8s_client().await?;
info!("creating grafanas crd");
let grafana = self.build_grafana(ns, &label); let grafana = self.build_grafana(ns, &label);
client.apply(&grafana, Some(ns)).await?; client.apply(&grafana, Some(ns)).await?;
//TODO change this to a ensure ready or something better than just a timeout
client client
.wait_until_deployment_ready( .wait_until_deployment_ready(
"grafana-grafana-deployment".to_string(), "grafana-grafana-deployment".to_string(),
@ -175,16 +162,25 @@ impl Grafana for K8sAnywhereTopology {
.await?; .await?;
let sa_name = "grafana-grafana-sa"; let sa_name = "grafana-grafana-sa";
let token_secret_name = "grafana-sa-token-secret"; let token_secret_name = "grafana-sa-token-secret";
// let sa_token_secret = self.build_sa_token_secret(token_secret_name, sa_name, ns); let sa_token_secret = self.build_sa_token_secret(token_secret_name, sa_name, ns);
//
// client.apply(&sa_token_secret, Some(ns)).await?;
let secret = self.build_token_secret(token_secret_name, ns).await; client.apply(&sa_token_secret, Some(ns)).await?;
client.apply(&secret, Some(ns)).await?; let secret_gvk = GroupVersionKind {
let token_request_status = self.create_service_account_token(sa_name, ns).await?; group: "".to_string(),
version: "v1".to_string(),
kind: "Secret".to_string(),
};
let secret = client
.get_resource_json_value(token_secret_name, Some(ns), &secret_gvk)
.await?;
let token = format!(
"Bearer {}",
self.extract_and_normalize_token(&secret).unwrap()
);
debug!("creating grafana clusterrole binding"); debug!("creating grafana clusterrole binding");
@ -195,8 +191,6 @@ impl Grafana for K8sAnywhereTopology {
debug!("creating grafana datasource crd"); debug!("creating grafana datasource crd");
// let token_str = format!("Bearer {}", token.token);
let thanos_url = format!( let thanos_url = format!(
"https://{}", "https://{}",
self.get_domain("thanos-querier-openshift-monitoring") self.get_domain("thanos-querier-openshift-monitoring")
@ -209,7 +203,7 @@ impl Grafana for K8sAnywhereTopology {
ns, ns,
&label_selector, &label_selector,
&thanos_url, &thanos_url,
&token_request_status.token, // Pass the secret name here &token,
); );
client.apply(&thanos_openshift_datasource, Some(ns)).await?; client.apply(&thanos_openshift_datasource, Some(ns)).await?;
@ -398,8 +392,21 @@ impl K8sAnywhereTopology {
.clone() .clone()
} }
pub fn build_service_account(&self, name: &str, namespace: &str) -> ServiceAccount { fn extract_and_normalize_token(&self, secret: &DynamicObject) -> Option<String> {
build_prom_service_account(name.to_string(), namespace.to_string()) let token_b64 = secret
.data
.get("token")
.or_else(|| secret.data.get("data").and_then(|d| d.get("token")))
.and_then(|v| v.as_str())?;
let bytes = general_purpose::STANDARD.decode(token_b64).ok()?;
let s = String::from_utf8(bytes).ok()?;
let cleaned = s
.trim_matches(|c: char| c.is_whitespace() || c == '\0')
.to_string();
Some(cleaned)
} }
pub fn build_cluster_rolebinding( pub fn build_cluster_rolebinding(
@ -451,69 +458,13 @@ impl K8sAnywhereTopology {
} }
} }
pub fn get_token_request(&self, ns: &str) -> TokenRequest {
debug!("building token request");
TokenRequest {
metadata: ObjectMeta {
namespace: Some(ns.to_string()),
..Default::default()
},
spec: TokenRequestSpec {
audiences: vec!["https://kubernetes.default.svc".to_string()],
expiration_seconds: Some(3600),
bound_object_ref: Some(BoundObjectReference {
kind: Some("Secret".to_string()),
name: Some("grafana-sa-token-secret".to_string()),
..Default::default()
}),
},
..Default::default()
}
}
pub async fn create_service_account_token(
&self,
service_account_name: &str,
ns: &str,
) -> Result<TokenRequestStatus, PreparationError> {
debug!("creating service account token");
let token_request = self.get_token_request(ns);
let client = self.k8s_client().await?;
let pp = PostParams::default();
let token_requests_api = client.service_account_api(ns).await;
let data = serde_json::to_vec(&token_request).unwrap();
let created_token_request = token_requests_api
.create_subresource::<TokenRequest>("token", service_account_name, &pp, data)
.await?;
let status = created_token_request
.status
.ok_or_else(|| PreparationError::new("missing token request status".to_string()))?;
Ok(status)
}
pub async fn build_token_secret(&self, secret_name: &str, ns: &str) -> Secret {
Secret {
metadata: ObjectMeta {
name: Some(secret_name.into()),
namespace: Some(ns.into()),
..Default::default()
},
string_data: None,
..Default::default()
}
}
fn build_grafana_datasource( fn build_grafana_datasource(
&self, &self,
name: &str, name: &str,
ns: &str, ns: &str,
label_selector: &LabelSelector, label_selector: &LabelSelector,
url: &str, url: &str,
token: &str, // Pass in the secret name token: &str,
) -> GrafanaDatasource { ) -> GrafanaDatasource {
let mut json_data = BTreeMap::new(); let mut json_data = BTreeMap::new();
json_data.insert("timeInterval".to_string(), "5s".to_string()); json_data.insert("timeInterval".to_string(), "5s".to_string());

View File

@ -156,7 +156,6 @@ pub struct GrafanaDatasourceSpec {
pub values_from: Option<Vec<GrafanaValueFrom>>, pub values_from: Option<Vec<GrafanaValueFrom>>,
} }
#[derive(Serialize, Deserialize, Debug, Clone, JsonSchema)] #[derive(Serialize, Deserialize, Debug, Clone, JsonSchema)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct GrafanaValueFrom { pub struct GrafanaValueFrom {

View File

@ -11,7 +11,9 @@ use std::process::Command;
use crate::modules::monitoring::kube_prometheus::crd::crd_alertmanager_config::CRDPrometheus; use crate::modules::monitoring::kube_prometheus::crd::crd_alertmanager_config::CRDPrometheus;
use crate::modules::monitoring::kube_prometheus::crd::crd_default_rules::build_default_application_rules; use crate::modules::monitoring::kube_prometheus::crd::crd_default_rules::build_default_application_rules;
use crate::modules::monitoring::kube_prometheus::crd::crd_grafana::{ use crate::modules::monitoring::kube_prometheus::crd::crd_grafana::{
Grafana, GrafanaDashboard, GrafanaDashboardSpec, GrafanaDatasource, GrafanaDatasourceConfig, GrafanaDatasourceJsonData, GrafanaDatasourceSpec, GrafanaSecretKeyRef, GrafanaSpec, GrafanaValueFrom, GrafanaValueSource Grafana, GrafanaDashboard, GrafanaDashboardSpec, GrafanaDatasource, GrafanaDatasourceConfig,
GrafanaDatasourceJsonData, GrafanaDatasourceSpec, GrafanaSecretKeyRef, GrafanaSpec,
GrafanaValueFrom, GrafanaValueSource,
}; };
use crate::modules::monitoring::kube_prometheus::crd::crd_prometheus_rules::{ use crate::modules::monitoring::kube_prometheus::crd::crd_prometheus_rules::{
PrometheusRule, PrometheusRuleSpec, RuleGroup, PrometheusRule, PrometheusRuleSpec, RuleGroup,