Skip to main content

greentic_setup/cli_commands/
setup.rs

1//! Setup and update commands for bundle configuration.
2
3use anyhow::{Context, Result, bail};
4
5use crate::cli_args::*;
6use crate::cli_helpers::{
7    complete_loaded_answers_with_prompts, ensure_deployment_targets_present, resolve_bundle_dir,
8    run_interactive_wizard,
9};
10use crate::cli_i18n::CliI18n;
11use crate::engine::{LoadedAnswers, SetupConfig, SetupRequest};
12use crate::plan::TenantSelection;
13use crate::platform_setup::StaticRoutesPolicy;
14use crate::{SetupEngine, SetupMode, bundle};
15
16/// Run the setup command.
17pub fn setup(args: BundleSetupArgs, i18n: &CliI18n) -> Result<()> {
18    setup_or_update(args, SetupMode::Create, i18n)
19}
20
21/// Run the update command.
22pub fn update(args: BundleSetupArgs, i18n: &CliI18n) -> Result<()> {
23    setup_or_update(args, SetupMode::Update, i18n)
24}
25
26/// Shared implementation for setup and update commands.
27fn setup_or_update(args: BundleSetupArgs, mode: SetupMode, i18n: &CliI18n) -> Result<()> {
28    let bundle_dir = resolve_bundle_dir(args.bundle)?;
29
30    bundle::validate_bundle_exists(&bundle_dir).context(i18n.t("cli.error.invalid_bundle"))?;
31
32    let provider_display = args
33        .provider_id
34        .clone()
35        .unwrap_or_else(|| "all".to_string());
36
37    let header_key = match mode {
38        SetupMode::Update => "cli.bundle.update.updating",
39        _ => "cli.bundle.setup.setting_up",
40    };
41    println!("{}", i18n.t(header_key));
42    println!(
43        "{}",
44        i18n.tf("cli.bundle.setup.provider", &[&provider_display])
45    );
46    println!(
47        "{}",
48        i18n.tf(
49            "cli.bundle.add.bundle",
50            &[&bundle_dir.display().to_string()]
51        )
52    );
53    println!("{}", i18n.tf("cli.bundle.add.tenant", &[&args.tenant]));
54    println!(
55        "{}",
56        i18n.tf(
57            "cli.bundle.add.team",
58            &[args.team.as_deref().unwrap_or("default")]
59        )
60    );
61    println!("{}", i18n.tf("cli.bundle.add.env", &[&args.env]));
62    println!("{}", i18n.tf("cli.bundle.setup.domain", &[&args.domain]));
63
64    let config = SetupConfig {
65        tenant: args.tenant.clone(),
66        team: args.team.clone(),
67        env: args.env.clone(),
68        offline: false,
69        verbose: true,
70    };
71    let engine = SetupEngine::new(config);
72
73    let loaded_answers = if let Some(answers_path) = &args.answers {
74        engine
75            .load_answers(answers_path, args.key.as_deref(), !args.non_interactive)
76            .context(i18n.t("cli.error.failed_read_answers"))?
77    } else if args.emit_answers.is_some() {
78        LoadedAnswers::default()
79    } else if args.non_interactive {
80        bail!("{}", i18n.t("cli.error.answers_required"));
81    } else {
82        println!("\n{}", i18n.t("cli.simple.interactive_mode"));
83        println!();
84        run_interactive_wizard(
85            &bundle_dir,
86            &args.tenant,
87            args.team.as_deref(),
88            &args.env,
89            args.advanced,
90        )?
91    };
92    let loaded_answers = if args.answers.is_some() && !args.non_interactive {
93        complete_loaded_answers_with_prompts(
94            &bundle_dir,
95            &args.tenant,
96            args.team.as_deref(),
97            &args.env,
98            args.advanced,
99            loaded_answers,
100        )?
101    } else {
102        loaded_answers
103    };
104    if args.non_interactive {
105        ensure_deployment_targets_present(&bundle_dir, &loaded_answers)?;
106    }
107
108    let providers = args
109        .provider_id
110        .clone()
111        .map_or_else(Vec::new, |id| vec![id]);
112
113    let request = SetupRequest {
114        bundle: bundle_dir.clone(),
115        providers,
116        tenants: vec![TenantSelection {
117            tenant: args.tenant,
118            team: args.team,
119            allow_paths: Vec::new(),
120        }],
121        static_routes: StaticRoutesPolicy::normalize(
122            loaded_answers.platform_setup.static_routes.as_ref(),
123            &args.env,
124        )
125        .context(i18n.t("cli.error.failed_read_answers"))?,
126        deployment_targets: loaded_answers.platform_setup.deployment_targets,
127        setup_answers: loaded_answers.setup_answers,
128        domain_filter: if args.domain == "all" {
129            None
130        } else {
131            Some(args.domain.clone())
132        },
133        parallel: args.parallel,
134        backup: args.backup,
135        skip_secrets_init: args.skip_secrets_init,
136        best_effort: args.best_effort,
137        ..Default::default()
138    };
139
140    let plan = engine
141        .plan(mode, &request, args.dry_run || args.emit_answers.is_some())
142        .context(i18n.t("cli.error.failed_build_plan"))?;
143
144    engine.print_plan(&plan);
145
146    if let Some(emit_path) = &args.emit_answers {
147        let emit_path_str = emit_path.display().to_string();
148        engine
149            .emit_answers(&plan, emit_path, args.key.as_deref(), !args.non_interactive)
150            .context(i18n.t("cli.error.failed_emit_answers"))?;
151        println!(
152            "\n{}",
153            i18n.tf("cli.bundle.setup.emit_written", &[&emit_path_str])
154        );
155        let usage_key = match mode {
156            SetupMode::Update => "cli.bundle.update.emit_usage",
157            _ => "cli.bundle.setup.emit_usage",
158        };
159        println!("{}", i18n.tf(usage_key, &[&emit_path_str]));
160        return Ok(());
161    }
162
163    if args.dry_run {
164        let dry_key = match mode {
165            SetupMode::Update => "cli.bundle.update.dry_run",
166            _ => "cli.bundle.setup.dry_run",
167        };
168        println!("\n{}", i18n.tf(dry_key, &[&provider_display]));
169        return Ok(());
170    }
171
172    engine
173        .execute(&plan)
174        .context(i18n.t("cli.error.failed_execute_plan"))?;
175
176    let done_key = match mode {
177        SetupMode::Update => "cli.bundle.update.complete",
178        _ => "cli.bundle.setup.complete",
179    };
180    println!("\n{}", i18n.tf(done_key, &[&provider_display]));
181
182    Ok(())
183}