use k8s_openapi::api::core::v1::ConfigMap; use kube::api::ObjectMeta; use kube::Resource; use crate::types::ErgoNetwork; pub fn build_configmap(network: &ErgoNetwork, oper_hash: Option<&str>, oauth2_secret: Option<&str>) -> ConfigMap { let name = network.metadata.name.clone().unwrap(); let ns = network.metadata.namespace.clone().unwrap(); let oref = network.controller_owner_ref(&()).unwrap(); let config = generate_ergo_config(network, oper_hash, oauth2_secret); ConfigMap { metadata: ObjectMeta { name: Some(format!("{name}-config")), namespace: Some(ns), owner_references: Some(vec![oref]), ..Default::default() }, data: Some([("ircd.yaml".to_string(), config)].into()), ..Default::default() } } fn generate_ergo_config(network: &ErgoNetwork, oper_hash: Option<&str>, oauth2_secret: Option<&str>) -> String { let spec = &network.spec; let network_name = spec .network_name .as_deref() .unwrap_or(&spec.hostname); let mut lines = Vec::new(); lines.push(format!("server:\n name: {}", spec.hostname)); lines.push(" listeners:".to_string()); for listener in &spec.listeners { if listener.tls { lines.push(format!( " \"{}\":\n tls:\n cert: /config/tls/tls.crt\n key: /config/tls/tls.key", listener.address )); } else { lines.push(format!(" \"{}\":", listener.address)); } } let max_sendq = spec.max_sendq.as_deref().unwrap_or("96k"); lines.push(format!(" max-sendq: {max_sendq}")); if let Some(cloaking) = &spec.cloaking { let cidr4 = cloaking.cidr_len_ipv4.unwrap_or(32); let cidr6 = cloaking.cidr_len_ipv6.unwrap_or(64); let bits = cloaking.num_bits.unwrap_or(64); lines.push(format!( " ip-cloaking:\n enabled: true\n enabled-for-always-on: true\n netname: \"{}\"\n cidr-len-ipv4: {cidr4}\n cidr-len-ipv6: {cidr6}\n num-bits: {bits}", cloaking.netname )); } lines.push(format!("\nnetwork:\n name: \"{}\"", network_name)); lines.push("\ndatastore:\n path: /ircd/ircd.db\n autoupgrade: true".to_string()); { let l = spec.limits.as_ref(); let nicklen = l.and_then(|l| l.nicklen).unwrap_or(32); let identlen = l.and_then(|l| l.identlen).unwrap_or(20); let realnamelen = l.and_then(|l| l.realnamelen).unwrap_or(150); let channellen = l.and_then(|l| l.channellen).unwrap_or(64); let awaylen = l.and_then(|l| l.awaylen).unwrap_or(390); let kicklen = l.and_then(|l| l.kicklen).unwrap_or(390); let topiclen = l.and_then(|l| l.topiclen).unwrap_or(390); let monitor = l.and_then(|l| l.monitor_entries).unwrap_or(100); let whowas = l.and_then(|l| l.whowas_entries).unwrap_or(100); let chanlist = l.and_then(|l| l.chan_list_modes).unwrap_or(100); let regmsgs = l.and_then(|l| l.registration_messages).unwrap_or(1024); let ml = l.and_then(|l| l.multiline.as_ref()); let ml_bytes = ml.and_then(|m| m.max_bytes).unwrap_or(4096); let ml_lines = ml.and_then(|m| m.max_lines).unwrap_or(100); lines.push(format!( "\nlimits:\n nicklen: {nicklen}\n identlen: {identlen}\n realnamelen: {realnamelen}\n channellen: {channellen}\n awaylen: {awaylen}\n kicklen: {kicklen}\n topiclen: {topiclen}\n monitor-entries: {monitor}\n whowas-entries: {whowas}\n chan-list-modes: {chanlist}\n registration-messages: {regmsgs}\n multiline:\n max-bytes: {ml_bytes}\n max-lines: {ml_lines}" )); } if let Some(hash) = oper_hash { let class_name = spec .oper_credentials .as_ref() .and_then(|o| o.class_name.as_deref()) .unwrap_or("server-admin"); lines.push(format!( "\nopers:\n admin:\n class: {class_name}\n password: \"{}\"", hash )); } { let v = spec.vhosts.as_ref(); let vhost_enabled = v.and_then(|v| v.enabled).unwrap_or(true); let vhost_maxlen = v.and_then(|v| v.max_length).unwrap_or(64); let vhost_regexp = v .and_then(|v| v.valid_regexp.as_deref()) .unwrap_or("^[a-zA-Z0-9.\\-]+$"); lines.push(format!( "\naccounts:\n vhosts:\n enabled: {vhost_enabled}\n max-length: {vhost_maxlen}\n valid-regexp: '{vhost_regexp}'" )); } if let Some(oauth2) = &spec.oauth2 { if let Some(secret) = oauth2_secret { let client_id = oauth2.client_id.as_deref().unwrap_or("ergo"); lines.push(format!( " oauth2:\n enabled: true\n autocreate: {}\n introspection-url: \"{}\"\n introspection-timeout: 10s\n client-id: {client_id}\n client-secret: \"{}\"", oauth2.autocreate, oauth2.introspection_url, secret )); } } lines.push(String::new()); lines.join("\n") } #[cfg(test)] mod tests { use super::*; use crate::testutil::test_network; #[test] fn config_contains_hostname() { let cm = build_configmap(&test_network(), None, None); let data = cm.data.unwrap(); let config = &data["ircd.yaml"]; assert!(config.contains("test-network.irc.now")); } #[test] fn config_contains_network_name() { let cm = build_configmap(&test_network(), None, None); let data = cm.data.unwrap(); let config = &data["ircd.yaml"]; assert!(config.contains("Test Network")); } #[test] fn config_contains_listeners() { let cm = build_configmap(&test_network(), None, None); let data = cm.data.unwrap(); let config = &data["ircd.yaml"]; assert!(config.contains(":6697")); assert!(config.contains(":6667")); } #[test] fn config_contains_tls_paths() { let cm = build_configmap(&test_network(), None, None); let data = cm.data.unwrap(); let config = &data["ircd.yaml"]; assert!(config.contains("/config/tls/tls.crt")); assert!(config.contains("/config/tls/tls.key")); } #[test] fn config_contains_oper_hash() { let hash = "$2a$10$examplehash"; let cm = build_configmap(&test_network(), Some(hash), None); let data = cm.data.unwrap(); let config = &data["ircd.yaml"]; assert!(config.contains(hash)); assert!(config.contains("server-admin")); } #[test] fn config_without_oper_omits_opers_section() { let cm = build_configmap(&test_network(), None, None); let data = cm.data.unwrap(); let config = &data["ircd.yaml"]; assert!(!config.contains("opers")); } #[test] fn network_name_defaults_to_hostname() { let mut network = test_network(); network.spec.network_name = None; let cm = build_configmap(&network, None, None); let data = cm.data.unwrap(); let config = &data["ircd.yaml"]; assert!(config.contains("test-network.irc.now")); } #[test] fn configmap_named_correctly() { let cm = build_configmap(&test_network(), None, None); assert_eq!(cm.metadata.name.unwrap(), "test-network-config"); } #[test] fn configmap_has_owner_reference() { let cm = build_configmap(&test_network(), None, None); let orefs = cm.metadata.owner_references.unwrap(); assert_eq!(orefs.len(), 1); assert_eq!(orefs[0].name, "test-network"); } #[test] fn config_contains_oauth2_when_configured() { let cm = build_configmap(&test_network(), None, Some("test-secret")); let data = cm.data.unwrap(); let config = &data["ircd.yaml"]; assert!(config.contains("oauth2")); assert!(config.contains("introspection")); assert!(config.contains("autocreate: true")); } #[test] fn config_omits_oauth2_when_not_configured() { let mut network = test_network(); network.spec.oauth2 = None; let cm = build_configmap(&network, None, None); let data = cm.data.unwrap(); let config = &data["ircd.yaml"]; assert!(!config.contains("oauth2")); } #[test] fn config_omits_oauth2_without_secret() { let cm = build_configmap(&test_network(), None, None); let data = cm.data.unwrap(); let config = &data["ircd.yaml"]; assert!(!config.contains("oauth2")); } #[test] fn config_contains_cloaking_when_configured() { let cm = build_configmap(&test_network(), None, None); let data = cm.data.unwrap(); let config = &data["ircd.yaml"]; assert!(config.contains("ip-cloaking")); assert!(config.contains("irc.now")); } #[test] fn config_omits_cloaking_when_not_configured() { let mut network = test_network(); network.spec.cloaking = None; let cm = build_configmap(&network, None, None); let data = cm.data.unwrap(); let config = &data["ircd.yaml"]; assert!(!config.contains("ip-cloaking")); } }