crabka-operator 0.3.4

Kubernetes operator for Crabka clusters
Documentation
//! `Kafka.spec.metricsConfig` — operator-side surface for the
//! broker's Prometheus `/metrics` endpoint.

use std::collections::BTreeMap;

use schemars::JsonSchema;
use serde::{Deserialize, Serialize};

#[derive(Debug, Clone, Default, Deserialize, Serialize, JsonSchema, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct MetricsConfig {
    #[serde(default)]
    pub r#type: MetricsType,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub pod_monitor: Option<PodMonitorSpec>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub service_monitor: Option<ServiceMonitorSpec>,
}

#[derive(Debug, Clone, Copy, Default, Deserialize, Serialize, JsonSchema, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub enum MetricsType {
    #[default]
    Prometheus,
}

#[derive(Debug, Clone, Default, Deserialize, Serialize, JsonSchema, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct PodMonitorSpec {
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub interval: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub scrape_timeout: Option<String>,
    #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
    pub labels: BTreeMap<String, String>,
}

#[derive(Debug, Clone, Default, Deserialize, Serialize, JsonSchema, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct ServiceMonitorSpec {
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub interval: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub scrape_timeout: Option<String>,
    #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
    pub labels: BTreeMap<String, String>,
}

#[cfg(test)]
mod tests {
    use super::*;
    use assert2::assert;

    #[test]
    fn metrics_config_defaults_type_prometheus() {
        let cfg: MetricsConfig = serde_json::from_str("{}").unwrap();
        assert!(cfg.r#type == MetricsType::Prometheus);
        assert!(cfg.pod_monitor.is_none());
        assert!(cfg.service_monitor.is_none());
    }

    #[test]
    fn metrics_config_round_trips() {
        let cfg = MetricsConfig {
            r#type: MetricsType::Prometheus,
            pod_monitor: Some(PodMonitorSpec {
                interval: Some("15s".into()),
                scrape_timeout: None,
                labels: [("team".to_string(), "platform".to_string())].into(),
            }),
            service_monitor: None,
        };
        let j = serde_json::to_string(&cfg).unwrap();
        assert!(j.contains("\"podMonitor\""));
        assert!(j.contains("\"interval\":\"15s\""));
        let back: MetricsConfig = serde_json::from_str(&j).unwrap();
        assert!(back == cfg);
    }

    #[test]
    fn metrics_type_rejects_unknown() {
        let err = serde_json::from_str::<MetricsType>("\"jmxExporter\"").unwrap_err();
        assert!(err.to_string().contains("prometheus"), "got: {err}");
    }
}