Skip to main content

greentic_operator/
config.rs

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