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