1mod apps;
2mod auth;
3mod backup;
4mod blob_storage;
5mod build;
6mod cli;
7mod cycles;
8mod deploy;
9mod diagnostic;
10mod endpoints;
11mod evidence;
12mod evidence_support;
13mod info;
14mod info_env;
15mod info_subnets;
16mod inspect;
17mod install;
18mod list;
19mod medic;
20mod metrics;
21mod network;
22mod output;
23mod replica;
24mod restore;
25mod scaffold;
26mod state;
27mod status;
28mod support;
29#[cfg(test)]
30mod test_support;
31mod token;
32
33use crate::cli::{
34 argv::trace_if_enabled,
35 clap::parse_matches,
36 globals::{DISPATCH_ARGS, apply_global_environment, apply_global_icp, misplaced_global_option},
37};
38use clap::error::ErrorKind;
39pub use cli::top_level_command;
40use std::ffi::OsString;
41use thiserror::Error as ThisError;
42
43const VERSION_TEXT: &str = concat!("canic ", env!("CARGO_PKG_VERSION"));
44
45#[derive(Debug, ThisError)]
50pub enum CliError {
51 #[error("backup: {0}")]
52 Backup(#[source] Box<backup::BackupCommandError>),
53
54 #[error("auth: {0}")]
55 Auth(#[source] Box<auth::AuthCommandError>),
56
57 #[error("blob-storage: {0}")]
58 BlobStorage(#[source] Box<blob_storage::BlobStorageCommandError>),
59
60 #[error(transparent)]
61 Clap(#[from] clap::Error),
62
63 #[error("build: {0}")]
64 Build(#[from] build::BuildCommandError),
65
66 #[error("cycles: {0}")]
67 Cycles(#[source] Box<cycles::CyclesCommandError>),
68
69 #[error("deploy: {0}")]
70 Deploy(#[from] deploy::DeployCommandError),
71
72 #[error("diagnostic: {0}")]
73 Diagnostic(#[from] diagnostic::DiagnosticCommandError),
74
75 #[error("evidence: {0}")]
76 Evidence(#[from] evidence::EvidenceCommandError),
77
78 #[error("install: {0}")]
79 Install(#[from] install::InstallCommandError),
80
81 #[error("inspect: {0}")]
82 Inspect(#[source] Box<inspect::InspectCommandError>),
83
84 #[error("info: {0}")]
85 Info(#[from] info::InfoCommandError),
86
87 #[error("medic: {0}")]
88 Medic(#[from] medic::MedicCommandError),
89
90 #[error("network: {0}")]
91 Network(#[from] network::NetworkCommandError),
92
93 #[error("app: {0}")]
94 Apps(#[source] Box<apps::AppCommandError>),
95
96 #[error("state: {0}")]
97 State(#[from] state::StateCommandError),
98
99 #[error("scaffold: {0}")]
100 Scaffold(#[from] scaffold::ScaffoldCommandError),
101
102 #[error("restore: {0}")]
103 Restore(#[from] restore::RestoreCommandError),
104
105 #[error("replica: {0}")]
106 Replica(#[from] replica::ReplicaCommandError),
107
108 #[error("status: {0}")]
109 Status(#[from] status::StatusCommandError),
110
111 #[error("token: {0}")]
112 Token(#[source] Box<token::TokenCommandError>),
113}
114
115impl From<backup::BackupCommandError> for CliError {
116 fn from(error: backup::BackupCommandError) -> Self {
117 Self::Backup(Box::new(error))
118 }
119}
120
121impl From<auth::AuthCommandError> for CliError {
122 fn from(error: auth::AuthCommandError) -> Self {
123 Self::Auth(Box::new(error))
124 }
125}
126
127impl From<blob_storage::BlobStorageCommandError> for CliError {
128 fn from(error: blob_storage::BlobStorageCommandError) -> Self {
129 Self::BlobStorage(Box::new(error))
130 }
131}
132
133impl From<cycles::CyclesCommandError> for CliError {
134 fn from(error: cycles::CyclesCommandError) -> Self {
135 Self::Cycles(Box::new(error))
136 }
137}
138
139impl From<inspect::InspectCommandError> for CliError {
140 fn from(error: inspect::InspectCommandError) -> Self {
141 Self::Inspect(Box::new(error))
142 }
143}
144
145impl From<apps::AppCommandError> for CliError {
146 fn from(error: apps::AppCommandError) -> Self {
147 Self::Apps(Box::new(error))
148 }
149}
150
151impl From<token::TokenCommandError> for CliError {
152 fn from(error: token::TokenCommandError) -> Self {
153 Self::Token(Box::new(error))
154 }
155}
156
157pub fn run_from_env() -> Result<(), CliError> {
159 let argv = std::env::args_os().collect::<Vec<_>>();
160 trace_if_enabled(&argv);
161 run(argv.into_iter().skip(1))
162}
163
164pub fn run<I>(args: I) -> Result<(), CliError>
166where
167 I: IntoIterator<Item = OsString>,
168{
169 let args = args.into_iter().collect::<Vec<_>>();
170 if let Some(option) = misplaced_global_option(&args) {
171 let error = top_level_command().error(
172 ErrorKind::UnknownArgument,
173 format!("unexpected argument '{option}' found; put it before the command"),
174 );
175 return Err(CliError::Clap(error));
176 }
177 let matches = match parse_matches(top_level_command(), args) {
178 Ok(matches) => matches,
179 Err(error) if !error.use_stderr() => {
180 let _ = error.print();
181 return Ok(());
182 }
183 Err(error) => return Err(CliError::Clap(error)),
184 };
185 let global_icp = cli::clap::string_option(&matches, "icp");
186 let global_environment = cli::clap::string_option(&matches, "environment");
187
188 let (command, subcommand_matches) = matches
189 .subcommand()
190 .unwrap_or_else(|| unreachable!("Clap requires one top-level command"));
191 let mut tail = subcommand_matches
192 .get_many::<OsString>(DISPATCH_ARGS)
193 .map(|values| values.cloned().collect::<Vec<_>>())
194 .unwrap_or_default();
195 apply_global_icp(command, &mut tail, global_icp);
196 apply_global_environment(command, &mut tail, global_environment);
197 let tail = tail.into_iter();
198
199 match command {
200 "app" => apps::run(tail).map_err(CliError::from),
201 "auth" => auth::run(tail).map_err(CliError::from),
202 "backup" => backup::run(tail).map_err(CliError::from),
203 "blob-storage" => blob_storage::run(tail).map_err(CliError::from),
204 "build" => build::run(tail).map_err(CliError::from),
205 "cycles" => cycles::run(tail).map_err(CliError::from),
206 "deploy" => deploy::run(tail).map_err(CliError::from),
207 "diagnostic" => diagnostic::run(tail).map_err(CliError::from),
208 "evidence" => evidence::run(tail).map_err(CliError::from),
209 "info" => info::run(tail).map_err(CliError::from),
210 "inspect" => inspect::run(tail).map_err(CliError::from),
211 "install" => install::run(tail).map_err(CliError::from),
212 "medic" => medic::run(tail).map_err(CliError::from),
213 "network" => network::run(tail).map_err(CliError::from),
214 "replica" => replica::run(tail).map_err(CliError::from),
215 "restore" => restore::run(tail).map_err(CliError::from),
216 "scaffold" => scaffold::run(tail).map_err(CliError::from),
217 "state" => state::run(tail).map_err(CliError::from),
218 "status" => status::run(tail).map_err(CliError::from),
219 "token" => token::run(tail).map_err(CliError::from),
220 _ => unreachable!("top-level dispatch command only defines known commands"),
221 }
222}
223
224#[must_use]
225pub const fn version_text() -> &'static str {
226 VERSION_TEXT
227}
228
229#[must_use]
230pub fn render_cli_error(error: &CliError) -> String {
231 match error {
232 CliError::BlobStorage(err) => err.json_error_report().unwrap_or_else(|| error.to_string()),
233 CliError::Build(build::BuildCommandError::Clap(err)) | CliError::Clap(err) => {
234 err.to_string().trim_end().to_string()
235 }
236 CliError::Deploy(err) if err.suppress_stderr() => String::new(),
237 CliError::Inspect(err) if err.suppress_stderr() => String::new(),
238 CliError::Medic(err) if err.suppress_stderr() => String::new(),
239 CliError::State(err) if err.suppress_stderr() => String::new(),
240 _ => error.to_string(),
241 }
242}
243
244#[must_use]
245pub fn cli_error_exit_code(err: &CliError) -> i32 {
246 match err {
247 CliError::Auth(err) => i32::from(err.exit_code()),
248 CliError::BlobStorage(err) => i32::from(err.exit_code()),
249 CliError::Build(err) => err.exit_code(),
250 CliError::Clap(err) => err.exit_code(),
251 CliError::Deploy(err) => i32::from(err.exit_code()),
252 CliError::Info(err) => i32::from(err.exit_code()),
253 CliError::Inspect(err) => i32::from(err.exit_code()),
254 CliError::Medic(err) => i32::from(err.exit_code()),
255 CliError::State(err) => i32::from(err.exit_code()),
256 _ => 1,
257 }
258}
259
260#[cfg(test)]
261mod tests;