feat/impl_installable_crd_prometheus #170

Merged
johnride merged 10 commits from feat/impl_installable_crd_prometheus into master 2025-10-24 16:42:55 +00:00
3 changed files with 45 additions and 93 deletions
Showing only changes of commit fc384599a1 - Show all commits

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,