use k8s_openapi::api::core::v1::ConfigMap; use kube::api::ObjectMeta; use kube::Resource; use crate::types::{Listener, SojuBouncer, SojuBouncerSpec}; pub fn build_configmap(bouncer: &SojuBouncer, db_uri: &str, auth_url: Option<&str>) -> ConfigMap { let name = bouncer.metadata.name.clone().unwrap(); let ns = bouncer.metadata.namespace.clone().unwrap(); let oref = bouncer.controller_owner_ref(&()).unwrap(); let config = generate_soju_config(&bouncer.spec, db_uri, auth_url); ConfigMap { metadata: ObjectMeta { name: Some(format!("{name}-config")), namespace: Some(ns), owner_references: Some(vec![oref]), ..Default::default() }, data: Some([("config".to_string(), config)].into()), ..Default::default() } } fn generate_soju_config(spec: &SojuBouncerSpec, db_uri: &str, auth_url: Option<&str>) -> String { let mut lines = Vec::new(); lines.push(format!("hostname {}", spec.hostname)); if let Some(title) = &spec.title { lines.push(format!("title \"{title}\"")); } if spec.tls.is_some() { lines.push("tls /etc/soju/tls/tls.crt /etc/soju/tls/tls.key".to_string()); } for listener in &spec.listeners { lines.push(format!("listen {}", listener_uri(listener))); } lines.push(format!("db postgres \"{db_uri}\"")); lines.push("message-store db".to_string()); if let (Some(_), Some(url)) = (&spec.auth, auth_url) { lines.push(format!("auth oauth2 {url}")); lines.push("enable-user-on-auth true".to_string()); } else { lines.push("auth internal".to_string()); } lines.join("\n") } fn listener_uri(listener: &Listener) -> String { let addr = &listener.address; let host_port = if addr.starts_with(':') { format!("0.0.0.0{addr}") } else { addr.clone() }; match (listener.websocket, listener.tls) { (true, true) => format!("wss://{host_port}"), (true, false) => format!("ws+insecure://{host_port}"), (false, true) => format!("ircs://{host_port}"), (false, false) => format!("irc+insecure://{host_port}"), } } #[cfg(test)] mod tests { use super::*; use crate::testutil::test_bouncer; #[test] fn generates_soju_config_with_listeners() { let db_uri = "host=db.svc port=5432 user=test password=secret dbname=soju_test sslmode=disable"; let cm = build_configmap(&test_bouncer(), db_uri, None); let data = cm.data.unwrap(); let config = &data["config"]; assert!(config.contains("listen ircs://0.0.0.0:6697")); assert!(config.contains("listen irc+insecure://0.0.0.0:6667")); assert!(config.contains("hostname irc.example.com")); assert!(config.contains("title \"Test Bouncer\"")); assert!(config.contains("tls /etc/soju/tls/tls.crt /etc/soju/tls/tls.key")); assert!(config.contains("db postgres")); } #[test] fn configmap_has_owner_reference() { let cm = build_configmap(&test_bouncer(), "host=localhost dbname=test", None); let orefs = cm.metadata.owner_references.unwrap(); assert_eq!(orefs.len(), 1); assert_eq!(orefs[0].name, "test-bouncer"); } #[test] fn generates_websocket_listener_uri() { let mut bouncer = test_bouncer(); bouncer.spec.listeners.push(Listener { address: ":8080".to_string(), tls: false, websocket: true, }); let cm = build_configmap( &bouncer, "host=db.svc port=5432 user=test password=secret dbname=soju_test sslmode=disable", None, ); let data = cm.data.unwrap(); let config = &data["config"]; assert!(config.contains("listen ws+insecure://0.0.0.0:8080")); } #[test] fn generates_wss_listener_uri() { let mut bouncer = test_bouncer(); bouncer.spec.listeners.push(Listener { address: ":8443".to_string(), tls: true, websocket: true, }); let cm = build_configmap( &bouncer, "host=db.svc port=5432 user=test password=secret dbname=soju_test sslmode=disable", None, ); let data = cm.data.unwrap(); let config = &data["config"]; assert!(config.contains("listen wss://0.0.0.0:8443")); } #[test] fn generates_oauth2_auth_config() { let mut bouncer = test_bouncer(); bouncer.spec.auth = Some(crate::types::AuthSpec { oauth2_url: "https://auth.example.com/realms/test".to_string(), secret_ref: None, }); let cm = build_configmap( &bouncer, "host=localhost dbname=test", Some("https://client:secret@auth.example.com/realms/test"), ); let data = cm.data.unwrap(); let config = &data["config"]; assert!(config.contains("auth oauth2 https://client:secret@auth.example.com/realms/test")); assert!(config.contains("enable-user-on-auth true")); assert!(!config.contains("auth internal")); } #[test] fn configmap_named_correctly() { let cm = build_configmap(&test_bouncer(), "host=localhost dbname=test", None); assert_eq!(cm.metadata.name.unwrap(), "test-bouncer-config"); } }