1#![forbid(unsafe_code)]
2
3use std::{convert::TryFrom, path::PathBuf};
4
5use anyhow::Result;
6use clap::{Parser, Subcommand};
7use greentic_types::{EnvId, TenantCtx, TenantId};
8use tokio::runtime::Runtime;
9
10pub mod add_extension;
11pub mod components;
12pub mod config;
13pub mod gui;
14pub mod input;
15pub mod inspect;
16pub mod lint;
17pub mod plan;
18pub mod providers;
19pub mod resolve;
20pub mod sign;
21pub mod update;
22pub mod verify;
23
24use crate::telemetry::set_current_tenant_ctx;
25use crate::{build, new, runtime};
26
27#[derive(Debug, Parser)]
28#[command(name = "greentic-pack", about = "Greentic pack CLI", version)]
29pub struct Cli {
30 #[arg(long = "log", default_value = "info", global = true)]
32 pub verbosity: String,
33
34 #[arg(long, global = true)]
36 pub offline: bool,
37
38 #[arg(long = "cache-dir", global = true)]
40 pub cache_dir: Option<PathBuf>,
41
42 #[arg(long = "config-override", value_name = "FILE", global = true)]
44 pub config_override: Option<PathBuf>,
45
46 #[arg(long, global = true)]
48 pub json: bool,
49
50 #[command(subcommand)]
51 pub command: Command,
52}
53
54#[allow(clippy::large_enum_variant)]
55#[derive(Debug, Subcommand)]
56pub enum Command {
57 Build(BuildArgs),
59 Lint(self::lint::LintArgs),
61 Components(self::components::ComponentsArgs),
63 Update(self::update::UpdateArgs),
65 New(new::NewArgs),
67 Sign(self::sign::SignArgs),
69 Verify(self::verify::VerifyArgs),
71 #[command(subcommand)]
73 Gui(self::gui::GuiCommand),
74 Doctor(self::inspect::InspectArgs),
76 Inspect(self::inspect::InspectArgs),
78 Config(self::config::ConfigArgs),
80 Plan(self::plan::PlanArgs),
82 #[command(subcommand)]
84 Providers(self::providers::ProvidersCommand),
85 #[command(subcommand)]
87 AddExtension(self::add_extension::AddExtensionCommand),
88 Resolve(self::resolve::ResolveArgs),
90}
91
92#[derive(Debug, Clone, Parser)]
93pub struct BuildArgs {
94 #[arg(long = "in", value_name = "DIR")]
96 pub input: PathBuf,
97
98 #[arg(long = "no-update", default_value_t = false)]
100 pub no_update: bool,
101
102 #[arg(long = "out", value_name = "FILE")]
104 pub component_out: Option<PathBuf>,
105
106 #[arg(long, value_name = "FILE")]
108 pub manifest: Option<PathBuf>,
109
110 #[arg(long, value_name = "FILE")]
112 pub sbom: Option<PathBuf>,
113
114 #[arg(long = "gtpack-out", value_name = "FILE")]
116 pub gtpack_out: Option<PathBuf>,
117
118 #[arg(long = "lock", value_name = "FILE")]
120 pub lock: Option<PathBuf>,
121
122 #[arg(long = "bundle", value_enum, default_value = "cache")]
124 pub bundle: crate::build::BundleMode,
125
126 #[arg(long)]
128 pub dry_run: bool,
129
130 #[arg(long = "secrets-req", value_name = "FILE")]
132 pub secrets_req: Option<PathBuf>,
133
134 #[arg(long = "default-secret-scope", value_name = "ENV/TENANT[/TEAM]")]
136 pub default_secret_scope: Option<String>,
137
138 #[arg(long = "allow-oci-tags", default_value_t = false)]
140 pub allow_oci_tags: bool,
141
142 #[arg(long, default_value_t = false)]
144 pub require_component_manifests: bool,
145
146 #[arg(long = "no-extra-dirs", default_value_t = false)]
148 pub no_extra_dirs: bool,
149}
150
151pub fn run() -> Result<()> {
152 Runtime::new()?.block_on(run_with_cli(Cli::parse(), false))
153}
154
155pub fn resolve_env_filter(cli: &Cli) -> String {
157 std::env::var("PACKC_LOG").unwrap_or_else(|_| cli.verbosity.clone())
158}
159
160pub async fn run_with_cli(cli: Cli, warn_inspect_alias: bool) -> Result<()> {
162 let runtime = runtime::resolve_runtime(
163 Some(std::env::current_dir()?.as_path()),
164 cli.cache_dir.as_deref(),
165 cli.offline,
166 cli.config_override.as_deref(),
167 )?;
168
169 crate::telemetry::install_with_config("packc", &runtime.resolved.config.telemetry)?;
171
172 set_current_tenant_ctx(&TenantCtx::new(
173 EnvId::try_from("local").expect("static env id"),
174 TenantId::try_from("packc").expect("static tenant id"),
175 ));
176
177 match cli.command {
178 Command::Build(args) => {
179 build::run(&build::BuildOptions::from_args(args, &runtime)?).await?
180 }
181 Command::Lint(args) => self::lint::handle(args, cli.json)?,
182 Command::Components(args) => self::components::handle(args, cli.json)?,
183 Command::Update(args) => self::update::handle(args, cli.json)?,
184 Command::New(args) => new::handle(args, cli.json, &runtime).await?,
185 Command::Sign(args) => self::sign::handle(args, cli.json)?,
186 Command::Verify(args) => self::verify::handle(args, cli.json)?,
187 Command::Gui(cmd) => self::gui::handle(cmd, cli.json, &runtime).await?,
188 Command::Inspect(args) | Command::Doctor(args) => {
189 if warn_inspect_alias {
190 eprintln!("WARNING: `inspect` is deprecated; use `doctor`.");
191 }
192 self::inspect::handle(args, cli.json, &runtime).await?
193 }
194 Command::Config(args) => self::config::handle(args, cli.json, &runtime)?,
195 Command::Plan(args) => self::plan::handle(&args)?,
196 Command::Providers(cmd) => self::providers::run(cmd)?,
197 Command::AddExtension(cmd) => self::add_extension::handle(cmd)?,
198 Command::Resolve(args) => self::resolve::handle(args, &runtime, true).await?,
199 }
200
201 Ok(())
202}