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