Skip to main content

greentic_setup/
lib.rs

1//! End-to-end bundle setup engine for the Greentic platform.
2//!
3//! Provides pack discovery, QA-driven configuration, secrets persistence,
4//! and bundle lifecycle management as a library crate.
5
6pub mod admin;
7pub mod bundle;
8pub mod bundle_source;
9pub mod capabilities;
10pub mod card_setup;
11pub mod cli_args;
12pub mod cli_commands;
13pub mod cli_helpers;
14pub mod cli_i18n;
15pub mod config_envelope;
16pub mod discovery;
17pub mod engine;
18pub mod flow;
19pub mod gtbundle;
20pub mod plan;
21pub mod platform_setup;
22pub mod reload;
23pub mod secret_name;
24pub mod secrets;
25pub mod setup_input;
26pub mod setup_to_formspec;
27pub mod webhook;
28
29pub mod qa {
30    //! QA-driven configuration: FormSpec bridge, wizard prompts, answers
31    //! persistence, and setup input loading.
32    pub mod bridge;
33    pub mod persist;
34    pub mod wizard;
35}
36
37pub use bundle_source::BundleSource;
38pub use engine::SetupEngine;
39pub use plan::{SetupMode, SetupPlan, SetupStep, SetupStepKind};
40
41/// Returns the crate version.
42pub fn version() -> &'static str {
43    env!("CARGO_PKG_VERSION")
44}
45
46/// Resolve the effective environment string.
47///
48/// Priority: explicit override > `$GREENTIC_ENV` > `"dev"`.
49pub fn resolve_env(override_env: Option<&str>) -> String {
50    override_env
51        .map(|v| v.to_string())
52        .or_else(|| std::env::var("GREENTIC_ENV").ok())
53        .unwrap_or_else(|| "dev".to_string())
54}
55
56/// Build a canonical secret URI: `secrets://{env}/{tenant}/{team}/{provider}/{key}`.
57pub fn canonical_secret_uri(
58    env: &str,
59    tenant: &str,
60    team: Option<&str>,
61    provider: &str,
62    key: &str,
63) -> String {
64    let team_segment = canonical_team(team);
65    let provider_segment = if provider.is_empty() {
66        "messaging".to_string()
67    } else {
68        provider.to_string()
69    };
70    let normalized_key = secret_name::canonical_secret_name(key);
71    format!("secrets://{env}/{tenant}/{team_segment}/{provider_segment}/{normalized_key}")
72}
73
74/// Normalize the team segment for secret URIs.
75///
76/// Empty, `"default"`, or `None` → `"_"` (wildcard).
77fn canonical_team(team: Option<&str>) -> &str {
78    match team
79        .map(|v| v.trim())
80        .filter(|t| !t.is_empty() && !t.eq_ignore_ascii_case("default"))
81    {
82        Some(v) => v,
83        None => "_",
84    }
85}
86
87#[cfg(test)]
88mod tests {
89    use super::*;
90
91    #[test]
92    fn version_is_correct() {
93        assert!(version().starts_with("0.4"));
94    }
95
96    #[test]
97    fn secret_uri_basic() {
98        let uri = canonical_secret_uri("dev", "demo", None, "messaging-telegram", "bot_token");
99        assert_eq!(uri, "secrets://dev/demo/_/messaging-telegram/bot_token");
100    }
101
102    #[test]
103    fn secret_uri_with_team() {
104        let uri = canonical_secret_uri("dev", "acme", Some("ops"), "state-redis", "redis_url");
105        assert_eq!(uri, "secrets://dev/acme/ops/state-redis/redis_url");
106    }
107
108    #[test]
109    fn secret_uri_default_team_becomes_wildcard() {
110        let uri = canonical_secret_uri(
111            "dev",
112            "demo",
113            Some("default"),
114            "messaging-slack",
115            "bot_token",
116        );
117        assert_eq!(uri, "secrets://dev/demo/_/messaging-slack/bot_token");
118    }
119}