Skip to main content

greentic_setup/cli_commands/
lifecycle.rs

1//! Bundle lifecycle commands: init, add, remove.
2
3use anyhow::{Context, Result, bail};
4
5use crate::cli_args::*;
6use crate::cli_helpers::resolve_bundle_dir;
7use crate::cli_i18n::CliI18n;
8use crate::engine::{SetupConfig, SetupRequest};
9use crate::plan::TenantSelection;
10use crate::{SetupEngine, SetupMode, bundle};
11
12/// Initialize a new bundle directory.
13pub fn init(args: BundleInitArgs, i18n: &CliI18n) -> Result<()> {
14    let bundle_dir = args.path.unwrap_or_else(|| std::path::PathBuf::from("."));
15    let bundle_path = bundle_dir.display().to_string();
16
17    if bundle::is_bundle_root(&bundle_dir) {
18        println!("{}", i18n.tf("cli.bundle.init.exists", &[&bundle_path]));
19        return Ok(());
20    }
21
22    println!("{}", i18n.tf("cli.bundle.init.creating", &[&bundle_path]));
23
24    bundle::create_demo_bundle_structure(&bundle_dir, args.name.as_deref())
25        .context(i18n.t("cli.error.failed_create_bundle"))?;
26
27    println!("{}", i18n.tf("cli.bundle.init.created", &[&bundle_path]));
28    println!("\n{}", i18n.t("cli.bundle.init.next_steps"));
29    println!("{}", i18n.tf("cli.bundle.init.step_add", &[&bundle_path]));
30    println!("{}", i18n.tf("cli.bundle.init.step_setup", &[&bundle_path]));
31
32    Ok(())
33}
34
35/// Add a pack to a bundle.
36pub fn add(args: BundleAddArgs, i18n: &CliI18n) -> Result<()> {
37    let bundle_dir = resolve_bundle_dir(args.bundle)?;
38    let bundle_path = bundle_dir.display().to_string();
39
40    println!("{}", i18n.t("cli.bundle.add.adding"));
41    println!("{}", i18n.tf("cli.bundle.add.pack_ref", &[&args.pack_ref]));
42    println!("{}", i18n.tf("cli.bundle.add.bundle", &[&bundle_path]));
43    println!("{}", i18n.tf("cli.bundle.add.tenant", &[&args.tenant]));
44    println!(
45        "{}",
46        i18n.tf(
47            "cli.bundle.add.team",
48            &[args.team.as_deref().unwrap_or("default")]
49        )
50    );
51    println!("{}", i18n.tf("cli.bundle.add.env", &[&args.env]));
52
53    if !bundle::is_bundle_root(&bundle_dir) {
54        bundle::create_demo_bundle_structure(&bundle_dir, None)
55            .context(i18n.t("cli.error.failed_create_bundle"))?;
56        println!(
57            "{}",
58            i18n.tf("cli.bundle.add.created_structure", &[&bundle_path])
59        );
60    }
61
62    let request = SetupRequest {
63        bundle: bundle_dir.clone(),
64        pack_refs: vec![args.pack_ref.clone()],
65        tenants: vec![TenantSelection {
66            tenant: args.tenant.clone(),
67            team: args.team.clone(),
68            allow_paths: Vec::new(),
69        }],
70        ..Default::default()
71    };
72
73    let config = SetupConfig {
74        tenant: args.tenant,
75        team: args.team,
76        env: args.env,
77        offline: false,
78        verbose: true,
79    };
80    let engine = SetupEngine::new(config);
81    let plan = engine
82        .plan(SetupMode::Create, &request, args.dry_run)
83        .context(i18n.t("cli.error.failed_build_plan"))?;
84
85    engine.print_plan(&plan);
86
87    if args.dry_run {
88        println!("\n{}", i18n.t("cli.bundle.add.dry_run"));
89        return Ok(());
90    }
91
92    let report = engine
93        .execute(&plan)
94        .context(i18n.t("cli.error.failed_execute_plan"))?;
95
96    println!("\n{}", i18n.t("cli.bundle.add.success"));
97    println!(
98        "{}",
99        i18n.tf(
100            "cli.bundle.add.resolved",
101            &[&report.resolved_packs.len().to_string()]
102        )
103    );
104
105    Ok(())
106}
107
108/// Remove a provider from a bundle.
109pub fn remove(args: BundleRemoveArgs, i18n: &CliI18n) -> Result<()> {
110    let bundle_dir = resolve_bundle_dir(args.bundle)?;
111
112    bundle::validate_bundle_exists(&bundle_dir).context(i18n.t("cli.error.invalid_bundle"))?;
113
114    println!("{}", i18n.t("cli.bundle.remove.removing"));
115    println!(
116        "{}",
117        i18n.tf("cli.bundle.setup.provider", &[&args.provider_id])
118    );
119    println!(
120        "{}",
121        i18n.tf(
122            "cli.bundle.add.bundle",
123            &[&bundle_dir.display().to_string()]
124        )
125    );
126
127    if !args.force {
128        println!("\n{}", i18n.t("cli.bundle.remove.confirm"));
129        println!("{}", i18n.t("cli.bundle.remove.use_force"));
130        bail!("{}", i18n.t("cli.bundle.remove.cancelled"));
131    }
132
133    let request = SetupRequest {
134        bundle: bundle_dir.clone(),
135        providers_remove: vec![args.provider_id.clone()],
136        tenants: vec![TenantSelection {
137            tenant: args.tenant.clone(),
138            team: args.team.clone(),
139            allow_paths: Vec::new(),
140        }],
141        ..Default::default()
142    };
143
144    let config = SetupConfig {
145        tenant: args.tenant,
146        team: args.team,
147        env: "dev".to_string(),
148        offline: false,
149        verbose: true,
150    };
151    let engine = SetupEngine::new(config);
152    let plan = engine
153        .plan(SetupMode::Remove, &request, false)
154        .context(i18n.t("cli.error.failed_build_plan"))?;
155
156    engine.print_plan(&plan);
157    engine
158        .execute(&plan)
159        .context(i18n.t("cli.error.failed_execute_plan"))?;
160
161    println!(
162        "\n{}",
163        i18n.tf("cli.bundle.remove.complete", &[&args.provider_id])
164    );
165
166    Ok(())
167}