1mod auth;
2mod backup;
3mod blob_storage;
4mod build;
5mod cli;
6mod cycles;
7mod deploy;
8mod endpoints;
9mod evidence;
10mod evidence_support;
11mod fleets;
12mod info;
13mod info_env;
14mod install;
15mod list;
16mod medic;
17mod metrics;
18mod output;
19mod replica;
20mod restore;
21mod scaffold;
22mod snapshot;
23mod status;
24mod support;
25#[cfg(test)]
26mod test_support;
27mod token;
28
29use crate::cli::{
30 clap::parse_matches,
31 globals::{
32 DISPATCH_ARGS, apply_global_icp, apply_global_network, command_local_global_option,
33 top_level_dispatch_command,
34 },
35 help::{first_arg_is_help, usage},
36};
37pub use cli::top_level_command;
38use std::ffi::OsString;
39use thiserror::Error as ThisError;
40
41const VERSION_TEXT: &str = concat!("canic ", env!("CARGO_PKG_VERSION"));
42
43#[derive(Debug, ThisError)]
48pub enum CliError {
49 #[error("{0}")]
50 Usage(String),
51
52 #[error("backup: {0}")]
53 Backup(#[from] backup::BackupCommandError),
54
55 #[error("auth: {0}")]
56 Auth(#[from] auth::AuthCommandError),
57
58 #[error("blob-storage: {0}")]
59 BlobStorage(#[from] blob_storage::BlobStorageCommandError),
60
61 #[error("build: {0}")]
62 Build(#[from] build::BuildCommandError),
63
64 #[error("cycles: {0}")]
65 Cycles(#[from] cycles::CyclesCommandError),
66
67 #[error("deploy: {0}")]
68 Deploy(#[from] deploy::DeployCommandError),
69
70 #[error("evidence: {0}")]
71 Evidence(#[from] evidence::EvidenceCommandError),
72
73 #[error("install: {0}")]
74 Install(#[from] install::InstallCommandError),
75
76 #[error("info: {0}")]
77 Info(#[from] info::InfoCommandError),
78
79 #[error("fleet: {0}")]
80 Fleets(#[from] fleets::FleetCommandError),
81
82 #[error("snapshot: {0}")]
83 Snapshot(#[from] snapshot::SnapshotCommandError),
84
85 #[error("scaffold: {0}")]
86 Scaffold(#[from] scaffold::ScaffoldCommandError),
87
88 #[error("restore: {0}")]
89 Restore(#[from] restore::RestoreCommandError),
90
91 #[error("replica: {0}")]
92 Replica(#[from] replica::ReplicaCommandError),
93
94 #[error("status: {0}")]
95 Status(#[from] status::StatusCommandError),
96
97 #[error("token: {0}")]
98 Token(#[from] token::TokenCommandError),
99}
100
101pub fn run_from_env() -> Result<(), CliError> {
103 run(std::env::args_os().skip(1))
104}
105
106pub fn run<I>(args: I) -> Result<(), CliError>
108where
109 I: IntoIterator<Item = OsString>,
110{
111 let args = args.into_iter().collect::<Vec<_>>();
112 if first_arg_is_help(&args) {
113 println!("{}", usage());
114 return Ok(());
115 }
116 if let Some(option) = command_local_global_option(&args) {
117 return Err(CliError::Usage(format!(
118 "{option} is a top-level option; put it before the command\n\n{}",
119 usage()
120 )));
121 }
122
123 let matches =
124 parse_matches(top_level_dispatch_command(), args).map_err(|_| CliError::Usage(usage()))?;
125 if matches.get_flag("version") {
126 println!("{}", version_text());
127 return Ok(());
128 }
129 let global_icp = cli::clap::string_option(&matches, "icp");
130 let global_network = cli::clap::string_option(&matches, "network");
131
132 let Some((command, subcommand_matches)) = matches.subcommand() else {
133 return Err(CliError::Usage(usage()));
134 };
135 let mut tail = subcommand_matches
136 .get_many::<OsString>(DISPATCH_ARGS)
137 .map(|values| values.cloned().collect::<Vec<_>>())
138 .unwrap_or_default();
139 apply_global_icp(command, &mut tail, global_icp);
140 apply_global_network(command, &mut tail, global_network);
141 let tail = tail.into_iter();
142
143 match command {
144 "backup" => backup::run(tail).map_err(CliError::from),
145 "auth" => auth::run(tail).map_err(CliError::from),
146 "blob-storage" => blob_storage::run(tail).map_err(CliError::from),
147 "build" => build::run(tail).map_err(CliError::from),
148 "cycles" => cycles::run(tail).map_err(CliError::from),
149 "deploy" => deploy::run(tail).map_err(CliError::from),
150 "evidence" => evidence::run(tail).map_err(CliError::from),
151 "fleet" => fleets::run(tail).map_err(CliError::from),
152 "info" => info::run(tail).map_err(CliError::from),
153 "install" => install::run(tail).map_err(CliError::from),
154 "replica" => replica::run(tail).map_err(CliError::from),
155 "scaffold" => scaffold::run(tail).map_err(CliError::from),
156 "snapshot" => snapshot::run(tail).map_err(CliError::from),
157 "status" => status::run(tail).map_err(CliError::from),
158 "token" => token::run(tail).map_err(CliError::from),
159 "restore" => restore::run(tail).map_err(CliError::from),
160 _ => unreachable!("top-level dispatch command only defines known commands"),
161 }
162}
163
164#[must_use]
165pub const fn version_text() -> &'static str {
166 VERSION_TEXT
167}
168
169#[must_use]
170pub fn render_cli_error(error: &CliError) -> String {
171 match error {
172 CliError::BlobStorage(err) => err.json_error_report().unwrap_or_else(|| error.to_string()),
173 _ => error.to_string(),
174 }
175}
176
177#[must_use]
178pub fn cli_error_exit_code(err: &CliError) -> i32 {
179 match err {
180 CliError::Auth(err) => i32::from(err.exit_code()),
181 CliError::BlobStorage(err) => i32::from(err.exit_code()),
182 _ => 1,
183 }
184}
185
186#[cfg(test)]
187mod tests;