Skip to main content

crabka_operator/
config.rs

1use std::net::SocketAddr;
2
3use clap::Args;
4use serde::{Deserialize, Serialize};
5
6/// Operator runtime configuration.
7///
8/// All fields can be set via CLI (`--watch-namespaces`, `--health-addr`, …)
9/// or via env (`WATCH_NAMESPACES`, `HEALTH_ADDR`, …). CLI wins on conflict.
10#[derive(Debug, Clone, Args, Serialize, Deserialize)]
11pub struct OperatorConfig {
12    /// Comma-separated namespaces to watch. Empty = cluster-scoped.
13    #[arg(long, env = "WATCH_NAMESPACES", value_delimiter = ',', num_args = 0..)]
14    pub watch_namespaces: Vec<String>,
15
16    /// Namespace the operator runs in (used for the leader-election Lease).
17    #[arg(long, env = "OPERATOR_NAMESPACE", default_value = "crabka-operator")]
18    pub operator_namespace: String,
19
20    /// Lease name for leader election.
21    #[arg(long, env = "LEASE_NAME", default_value = "crabka-operator-leader")]
22    pub lease_name: String,
23
24    /// Identity advertised in the Lease (typically the pod name).
25    #[arg(long, env = "POD_NAME", default_value = "crabka-operator-local")]
26    pub pod_name: String,
27
28    /// Address for `/healthz`, `/readyz`, `/metrics`.
29    #[arg(long, env = "HEALTH_ADDR", default_value = "0.0.0.0:8080")]
30    pub health_addr: SocketAddr,
31
32    /// Tracing filter (e.g. `info,kube=warn`).
33    #[arg(
34        long,
35        env = "RUST_LOG",
36        default_value = "info,kube_client::client::builder=warn"
37    )]
38    pub log_filter: String,
39
40    /// Default broker image used when `Kafka.spec.image` is unset.
41    #[arg(long, env = "DEFAULT_BROKER_IMAGE")]
42    pub default_broker_image: Option<String>,
43
44    /// Default gateway image used when `KafkaGrpcGateway.spec.image` is unset.
45    #[arg(long, env = "DEFAULT_GATEWAY_IMAGE")]
46    pub default_gateway_image: Option<String>,
47    /// Default schema-registry image used when `SchemaRegistry.spec.image` is unset.
48    #[arg(long, env = "DEFAULT_SCHEMA_REGISTRY_IMAGE")]
49    pub default_schema_registry_image: Option<String>,
50}
51
52impl OperatorConfig {
53    /// Iterator over watched namespaces, or `None` for cluster-scoped.
54    #[must_use]
55    pub fn watched(&self) -> Option<&[String]> {
56        if self.watch_namespaces.is_empty() {
57            None
58        } else {
59            Some(&self.watch_namespaces)
60        }
61    }
62}
63
64#[cfg(test)]
65mod tests {
66    use super::*;
67    use assert2::assert;
68    use clap::Parser;
69
70    #[derive(Parser)]
71    struct Wrap {
72        #[command(flatten)]
73        cfg: OperatorConfig,
74    }
75
76    #[test]
77    fn cli_defaults_compute_cluster_scope() {
78        let parsed = Wrap::parse_from(["bin"]);
79        assert!(parsed.cfg.watched().is_none());
80        assert!(parsed.cfg.operator_namespace == "crabka-operator");
81    }
82
83    #[test]
84    fn comma_separated_namespaces_parse() {
85        let parsed = Wrap::parse_from(["bin", "--watch-namespaces=a,b,c"]);
86        assert!(parsed.cfg.watch_namespaces == vec!["a", "b", "c"]);
87        assert!(parsed.cfg.watched().is_some());
88    }
89}