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}