1#![allow(dead_code)]
2
3use std::collections::BTreeMap;
4use std::path::{Path, PathBuf};
5
6use serde::Deserialize;
7
8#[derive(Clone, Debug, Deserialize, Default)]
9pub struct OperatorConfig {
10 #[serde(default)]
11 pub services: Option<OperatorServicesConfig>,
12 #[serde(default)]
13 pub binaries: BTreeMap<String, String>,
14 #[serde(default)]
15 pub webchat: Option<WebchatConfig>,
16}
17
18#[derive(Clone, Debug, Deserialize, Default)]
19pub struct WebchatConfig {
20 #[serde(default)]
21 pub notifier: crate::notifier::NotifierConfig,
22}
23#[derive(Clone, Copy, Debug, Default, Deserialize)]
24#[serde(rename_all = "lowercase")]
25pub enum DomainEnabledMode {
26 #[default]
27 Auto,
28 True,
29 False,
30}
31
32impl DomainEnabledMode {
33 pub fn is_enabled(self, has_providers: bool) -> bool {
34 match self {
35 Self::Auto => has_providers,
36 Self::True => true,
37 Self::False => false,
38 }
39 }
40}
41
42#[derive(Clone, Debug, Deserialize, Default)]
43pub struct OperatorServicesConfig {
44 #[serde(default)]
45 pub messaging: DomainServicesConfig,
46 #[serde(default)]
47 pub events: DomainServicesConfig,
48}
49
50#[derive(Clone, Debug, Deserialize, Default)]
51pub struct DomainServicesConfig {
52 #[serde(default)]
53 pub enabled: DomainEnabledMode,
54 #[serde(default)]
55 pub components: Vec<ServiceComponentConfig>,
56}
57
58#[derive(Clone, Debug, Deserialize)]
59pub struct ServiceComponentConfig {
60 pub id: String,
61 pub binary: String,
62 #[serde(default)]
63 pub args: Vec<String>,
64}
65
66pub fn load_operator_config(root: &Path) -> anyhow::Result<Option<OperatorConfig>> {
67 let path = root.join("greentic.yaml");
68 if !path.exists() {
69 return Ok(None);
70 }
71 let contents = std::fs::read_to_string(&path)?;
72 if contents
73 .lines()
74 .all(|line| line.trim().is_empty() || line.trim().starts_with('#'))
75 {
76 return Ok(None);
77 }
78 let config: OperatorConfig = serde_yaml_bw::from_str(&contents)?;
79 Ok(Some(config))
80}
81
82pub fn binary_override(
83 config: Option<&OperatorConfig>,
84 name: &str,
85 config_dir: &Path,
86) -> Option<PathBuf> {
87 config.and_then(|config| config_binary_path(config, name, config_dir))
88}
89
90#[derive(Clone, Debug, Deserialize)]
91pub struct DemoConfig {
92 #[serde(default = "default_demo_tenant")]
93 pub tenant: String,
94 #[serde(default = "default_demo_team")]
95 pub team: String,
96 #[serde(default)]
97 pub services: DemoServicesConfig,
98 #[serde(default)]
99 pub providers: Option<std::collections::BTreeMap<String, DemoProviderConfig>>,
100}
101
102impl Default for DemoConfig {
103 fn default() -> Self {
104 Self {
105 tenant: default_demo_tenant(),
106 team: default_demo_team(),
107 services: DemoServicesConfig::default(),
108 providers: None,
109 }
110 }
111}
112
113#[derive(Clone, Debug, Deserialize, Default)]
114pub struct DemoServicesConfig {
115 #[serde(default)]
116 pub nats: DemoNatsConfig,
117 #[serde(default)]
118 pub gateway: DemoGatewayConfig,
119 #[serde(default)]
120 pub egress: DemoEgressConfig,
121 #[serde(default)]
122 pub subscriptions: DemoSubscriptionsConfig,
123 #[serde(default)]
124 pub events: DemoEventsConfig,
125}
126
127#[derive(Clone, Debug, Deserialize)]
128pub struct DemoNatsConfig {
129 #[serde(default = "default_true")]
130 pub enabled: bool,
131 #[serde(default = "default_nats_url")]
132 pub url: String,
133 #[serde(default)]
134 pub spawn: DemoNatsSpawnConfig,
135}
136
137#[derive(Clone, Debug, Deserialize)]
138pub struct DemoNatsSpawnConfig {
139 #[serde(default = "default_true")]
140 pub enabled: bool,
141 #[serde(default = "default_nats_binary")]
142 pub binary: String,
143 #[serde(default = "default_nats_args")]
144 pub args: Vec<String>,
145}
146
147#[derive(Clone, Debug, Deserialize)]
148pub struct DemoGatewayConfig {
149 #[serde(default = "default_gateway_binary")]
150 pub binary: String,
151 #[serde(default = "default_gateway_listen_addr")]
152 pub listen_addr: String,
153 #[serde(default = "default_gateway_port")]
154 pub port: u16,
155 #[serde(default)]
156 pub args: Vec<String>,
157}
158
159#[derive(Clone, Debug, Deserialize)]
160pub struct DemoEgressConfig {
161 #[serde(default = "default_egress_binary")]
162 pub binary: String,
163 #[serde(default)]
164 pub args: Vec<String>,
165}
166
167#[derive(Clone, Debug, Deserialize)]
168pub struct DemoSubscriptionsConfig {
169 #[serde(default = "default_subscriptions_mode")]
170 pub mode: DemoSubscriptionsMode,
171 #[serde(default)]
172 pub universal: DemoSubscriptionsUniversalConfig,
173 #[serde(default)]
174 pub msgraph: DemoMsgraphSubscriptionsConfig,
175}
176
177impl Default for DemoSubscriptionsConfig {
178 fn default() -> Self {
179 Self {
180 mode: default_subscriptions_mode(),
181 universal: DemoSubscriptionsUniversalConfig::default(),
182 msgraph: DemoMsgraphSubscriptionsConfig::default(),
183 }
184 }
185}
186
187#[derive(Clone, Copy, Debug, Deserialize, Default)]
188#[serde(rename_all = "snake_case")]
189pub enum DemoSubscriptionsMode {
190 #[default]
191 LegacyGsm,
192 UniversalOps,
193}
194
195fn default_subscriptions_mode() -> DemoSubscriptionsMode {
196 DemoSubscriptionsMode::LegacyGsm
197}
198
199#[derive(Clone, Debug, Deserialize)]
200pub struct DemoSubscriptionsUniversalConfig {
201 #[serde(default = "default_universal_renew_interval")]
202 pub renew_interval_seconds: u64,
203 #[serde(default = "default_universal_renew_skew")]
204 pub renew_skew_minutes: u64,
205 #[serde(default)]
206 pub desired: Vec<DemoDesiredSubscription>,
207}
208
209impl Default for DemoSubscriptionsUniversalConfig {
210 fn default() -> Self {
211 Self {
212 renew_interval_seconds: default_universal_renew_interval(),
213 renew_skew_minutes: default_universal_renew_skew(),
214 desired: Vec::new(),
215 }
216 }
217}
218
219fn default_universal_renew_interval() -> u64 {
220 60
221}
222
223fn default_universal_renew_skew() -> u64 {
224 10
225}
226
227#[derive(Clone, Debug, Deserialize)]
228pub struct DemoDesiredSubscription {
229 pub provider: String,
230 pub resource: String,
231 #[serde(default = "default_change_types")]
232 pub change_types: Vec<String>,
233 #[serde(default)]
234 pub notification_url: Option<String>,
235 #[serde(default)]
236 pub client_state: Option<String>,
237 #[serde(default)]
238 pub binding_id: Option<String>,
239 #[serde(default)]
240 pub user: Option<AuthUserConfig>,
241}
242
243fn default_change_types() -> Vec<String> {
244 vec!["created".to_string()]
245}
246
247#[derive(Clone, Debug, Deserialize)]
248pub struct AuthUserConfig {
249 pub user_id: String,
250 pub token_key: String,
251}
252
253#[derive(Clone, Debug, Deserialize)]
254pub struct DemoEventsConfig {
255 #[serde(default)]
256 pub enabled: DomainEnabledMode,
257 #[serde(default = "default_events_components")]
258 pub components: Vec<ServiceComponentConfig>,
259}
260
261#[derive(Clone, Debug, Deserialize)]
262pub struct DemoMsgraphSubscriptionsConfig {
263 #[serde(default = "default_true")]
264 pub enabled: bool,
265 #[serde(default = "default_msgraph_binary")]
266 pub binary: String,
267 #[serde(default = "default_msgraph_mode")]
268 pub mode: String,
269 #[serde(default)]
270 pub args: Vec<String>,
271}
272
273#[derive(Clone, Debug, Deserialize)]
274pub struct DemoProviderConfig {
275 #[serde(default)]
276 pub pack: Option<String>,
277 #[serde(default)]
278 pub setup_flow: Option<String>,
279 #[serde(default)]
280 pub verify_flow: Option<String>,
281}
282
283impl Default for DemoNatsConfig {
284 fn default() -> Self {
285 Self {
286 enabled: true,
287 url: default_nats_url(),
288 spawn: DemoNatsSpawnConfig::default(),
289 }
290 }
291}
292
293impl Default for DemoNatsSpawnConfig {
294 fn default() -> Self {
295 Self {
296 enabled: true,
297 binary: default_nats_binary(),
298 args: default_nats_args(),
299 }
300 }
301}
302
303impl Default for DemoGatewayConfig {
304 fn default() -> Self {
305 Self {
306 binary: default_gateway_binary(),
307 listen_addr: default_gateway_listen_addr(),
308 port: default_gateway_port(),
309 args: Vec::new(),
310 }
311 }
312}
313
314impl Default for DemoEgressConfig {
315 fn default() -> Self {
316 Self {
317 binary: default_egress_binary(),
318 args: Vec::new(),
319 }
320 }
321}
322
323impl Default for DemoMsgraphSubscriptionsConfig {
324 fn default() -> Self {
325 Self {
326 enabled: true,
327 binary: default_msgraph_binary(),
328 mode: default_msgraph_mode(),
329 args: Vec::new(),
330 }
331 }
332}
333
334impl Default for DemoEventsConfig {
335 fn default() -> Self {
336 Self {
337 enabled: DomainEnabledMode::Auto,
338 components: default_events_components(),
339 }
340 }
341}
342
343pub fn load_demo_config(path: &Path) -> anyhow::Result<DemoConfig> {
344 let contents = std::fs::read_to_string(path)?;
345 let config: DemoConfig = serde_yaml_bw::from_str(&contents)?;
346 Ok(config)
347}
348
349fn config_binary_path(config: &OperatorConfig, name: &str, config_dir: &Path) -> Option<PathBuf> {
350 config
351 .binaries
352 .get(name)
353 .map(|value| resolve_path(config_dir, value))
354}
355
356fn resolve_path(base: &Path, value: &str) -> PathBuf {
357 let path = PathBuf::from(value);
358 if path.is_absolute() {
359 path
360 } else {
361 base.join(path)
362 }
363}
364
365fn default_demo_tenant() -> String {
366 "demo".to_string()
367}
368
369fn default_demo_team() -> String {
370 "default".to_string()
371}
372
373fn default_true() -> bool {
374 true
375}
376
377pub fn default_nats_url() -> String {
378 "nats://127.0.0.1:4222".to_string()
379}
380
381pub fn default_receive_nats_url() -> String {
382 "nats://127.0.0.1:4347".to_string()
383}
384
385fn default_nats_binary() -> String {
386 "nats-server".to_string()
387}
388
389fn default_nats_args() -> Vec<String> {
390 vec!["-p".to_string(), "4222".to_string(), "-js".to_string()]
391}
392
393fn default_gateway_binary() -> String {
394 "gateway".to_string()
395}
396
397fn default_gateway_listen_addr() -> String {
398 "127.0.0.1".to_string()
399}
400
401fn default_gateway_port() -> u16 {
402 8080
403}
404
405fn default_egress_binary() -> String {
406 "egress".to_string()
407}
408
409fn default_msgraph_binary() -> String {
410 "subscriptions-msgraph".to_string()
411}
412
413fn default_msgraph_mode() -> String {
414 "poll".to_string()
415}
416
417pub(crate) fn default_events_components() -> Vec<ServiceComponentConfig> {
418 Vec::new()
419}
420
421#[cfg(test)]
422mod webchat_config_tests {
423 use super::*;
424
425 #[test]
426 fn operator_config_parses_webchat_notifier_redis() {
427 let yaml = "\
428binaries:
429 some_binary: /usr/bin/foo
430webchat:
431 notifier:
432 backend: redis
433";
434 let cfg: OperatorConfig = serde_yaml_bw::from_str(yaml).expect("parse");
435 let webchat = cfg.webchat.expect("webchat section present");
436 match webchat.notifier {
437 crate::notifier::NotifierConfig::Redis { url, .. } => assert!(url.is_none()),
438 _ => panic!("expected Redis notifier"),
439 }
440 }
441
442 #[test]
443 fn operator_config_webchat_absent_is_none() {
444 let yaml = "binaries: {}\n";
445 let cfg: OperatorConfig = serde_yaml_bw::from_str(yaml).expect("parse");
446 assert!(cfg.webchat.is_none());
447 }
448}