1use crate::paths::DefaultPaths;
2use greentic_config_types::ConfigVersion;
3use greentic_types::{ConnectionKind, DeploymentCtx, EnvId};
4use serde::{Deserialize, Serialize};
5use std::fs;
6use std::path::{Path, PathBuf};
7
8const ENV_PREFIX: &str = "GREENTIC_";
9pub const DEFAULT_DEPLOYER_BASE_DOMAIN: &str = "deploy.greentic.ai";
10
11#[derive(Debug, Clone, Copy)]
12pub enum ConfigFileFormat {
13 Toml,
14 Json,
15}
16
17#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
18pub struct ConfigLayer {
19 #[serde(default)]
20 pub schema_version: Option<ConfigVersion>,
21 #[serde(default)]
22 pub environment: Option<EnvironmentLayer>,
23 #[serde(default)]
24 pub paths: Option<PathsLayer>,
25 #[serde(default)]
26 pub services: Option<ServicesLayer>,
27 #[serde(default)]
28 pub events: Option<EventsLayer>,
29 #[serde(default)]
30 pub runtime: Option<RuntimeLayer>,
31 #[serde(default)]
32 pub telemetry: Option<TelemetryLayer>,
33 #[serde(default)]
34 pub network: Option<NetworkLayer>,
35 #[serde(default)]
36 pub deployer: Option<DeployerLayer>,
37 #[serde(default)]
38 pub secrets: Option<SecretsBackendRefLayer>,
39 #[serde(default)]
40 pub packs: Option<PacksLayer>,
41 #[serde(default)]
42 pub dev: Option<DevLayer>,
43}
44
45#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
46pub struct EnvironmentLayer {
47 #[serde(default)]
48 pub env_id: Option<EnvId>,
49 #[serde(default)]
50 pub deployment: Option<DeploymentCtx>,
51 #[serde(default)]
52 pub connection: Option<ConnectionKind>,
53 #[serde(default)]
54 pub region: Option<String>,
55}
56
57#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
58pub struct PathsLayer {
59 #[serde(default)]
60 pub greentic_root: Option<PathBuf>,
61 #[serde(default)]
62 pub state_dir: Option<PathBuf>,
63 #[serde(default)]
64 pub cache_dir: Option<PathBuf>,
65 #[serde(default)]
66 pub logs_dir: Option<PathBuf>,
67}
68
69#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
70pub struct ServicesLayer {
71 #[serde(default)]
72 pub events: Option<ServiceEndpointLayer>,
73 #[serde(default)]
74 pub runner: Option<ServiceLayer>,
75 #[serde(default)]
76 pub deployer: Option<ServiceLayer>,
77 #[serde(default)]
78 pub events_transport: Option<ServiceLayer>,
79 #[serde(default)]
80 pub source: Option<ServiceLayer>,
81 #[serde(default)]
82 pub publish: Option<ServiceLayer>,
83 #[serde(default)]
84 pub metadata: Option<ServiceLayer>,
85 #[serde(default)]
86 pub oauth_broker: Option<ServiceLayer>,
87}
88
89#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
90pub struct ServiceEndpointLayer {
91 #[serde(default)]
92 pub url: Option<url::Url>,
93 #[serde(default)]
94 pub headers: Option<std::collections::BTreeMap<String, String>>,
95}
96
97#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
98pub struct EventsLayer {
99 #[serde(default)]
100 pub reconnect: Option<ReconnectLayer>,
101 #[serde(default)]
102 pub backoff: Option<BackoffLayer>,
103}
104
105#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
106pub struct ServiceTransportLayer {
107 #[serde(default)]
108 pub kind: Option<String>,
109 #[serde(default)]
110 pub url: Option<url::Url>,
111 #[serde(default)]
112 pub headers: Option<std::collections::BTreeMap<String, String>>,
113 #[serde(default)]
114 pub subject_prefix: Option<String>,
115}
116
117#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
118pub struct ServiceLayer {
119 #[serde(default, flatten)]
120 pub transport: ServiceTransportLayer,
121 #[serde(default)]
122 pub service: Option<ServiceConfigLayer>,
123}
124
125#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
126pub struct ServiceConfigLayer {
127 #[serde(default)]
128 pub bind_addr: Option<String>,
129 #[serde(default)]
130 pub port: Option<u16>,
131 #[serde(default)]
132 pub public_base_url: Option<String>,
133 #[serde(default)]
134 pub metrics: Option<MetricsLayer>,
135}
136
137#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
138pub struct MetricsLayer {
139 #[serde(default)]
140 pub enabled: Option<bool>,
141 #[serde(default)]
142 pub bind_addr: Option<String>,
143 #[serde(default)]
144 pub port: Option<u16>,
145 #[serde(default)]
146 pub path: Option<String>,
147}
148
149#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
150pub struct ReconnectLayer {
151 #[serde(default)]
152 pub enabled: Option<bool>,
153 #[serde(default)]
154 pub max_retries: Option<u32>,
155}
156
157#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
158pub struct BackoffLayer {
159 #[serde(default)]
160 pub initial_ms: Option<u64>,
161 #[serde(default)]
162 pub max_ms: Option<u64>,
163 #[serde(default)]
164 pub multiplier: Option<f64>,
165 #[serde(default)]
166 pub jitter: Option<bool>,
167}
168
169#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
170pub struct RuntimeLayer {
171 #[serde(default)]
172 pub max_concurrency: Option<u32>,
173 #[serde(default)]
174 pub task_timeout_ms: Option<u64>,
175 #[serde(default)]
176 pub shutdown_grace_ms: Option<u64>,
177 #[serde(default)]
178 pub admin_endpoints: Option<AdminEndpointsLayer>,
179}
180
181#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
182pub struct AdminEndpointsLayer {
183 #[serde(default)]
184 pub secrets_explain_enabled: Option<bool>,
185}
186
187#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
188pub struct TelemetryLayer {
189 #[serde(default)]
190 pub enabled: Option<bool>,
191 #[serde(default)]
192 pub exporter: Option<String>,
193 #[serde(default)]
194 pub endpoint: Option<String>,
195 #[serde(default)]
196 pub sampling: Option<f32>,
197}
198
199#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
200pub struct NetworkLayer {
201 #[serde(default)]
202 pub proxy_url: Option<String>,
203 #[serde(default)]
204 pub tls_mode: Option<String>,
205 #[serde(default)]
206 pub connect_timeout_ms: Option<u64>,
207 #[serde(default)]
208 pub read_timeout_ms: Option<u64>,
209}
210
211#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
212pub struct DeployerLayer {
213 #[serde(default)]
214 pub base_domain: Option<String>,
215 #[serde(default)]
216 pub provider: Option<DeployerProviderDefaultsLayer>,
217}
218
219#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
220pub struct DeployerProviderDefaultsLayer {
221 #[serde(default)]
222 pub provider_kind: Option<String>,
223 #[serde(default)]
224 pub region: Option<String>,
225}
226
227#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
228pub struct SecretsBackendRefLayer {
229 #[serde(default)]
230 pub kind: Option<String>,
231 #[serde(default)]
232 pub reference: Option<String>,
233}
234
235#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
236pub struct PacksLayer {
237 #[serde(default)]
238 pub source: Option<PackSourceLayer>,
239 #[serde(default)]
240 pub cache_dir: Option<PathBuf>,
241 #[serde(default)]
242 pub index_cache_ttl_secs: Option<u64>,
243 #[serde(default)]
244 pub trust: Option<PackTrustLayer>,
245}
246
247#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
248#[serde(tag = "type", rename_all = "snake_case")]
249pub enum PackSourceLayer {
250 LocalIndex {
251 #[serde(default)]
252 path: Option<PathBuf>,
253 },
254 HttpIndex {
255 #[serde(default)]
256 url: Option<String>,
257 },
258 OciRegistry {
259 #[serde(default)]
260 reference: Option<String>,
261 },
262}
263
264impl Default for PackSourceLayer {
265 fn default() -> Self {
266 Self::LocalIndex { path: None }
267 }
268}
269
270#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
271pub struct PackTrustLayer {
272 #[serde(default)]
273 pub public_keys: Option<Vec<String>>,
274 #[serde(default)]
275 pub require_signatures: Option<bool>,
276}
277
278#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
279pub struct DevLayer {
280 #[serde(default)]
281 pub default_env: Option<EnvId>,
282 #[serde(default)]
283 pub default_tenant: Option<String>,
284 #[serde(default)]
285 pub default_team: Option<String>,
286}
287
288pub fn default_layer(root: &Path, defaults: &DefaultPaths) -> ConfigLayer {
289 ConfigLayer {
290 schema_version: Some(ConfigVersion::v1()),
291 environment: Some(EnvironmentLayer {
292 env_id: Some(default_env_id()),
293 deployment: None,
294 connection: None,
295 region: None,
296 }),
297 paths: Some(PathsLayer {
298 greentic_root: Some(root.to_path_buf()),
299 state_dir: Some(defaults.state_dir.clone()),
300 cache_dir: Some(defaults.cache_dir.clone()),
301 logs_dir: Some(defaults.logs_dir.clone()),
302 }),
303 services: None,
304 events: Some(EventsLayer {
305 reconnect: Some(ReconnectLayer {
306 enabled: Some(true),
307 max_retries: Some(50),
308 }),
309 backoff: Some(BackoffLayer {
310 initial_ms: Some(250),
311 max_ms: Some(30_000),
312 multiplier: Some(2.0),
313 jitter: Some(true),
314 }),
315 }),
316 runtime: Some(RuntimeLayer::default()),
317 telemetry: Some(TelemetryLayer {
318 enabled: Some(true),
319 exporter: Some("none".to_string()),
320 endpoint: None,
321 sampling: Some(1.0),
322 }),
323 network: Some(NetworkLayer {
324 proxy_url: None,
325 tls_mode: Some("system".to_string()),
326 connect_timeout_ms: None,
327 read_timeout_ms: None,
328 }),
329 deployer: Some(DeployerLayer {
330 base_domain: Some(DEFAULT_DEPLOYER_BASE_DOMAIN.to_string()),
331 provider: None,
332 }),
333 secrets: Some(SecretsBackendRefLayer {
334 kind: Some("none".to_string()),
335 reference: None,
336 }),
337 packs: None,
338 dev: None,
339 }
340}
341
342pub fn load_user_config() -> anyhow::Result<ConfigLayer> {
343 let Some(dirs) = directories::ProjectDirs::from("com", "greentic", "greentic") else {
344 return Ok(ConfigLayer::default());
345 };
346 let path = dirs.config_dir().join("config.toml");
347 load_config_file_if_exists(&path)
348}
349
350pub fn load_user_config_with_origin() -> anyhow::Result<(ConfigLayer, Option<PathBuf>)> {
351 let Some(dirs) = directories::ProjectDirs::from("com", "greentic", "greentic") else {
352 return Ok((ConfigLayer::default(), None));
353 };
354 let path = dirs.config_dir().join("config.toml");
355 let layer = load_config_file_if_exists(&path)?;
356 let abs = crate::paths::absolute_path(&path)?;
357 Ok((layer, Some(abs)))
358}
359
360pub fn load_project_config(project_root: &Path) -> anyhow::Result<ConfigLayer> {
361 let path = project_root.join(".greentic").join("config.toml");
362 load_config_file_if_exists(&path)
363}
364
365pub fn load_project_config_with_origin(
366 project_root: &Path,
367) -> anyhow::Result<(ConfigLayer, PathBuf)> {
368 let path = project_root.join(".greentic").join("config.toml");
369 let layer = load_project_config(project_root)?;
370 let abs = crate::paths::absolute_path(&path)?;
371 Ok((layer, abs))
372}
373
374pub fn load_config_file_required(path: &Path) -> anyhow::Result<ConfigLayer> {
375 let abs = crate::paths::absolute_path(path)?;
376 if !abs.exists() {
377 return Err(anyhow::anyhow!(
378 "explicit config file not found: {}\nHint: pass an existing file to --config / with_config_path()",
379 abs.display()
380 ));
381 }
382 let contents = fs::read_to_string(&abs)?;
383 let format = match abs.extension().and_then(|s| s.to_str()) {
384 Some("json") => ConfigFileFormat::Json,
385 _ => ConfigFileFormat::Toml,
386 };
387 parse_layer(&contents, format)
388}
389
390fn load_config_file_if_exists(path: &Path) -> anyhow::Result<ConfigLayer> {
391 if !path.exists() {
392 return Ok(ConfigLayer::default());
393 }
394 let contents = fs::read_to_string(path)?;
395 let format = match path.extension().and_then(|s| s.to_str()) {
396 Some("json") => ConfigFileFormat::Json,
397 _ => ConfigFileFormat::Toml,
398 };
399 let layer = parse_layer(&contents, format)?;
400 Ok(layer)
401}
402
403fn parse_layer(contents: &str, format: ConfigFileFormat) -> anyhow::Result<ConfigLayer> {
404 let layer = match format {
405 ConfigFileFormat::Toml => toml::from_str::<ConfigLayer>(contents)?,
406 ConfigFileFormat::Json => serde_json::from_str::<ConfigLayer>(contents)?,
407 };
408 Ok(layer)
409}
410
411pub fn load_env_layer() -> (ConfigLayer, Vec<String>) {
412 let mut layer = ConfigLayer::default();
413 let mut warnings = Vec::new();
414 for (key, value) in std::env::vars() {
415 if !key.starts_with(ENV_PREFIX) {
416 continue;
417 }
418 apply_env_var(&key, &value, &mut layer, &mut warnings);
419 }
420 (layer, warnings)
421}
422
423pub fn load_env_layers_detailed() -> Vec<(ConfigLayer, String, Vec<String>)> {
424 load_env_layers_detailed_from(std::env::vars())
425}
426
427pub fn load_env_layers_detailed_from<I>(vars: I) -> Vec<(ConfigLayer, String, Vec<String>)>
428where
429 I: IntoIterator<Item = (String, String)>,
430{
431 let mut layers = Vec::new();
432 for (key, value) in vars {
433 if !key.starts_with(ENV_PREFIX) {
434 continue;
435 }
436 let mut layer = ConfigLayer::default();
437 let mut warnings = Vec::new();
438 if apply_env_var(&key, &value, &mut layer, &mut warnings) {
439 layers.push((layer, key, warnings));
440 }
441 }
442 layers
443}
444
445fn apply_env_var(
446 key: &str,
447 value: &str,
448 layer: &mut ConfigLayer,
449 warnings: &mut Vec<String>,
450) -> bool {
451 match key {
452 "GREENTIC_SCHEMA_VERSION" => layer.schema_version = Some(ConfigVersion(value.to_string())),
453 "GREENTIC_ENVIRONMENT_ENV_ID" => {
454 layer
455 .environment
456 .get_or_insert_with(Default::default)
457 .env_id = parse_string_as::<EnvId>(value)
458 }
459 "GREENTIC_ENVIRONMENT_DEPLOYMENT" => {
460 layer
461 .environment
462 .get_or_insert_with(Default::default)
463 .deployment = parse_string_as::<DeploymentCtx>(value)
464 }
465 "GREENTIC_ENVIRONMENT_CONNECTION" => {
466 layer
467 .environment
468 .get_or_insert_with(Default::default)
469 .connection = parse_string_as::<ConnectionKind>(value)
470 }
471 "GREENTIC_ENVIRONMENT_REGION" => {
472 layer
473 .environment
474 .get_or_insert_with(Default::default)
475 .region = Some(value.to_string())
476 }
477 "GREENTIC_PATHS_GREENTIC_ROOT" => {
478 layer
479 .paths
480 .get_or_insert_with(Default::default)
481 .greentic_root = Some(PathBuf::from(value))
482 }
483 "GREENTIC_PATHS_STATE_DIR" => {
484 layer.paths.get_or_insert_with(Default::default).state_dir = Some(PathBuf::from(value))
485 }
486 "GREENTIC_PATHS_CACHE_DIR" => {
487 layer.paths.get_or_insert_with(Default::default).cache_dir = Some(PathBuf::from(value))
488 }
489 "GREENTIC_PATHS_LOGS_DIR" => {
490 layer.paths.get_or_insert_with(Default::default).logs_dir = Some(PathBuf::from(value))
491 }
492 "GREENTIC_SERVICES_EVENTS_URL" => {
493 layer
494 .services
495 .get_or_insert_with(Default::default)
496 .events
497 .get_or_insert_with(Default::default)
498 .url = parse_string_as::<url::Url>(value)
499 }
500 "GREENTIC_SERVICES_RUNNER_KIND" => {
501 layer
502 .services
503 .get_or_insert_with(Default::default)
504 .runner
505 .get_or_insert_with(Default::default)
506 .transport
507 .kind = Some(value.to_lowercase())
508 }
509 "GREENTIC_SERVICES_RUNNER_URL" => {
510 layer
511 .services
512 .get_or_insert_with(Default::default)
513 .runner
514 .get_or_insert_with(Default::default)
515 .transport
516 .url = parse_string_as::<url::Url>(value)
517 }
518 "GREENTIC_SERVICES_RUNNER_BIND_ADDR" => {
519 layer
520 .services
521 .get_or_insert_with(Default::default)
522 .runner
523 .get_or_insert_with(Default::default)
524 .service
525 .get_or_insert_with(Default::default)
526 .bind_addr = Some(value.to_string());
527 }
528 "GREENTIC_SERVICES_RUNNER_PORT" => {
529 layer
530 .services
531 .get_or_insert_with(Default::default)
532 .runner
533 .get_or_insert_with(Default::default)
534 .service
535 .get_or_insert_with(Default::default)
536 .port = parse_u16_warn(value, key, warnings);
537 }
538 "GREENTIC_SERVICES_RUNNER_PUBLIC_BASE_URL" => {
539 layer
540 .services
541 .get_or_insert_with(Default::default)
542 .runner
543 .get_or_insert_with(Default::default)
544 .service
545 .get_or_insert_with(Default::default)
546 .public_base_url = Some(value.to_string());
547 }
548 "GREENTIC_SERVICES_RUNNER_METRICS_ENABLED" => {
549 layer
550 .services
551 .get_or_insert_with(Default::default)
552 .runner
553 .get_or_insert_with(Default::default)
554 .service
555 .get_or_insert_with(Default::default)
556 .metrics
557 .get_or_insert_with(Default::default)
558 .enabled = parse_bool(value);
559 }
560 "GREENTIC_SERVICES_RUNNER_METRICS_BIND_ADDR" => {
561 layer
562 .services
563 .get_or_insert_with(Default::default)
564 .runner
565 .get_or_insert_with(Default::default)
566 .service
567 .get_or_insert_with(Default::default)
568 .metrics
569 .get_or_insert_with(Default::default)
570 .bind_addr = Some(value.to_string());
571 }
572 "GREENTIC_SERVICES_RUNNER_METRICS_PORT" => {
573 layer
574 .services
575 .get_or_insert_with(Default::default)
576 .runner
577 .get_or_insert_with(Default::default)
578 .service
579 .get_or_insert_with(Default::default)
580 .metrics
581 .get_or_insert_with(Default::default)
582 .port = parse_u16_warn(value, key, warnings);
583 }
584 "GREENTIC_SERVICES_RUNNER_METRICS_PATH" => {
585 layer
586 .services
587 .get_or_insert_with(Default::default)
588 .runner
589 .get_or_insert_with(Default::default)
590 .service
591 .get_or_insert_with(Default::default)
592 .metrics
593 .get_or_insert_with(Default::default)
594 .path = Some(value.to_string());
595 }
596 "GREENTIC_SERVICES_DEPLOYER_KIND" => {
597 layer
598 .services
599 .get_or_insert_with(Default::default)
600 .deployer
601 .get_or_insert_with(Default::default)
602 .transport
603 .kind = Some(value.to_lowercase())
604 }
605 "GREENTIC_SERVICES_DEPLOYER_URL" => {
606 layer
607 .services
608 .get_or_insert_with(Default::default)
609 .deployer
610 .get_or_insert_with(Default::default)
611 .transport
612 .url = parse_string_as::<url::Url>(value)
613 }
614 "GREENTIC_SERVICES_DEPLOYER_BIND_ADDR" => {
615 layer
616 .services
617 .get_or_insert_with(Default::default)
618 .deployer
619 .get_or_insert_with(Default::default)
620 .service
621 .get_or_insert_with(Default::default)
622 .bind_addr = Some(value.to_string());
623 }
624 "GREENTIC_SERVICES_DEPLOYER_PORT" => {
625 layer
626 .services
627 .get_or_insert_with(Default::default)
628 .deployer
629 .get_or_insert_with(Default::default)
630 .service
631 .get_or_insert_with(Default::default)
632 .port = parse_u16_warn(value, key, warnings);
633 }
634 "GREENTIC_SERVICES_DEPLOYER_PUBLIC_BASE_URL" => {
635 layer
636 .services
637 .get_or_insert_with(Default::default)
638 .deployer
639 .get_or_insert_with(Default::default)
640 .service
641 .get_or_insert_with(Default::default)
642 .public_base_url = Some(value.to_string());
643 }
644 "GREENTIC_SERVICES_DEPLOYER_METRICS_ENABLED" => {
645 layer
646 .services
647 .get_or_insert_with(Default::default)
648 .deployer
649 .get_or_insert_with(Default::default)
650 .service
651 .get_or_insert_with(Default::default)
652 .metrics
653 .get_or_insert_with(Default::default)
654 .enabled = parse_bool(value);
655 }
656 "GREENTIC_SERVICES_DEPLOYER_METRICS_BIND_ADDR" => {
657 layer
658 .services
659 .get_or_insert_with(Default::default)
660 .deployer
661 .get_or_insert_with(Default::default)
662 .service
663 .get_or_insert_with(Default::default)
664 .metrics
665 .get_or_insert_with(Default::default)
666 .bind_addr = Some(value.to_string());
667 }
668 "GREENTIC_SERVICES_DEPLOYER_METRICS_PORT" => {
669 layer
670 .services
671 .get_or_insert_with(Default::default)
672 .deployer
673 .get_or_insert_with(Default::default)
674 .service
675 .get_or_insert_with(Default::default)
676 .metrics
677 .get_or_insert_with(Default::default)
678 .port = parse_u16_warn(value, key, warnings);
679 }
680 "GREENTIC_SERVICES_DEPLOYER_METRICS_PATH" => {
681 layer
682 .services
683 .get_or_insert_with(Default::default)
684 .deployer
685 .get_or_insert_with(Default::default)
686 .service
687 .get_or_insert_with(Default::default)
688 .metrics
689 .get_or_insert_with(Default::default)
690 .path = Some(value.to_string());
691 }
692 "GREENTIC_SERVICES_EVENTS_TRANSPORT_KIND" => {
693 layer
694 .services
695 .get_or_insert_with(Default::default)
696 .events_transport
697 .get_or_insert_with(Default::default)
698 .transport
699 .kind = Some(value.to_lowercase())
700 }
701 "GREENTIC_SERVICES_EVENTS_TRANSPORT_URL" => {
702 layer
703 .services
704 .get_or_insert_with(Default::default)
705 .events_transport
706 .get_or_insert_with(Default::default)
707 .transport
708 .url = parse_string_as::<url::Url>(value)
709 }
710 "GREENTIC_SERVICES_EVENTS_TRANSPORT_BIND_ADDR" => {
711 layer
712 .services
713 .get_or_insert_with(Default::default)
714 .events_transport
715 .get_or_insert_with(Default::default)
716 .service
717 .get_or_insert_with(Default::default)
718 .bind_addr = Some(value.to_string());
719 }
720 "GREENTIC_SERVICES_EVENTS_TRANSPORT_PORT" => {
721 layer
722 .services
723 .get_or_insert_with(Default::default)
724 .events_transport
725 .get_or_insert_with(Default::default)
726 .service
727 .get_or_insert_with(Default::default)
728 .port = parse_u16_warn(value, key, warnings);
729 }
730 "GREENTIC_SERVICES_EVENTS_TRANSPORT_PUBLIC_BASE_URL" => {
731 layer
732 .services
733 .get_or_insert_with(Default::default)
734 .events_transport
735 .get_or_insert_with(Default::default)
736 .service
737 .get_or_insert_with(Default::default)
738 .public_base_url = Some(value.to_string());
739 }
740 "GREENTIC_SERVICES_EVENTS_TRANSPORT_METRICS_ENABLED" => {
741 layer
742 .services
743 .get_or_insert_with(Default::default)
744 .events_transport
745 .get_or_insert_with(Default::default)
746 .service
747 .get_or_insert_with(Default::default)
748 .metrics
749 .get_or_insert_with(Default::default)
750 .enabled = parse_bool(value);
751 }
752 "GREENTIC_SERVICES_EVENTS_TRANSPORT_METRICS_BIND_ADDR" => {
753 layer
754 .services
755 .get_or_insert_with(Default::default)
756 .events_transport
757 .get_or_insert_with(Default::default)
758 .service
759 .get_or_insert_with(Default::default)
760 .metrics
761 .get_or_insert_with(Default::default)
762 .bind_addr = Some(value.to_string());
763 }
764 "GREENTIC_SERVICES_EVENTS_TRANSPORT_METRICS_PORT" => {
765 layer
766 .services
767 .get_or_insert_with(Default::default)
768 .events_transport
769 .get_or_insert_with(Default::default)
770 .service
771 .get_or_insert_with(Default::default)
772 .metrics
773 .get_or_insert_with(Default::default)
774 .port = parse_u16_warn(value, key, warnings);
775 }
776 "GREENTIC_SERVICES_EVENTS_TRANSPORT_METRICS_PATH" => {
777 layer
778 .services
779 .get_or_insert_with(Default::default)
780 .events_transport
781 .get_or_insert_with(Default::default)
782 .service
783 .get_or_insert_with(Default::default)
784 .metrics
785 .get_or_insert_with(Default::default)
786 .path = Some(value.to_string());
787 }
788 "GREENTIC_SERVICES_SOURCE_KIND" => {
789 layer
790 .services
791 .get_or_insert_with(Default::default)
792 .source
793 .get_or_insert_with(Default::default)
794 .transport
795 .kind = Some(value.to_lowercase())
796 }
797 "GREENTIC_SERVICES_SOURCE_URL" => {
798 layer
799 .services
800 .get_or_insert_with(Default::default)
801 .source
802 .get_or_insert_with(Default::default)
803 .transport
804 .url = parse_string_as::<url::Url>(value)
805 }
806 "GREENTIC_SERVICES_SOURCE_BIND_ADDR" => {
807 layer
808 .services
809 .get_or_insert_with(Default::default)
810 .source
811 .get_or_insert_with(Default::default)
812 .service
813 .get_or_insert_with(Default::default)
814 .bind_addr = Some(value.to_string());
815 }
816 "GREENTIC_SERVICES_SOURCE_PORT" => {
817 layer
818 .services
819 .get_or_insert_with(Default::default)
820 .source
821 .get_or_insert_with(Default::default)
822 .service
823 .get_or_insert_with(Default::default)
824 .port = parse_u16_warn(value, key, warnings);
825 }
826 "GREENTIC_SERVICES_SOURCE_PUBLIC_BASE_URL" => {
827 layer
828 .services
829 .get_or_insert_with(Default::default)
830 .source
831 .get_or_insert_with(Default::default)
832 .service
833 .get_or_insert_with(Default::default)
834 .public_base_url = Some(value.to_string());
835 }
836 "GREENTIC_SERVICES_SOURCE_METRICS_ENABLED" => {
837 layer
838 .services
839 .get_or_insert_with(Default::default)
840 .source
841 .get_or_insert_with(Default::default)
842 .service
843 .get_or_insert_with(Default::default)
844 .metrics
845 .get_or_insert_with(Default::default)
846 .enabled = parse_bool(value);
847 }
848 "GREENTIC_SERVICES_SOURCE_METRICS_BIND_ADDR" => {
849 layer
850 .services
851 .get_or_insert_with(Default::default)
852 .source
853 .get_or_insert_with(Default::default)
854 .service
855 .get_or_insert_with(Default::default)
856 .metrics
857 .get_or_insert_with(Default::default)
858 .bind_addr = Some(value.to_string());
859 }
860 "GREENTIC_SERVICES_SOURCE_METRICS_PORT" => {
861 layer
862 .services
863 .get_or_insert_with(Default::default)
864 .source
865 .get_or_insert_with(Default::default)
866 .service
867 .get_or_insert_with(Default::default)
868 .metrics
869 .get_or_insert_with(Default::default)
870 .port = parse_u16_warn(value, key, warnings);
871 }
872 "GREENTIC_SERVICES_SOURCE_METRICS_PATH" => {
873 layer
874 .services
875 .get_or_insert_with(Default::default)
876 .source
877 .get_or_insert_with(Default::default)
878 .service
879 .get_or_insert_with(Default::default)
880 .metrics
881 .get_or_insert_with(Default::default)
882 .path = Some(value.to_string());
883 }
884 "GREENTIC_SERVICES_PUBLISH_KIND" => {
885 layer
886 .services
887 .get_or_insert_with(Default::default)
888 .publish
889 .get_or_insert_with(Default::default)
890 .transport
891 .kind = Some(value.to_lowercase())
892 }
893 "GREENTIC_SERVICES_PUBLISH_URL" => {
894 layer
895 .services
896 .get_or_insert_with(Default::default)
897 .publish
898 .get_or_insert_with(Default::default)
899 .transport
900 .url = parse_string_as::<url::Url>(value)
901 }
902 "GREENTIC_SERVICES_PUBLISH_BIND_ADDR" => {
903 layer
904 .services
905 .get_or_insert_with(Default::default)
906 .publish
907 .get_or_insert_with(Default::default)
908 .service
909 .get_or_insert_with(Default::default)
910 .bind_addr = Some(value.to_string());
911 }
912 "GREENTIC_SERVICES_PUBLISH_PORT" => {
913 layer
914 .services
915 .get_or_insert_with(Default::default)
916 .publish
917 .get_or_insert_with(Default::default)
918 .service
919 .get_or_insert_with(Default::default)
920 .port = parse_u16_warn(value, key, warnings);
921 }
922 "GREENTIC_SERVICES_PUBLISH_PUBLIC_BASE_URL" => {
923 layer
924 .services
925 .get_or_insert_with(Default::default)
926 .publish
927 .get_or_insert_with(Default::default)
928 .service
929 .get_or_insert_with(Default::default)
930 .public_base_url = Some(value.to_string());
931 }
932 "GREENTIC_SERVICES_PUBLISH_METRICS_ENABLED" => {
933 layer
934 .services
935 .get_or_insert_with(Default::default)
936 .publish
937 .get_or_insert_with(Default::default)
938 .service
939 .get_or_insert_with(Default::default)
940 .metrics
941 .get_or_insert_with(Default::default)
942 .enabled = parse_bool(value);
943 }
944 "GREENTIC_SERVICES_PUBLISH_METRICS_BIND_ADDR" => {
945 layer
946 .services
947 .get_or_insert_with(Default::default)
948 .publish
949 .get_or_insert_with(Default::default)
950 .service
951 .get_or_insert_with(Default::default)
952 .metrics
953 .get_or_insert_with(Default::default)
954 .bind_addr = Some(value.to_string());
955 }
956 "GREENTIC_SERVICES_PUBLISH_METRICS_PORT" => {
957 layer
958 .services
959 .get_or_insert_with(Default::default)
960 .publish
961 .get_or_insert_with(Default::default)
962 .service
963 .get_or_insert_with(Default::default)
964 .metrics
965 .get_or_insert_with(Default::default)
966 .port = parse_u16_warn(value, key, warnings);
967 }
968 "GREENTIC_SERVICES_PUBLISH_METRICS_PATH" => {
969 layer
970 .services
971 .get_or_insert_with(Default::default)
972 .publish
973 .get_or_insert_with(Default::default)
974 .service
975 .get_or_insert_with(Default::default)
976 .metrics
977 .get_or_insert_with(Default::default)
978 .path = Some(value.to_string());
979 }
980 "GREENTIC_SERVICES_METADATA_KIND" => {
981 layer
982 .services
983 .get_or_insert_with(Default::default)
984 .metadata
985 .get_or_insert_with(Default::default)
986 .transport
987 .kind = Some(value.to_lowercase())
988 }
989 "GREENTIC_SERVICES_METADATA_URL" => {
990 layer
991 .services
992 .get_or_insert_with(Default::default)
993 .metadata
994 .get_or_insert_with(Default::default)
995 .transport
996 .url = parse_string_as::<url::Url>(value)
997 }
998 "GREENTIC_SERVICES_METADATA_BIND_ADDR" => {
999 layer
1000 .services
1001 .get_or_insert_with(Default::default)
1002 .metadata
1003 .get_or_insert_with(Default::default)
1004 .service
1005 .get_or_insert_with(Default::default)
1006 .bind_addr = Some(value.to_string());
1007 }
1008 "GREENTIC_SERVICES_METADATA_PORT" => {
1009 layer
1010 .services
1011 .get_or_insert_with(Default::default)
1012 .metadata
1013 .get_or_insert_with(Default::default)
1014 .service
1015 .get_or_insert_with(Default::default)
1016 .port = parse_u16_warn(value, key, warnings);
1017 }
1018 "GREENTIC_SERVICES_METADATA_PUBLIC_BASE_URL" => {
1019 layer
1020 .services
1021 .get_or_insert_with(Default::default)
1022 .metadata
1023 .get_or_insert_with(Default::default)
1024 .service
1025 .get_or_insert_with(Default::default)
1026 .public_base_url = Some(value.to_string());
1027 }
1028 "GREENTIC_SERVICES_METADATA_METRICS_ENABLED" => {
1029 layer
1030 .services
1031 .get_or_insert_with(Default::default)
1032 .metadata
1033 .get_or_insert_with(Default::default)
1034 .service
1035 .get_or_insert_with(Default::default)
1036 .metrics
1037 .get_or_insert_with(Default::default)
1038 .enabled = parse_bool(value);
1039 }
1040 "GREENTIC_SERVICES_METADATA_METRICS_BIND_ADDR" => {
1041 layer
1042 .services
1043 .get_or_insert_with(Default::default)
1044 .metadata
1045 .get_or_insert_with(Default::default)
1046 .service
1047 .get_or_insert_with(Default::default)
1048 .metrics
1049 .get_or_insert_with(Default::default)
1050 .bind_addr = Some(value.to_string());
1051 }
1052 "GREENTIC_SERVICES_METADATA_METRICS_PORT" => {
1053 layer
1054 .services
1055 .get_or_insert_with(Default::default)
1056 .metadata
1057 .get_or_insert_with(Default::default)
1058 .service
1059 .get_or_insert_with(Default::default)
1060 .metrics
1061 .get_or_insert_with(Default::default)
1062 .port = parse_u16_warn(value, key, warnings);
1063 }
1064 "GREENTIC_SERVICES_METADATA_METRICS_PATH" => {
1065 layer
1066 .services
1067 .get_or_insert_with(Default::default)
1068 .metadata
1069 .get_or_insert_with(Default::default)
1070 .service
1071 .get_or_insert_with(Default::default)
1072 .metrics
1073 .get_or_insert_with(Default::default)
1074 .path = Some(value.to_string());
1075 }
1076 "GREENTIC_SERVICES_OAUTH_BROKER_KIND" => {
1077 layer
1078 .services
1079 .get_or_insert_with(Default::default)
1080 .oauth_broker
1081 .get_or_insert_with(Default::default)
1082 .transport
1083 .kind = Some(value.to_lowercase())
1084 }
1085 "GREENTIC_SERVICES_OAUTH_BROKER_URL" => {
1086 layer
1087 .services
1088 .get_or_insert_with(Default::default)
1089 .oauth_broker
1090 .get_or_insert_with(Default::default)
1091 .transport
1092 .url = parse_string_as::<url::Url>(value)
1093 }
1094 "GREENTIC_SERVICES_OAUTH_BROKER_BIND_ADDR" => {
1095 layer
1096 .services
1097 .get_or_insert_with(Default::default)
1098 .oauth_broker
1099 .get_or_insert_with(Default::default)
1100 .service
1101 .get_or_insert_with(Default::default)
1102 .bind_addr = Some(value.to_string());
1103 }
1104 "GREENTIC_SERVICES_OAUTH_BROKER_PORT" => {
1105 layer
1106 .services
1107 .get_or_insert_with(Default::default)
1108 .oauth_broker
1109 .get_or_insert_with(Default::default)
1110 .service
1111 .get_or_insert_with(Default::default)
1112 .port = parse_u16_warn(value, key, warnings);
1113 }
1114 "GREENTIC_SERVICES_OAUTH_BROKER_PUBLIC_BASE_URL" => {
1115 layer
1116 .services
1117 .get_or_insert_with(Default::default)
1118 .oauth_broker
1119 .get_or_insert_with(Default::default)
1120 .service
1121 .get_or_insert_with(Default::default)
1122 .public_base_url = Some(value.to_string());
1123 }
1124 "GREENTIC_SERVICES_OAUTH_BROKER_METRICS_ENABLED" => {
1125 layer
1126 .services
1127 .get_or_insert_with(Default::default)
1128 .oauth_broker
1129 .get_or_insert_with(Default::default)
1130 .service
1131 .get_or_insert_with(Default::default)
1132 .metrics
1133 .get_or_insert_with(Default::default)
1134 .enabled = parse_bool(value);
1135 }
1136 "GREENTIC_SERVICES_OAUTH_BROKER_METRICS_BIND_ADDR" => {
1137 layer
1138 .services
1139 .get_or_insert_with(Default::default)
1140 .oauth_broker
1141 .get_or_insert_with(Default::default)
1142 .service
1143 .get_or_insert_with(Default::default)
1144 .metrics
1145 .get_or_insert_with(Default::default)
1146 .bind_addr = Some(value.to_string());
1147 }
1148 "GREENTIC_SERVICES_OAUTH_BROKER_METRICS_PORT" => {
1149 layer
1150 .services
1151 .get_or_insert_with(Default::default)
1152 .oauth_broker
1153 .get_or_insert_with(Default::default)
1154 .service
1155 .get_or_insert_with(Default::default)
1156 .metrics
1157 .get_or_insert_with(Default::default)
1158 .port = parse_u16_warn(value, key, warnings);
1159 }
1160 "GREENTIC_SERVICES_OAUTH_BROKER_METRICS_PATH" => {
1161 layer
1162 .services
1163 .get_or_insert_with(Default::default)
1164 .oauth_broker
1165 .get_or_insert_with(Default::default)
1166 .service
1167 .get_or_insert_with(Default::default)
1168 .metrics
1169 .get_or_insert_with(Default::default)
1170 .path = Some(value.to_string());
1171 }
1172 "GREENTIC_RUNTIME_MAX_CONCURRENCY" => {
1173 layer
1174 .runtime
1175 .get_or_insert_with(Default::default)
1176 .max_concurrency = parse_u32(value)
1177 }
1178 "GREENTIC_RUNTIME_TASK_TIMEOUT_MS" => {
1179 layer
1180 .runtime
1181 .get_or_insert_with(Default::default)
1182 .task_timeout_ms = parse_u64(value)
1183 }
1184 "GREENTIC_RUNTIME_SHUTDOWN_GRACE_MS" => {
1185 layer
1186 .runtime
1187 .get_or_insert_with(Default::default)
1188 .shutdown_grace_ms = parse_u64(value)
1189 }
1190 "GREENTIC_RUNTIME_ADMIN_SECRETS_EXPLAIN_ENABLED" => {
1191 layer
1192 .runtime
1193 .get_or_insert_with(Default::default)
1194 .admin_endpoints
1195 .get_or_insert_with(Default::default)
1196 .secrets_explain_enabled = parse_bool(value)
1197 }
1198 "GREENTIC_TELEMETRY_ENABLED" => {
1199 layer.telemetry.get_or_insert_with(Default::default).enabled = parse_bool(value)
1200 }
1201 "GREENTIC_TELEMETRY_EXPORTER" => {
1202 layer
1203 .telemetry
1204 .get_or_insert_with(Default::default)
1205 .exporter = Some(value.to_lowercase())
1206 }
1207 "GREENTIC_TELEMETRY_ENDPOINT" => {
1208 layer
1209 .telemetry
1210 .get_or_insert_with(Default::default)
1211 .endpoint = Some(value.to_string())
1212 }
1213 "GREENTIC_TELEMETRY_SAMPLING" => {
1214 layer
1215 .telemetry
1216 .get_or_insert_with(Default::default)
1217 .sampling = parse_f32(value)
1218 }
1219 "GREENTIC_NETWORK_PROXY_URL" => {
1220 layer.network.get_or_insert_with(Default::default).proxy_url = Some(value.to_string())
1221 }
1222 "GREENTIC_NETWORK_TLS_MODE" => {
1223 layer.network.get_or_insert_with(Default::default).tls_mode = Some(value.to_lowercase())
1224 }
1225 "GREENTIC_NETWORK_CONNECT_TIMEOUT_MS" => {
1226 layer
1227 .network
1228 .get_or_insert_with(Default::default)
1229 .connect_timeout_ms = parse_u64(value)
1230 }
1231 "GREENTIC_NETWORK_READ_TIMEOUT_MS" => {
1232 layer
1233 .network
1234 .get_or_insert_with(Default::default)
1235 .read_timeout_ms = parse_u64(value)
1236 }
1237 "GREENTIC_SECRETS_KIND" => {
1238 layer.secrets.get_or_insert_with(Default::default).kind = Some(value.to_string())
1239 }
1240 "GREENTIC_SECRETS_REFERENCE" => {
1241 layer.secrets.get_or_insert_with(Default::default).reference = Some(value.to_string())
1242 }
1243 "GREENTIC_DEV_DEFAULT_ENV" => {
1244 layer.dev.get_or_insert_with(Default::default).default_env =
1245 parse_string_as::<EnvId>(value)
1246 }
1247 "GREENTIC_DEV_DEFAULT_TENANT" => {
1248 layer
1249 .dev
1250 .get_or_insert_with(Default::default)
1251 .default_tenant = Some(value.to_string())
1252 }
1253 "GREENTIC_DEV_DEFAULT_TEAM" => {
1254 layer.dev.get_or_insert_with(Default::default).default_team = Some(value.to_string())
1255 }
1256 "GREENTIC_EVENTS_RECONNECT_ENABLED" => {
1257 layer
1258 .events
1259 .get_or_insert_with(Default::default)
1260 .reconnect
1261 .get_or_insert_with(Default::default)
1262 .enabled = parse_bool(value)
1263 }
1264 "GREENTIC_EVENTS_RECONNECT_MAX_RETRIES" => {
1265 layer
1266 .events
1267 .get_or_insert_with(Default::default)
1268 .reconnect
1269 .get_or_insert_with(Default::default)
1270 .max_retries = parse_u32(value)
1271 }
1272 "GREENTIC_EVENTS_BACKOFF_INITIAL_MS" => {
1273 layer
1274 .events
1275 .get_or_insert_with(Default::default)
1276 .backoff
1277 .get_or_insert_with(Default::default)
1278 .initial_ms = parse_u64(value)
1279 }
1280 "GREENTIC_EVENTS_BACKOFF_MAX_MS" => {
1281 layer
1282 .events
1283 .get_or_insert_with(Default::default)
1284 .backoff
1285 .get_or_insert_with(Default::default)
1286 .max_ms = parse_u64(value)
1287 }
1288 "GREENTIC_EVENTS_BACKOFF_MULTIPLIER" => {
1289 layer
1290 .events
1291 .get_or_insert_with(Default::default)
1292 .backoff
1293 .get_or_insert_with(Default::default)
1294 .multiplier = parse_f32(value).map(|v| v as f64)
1295 }
1296 "GREENTIC_EVENTS_BACKOFF_JITTER" => {
1297 layer
1298 .events
1299 .get_or_insert_with(Default::default)
1300 .backoff
1301 .get_or_insert_with(Default::default)
1302 .jitter = parse_bool(value)
1303 }
1304 _ => return false,
1305 }
1306 true
1307}
1308
1309fn parse_string_as<T: for<'de> Deserialize<'de>>(value: &str) -> Option<T> {
1310 serde_json::from_str(&format!("\"{value}\"")).ok()
1311}
1312
1313fn parse_bool(value: &str) -> Option<bool> {
1314 match value.to_ascii_lowercase().as_str() {
1315 "1" | "true" | "yes" | "on" => Some(true),
1316 "0" | "false" | "no" | "off" => Some(false),
1317 _ => None,
1318 }
1319}
1320
1321fn parse_u64(value: &str) -> Option<u64> {
1322 value.parse::<u64>().ok()
1323}
1324
1325fn parse_u32(value: &str) -> Option<u32> {
1326 value.parse::<u32>().ok()
1327}
1328
1329fn parse_f32(value: &str) -> Option<f32> {
1330 value.parse::<f32>().ok()
1331}
1332
1333fn parse_u16_warn(value: &str, key: &str, warnings: &mut Vec<String>) -> Option<u16> {
1334 match value.parse::<u16>() {
1335 Ok(v) => Some(v),
1336 Err(_) => {
1337 warnings.push(format!("Ignored {key}: expected u16 but got '{value}'"));
1338 None
1339 }
1340 }
1341}
1342
1343pub(crate) fn default_env_id() -> EnvId {
1344 serde_json::from_str("\"dev\"").expect("EnvId should deserialize from string")
1345}