mod admission;
mod apps;
mod auth;
mod backup;
mod blob_storage;
mod build;
mod cli;
mod cycles;
mod deploy;
mod diagnostic;
mod endpoints;
mod evidence;
mod evidence_support;
mod info;
mod info_env;
mod info_subnets;
mod inspect;
mod install;
mod list;
mod medic;
mod metrics;
mod network;
mod output;
mod replica;
mod restore;
mod scaffold;
mod state;
mod status;
mod support;
#[cfg(test)]
mod test_support;
mod token;
use crate::cli::{
argv::trace_if_enabled,
clap::parse_matches,
globals::{
DISPATCH_ARGS, apply_global_environment, apply_global_icp, global_environment_conflict,
misplaced_global_option,
},
};
use clap::error::ErrorKind;
pub use cli::top_level_command;
use std::ffi::OsString;
use thiserror::Error as ThisError;
const VERSION_TEXT: &str = concat!("canic ", env!("CARGO_PKG_VERSION"));
#[derive(Debug, ThisError)]
pub enum CliError {
#[error("admission: {0}")]
Admission(#[source] Box<admission::AdmissionCommandError>),
#[error("backup: {0}")]
Backup(#[source] Box<backup::BackupCommandError>),
#[error("auth: {0}")]
Auth(#[source] Box<auth::AuthCommandError>),
#[error("blob-storage: {0}")]
BlobStorage(#[source] Box<blob_storage::BlobStorageCommandError>),
#[error(transparent)]
Clap(#[from] clap::Error),
#[error("build: {0}")]
Build(#[from] build::BuildCommandError),
#[error("cycles: {0}")]
Cycles(#[source] Box<cycles::CyclesCommandError>),
#[error("deploy: {0}")]
Deploy(#[from] deploy::DeployCommandError),
#[error("diagnostic: {0}")]
Diagnostic(#[from] diagnostic::DiagnosticCommandError),
#[error("evidence: {0}")]
Evidence(#[from] evidence::EvidenceCommandError),
#[error("install: {0}")]
Install(#[from] install::InstallCommandError),
#[error("inspect: {0}")]
Inspect(#[source] Box<inspect::InspectCommandError>),
#[error("info: {0}")]
Info(#[from] info::InfoCommandError),
#[error("medic: {0}")]
Medic(#[from] medic::MedicCommandError),
#[error("network: {0}")]
Network(#[from] network::NetworkCommandError),
#[error("app: {0}")]
Apps(#[source] Box<apps::AppCommandError>),
#[error("state: {0}")]
State(#[from] state::StateCommandError),
#[error("scaffold: {0}")]
Scaffold(#[from] scaffold::ScaffoldCommandError),
#[error("restore: {0}")]
Restore(#[from] restore::RestoreCommandError),
#[error("replica: {0}")]
Replica(#[from] replica::ReplicaCommandError),
#[error("status: {0}")]
Status(#[from] status::StatusCommandError),
#[error("token: {0}")]
Token(#[source] Box<token::TokenCommandError>),
}
impl From<backup::BackupCommandError> for CliError {
fn from(error: backup::BackupCommandError) -> Self {
Self::Backup(Box::new(error))
}
}
impl From<admission::AdmissionCommandError> for CliError {
fn from(error: admission::AdmissionCommandError) -> Self {
Self::Admission(Box::new(error))
}
}
impl From<auth::AuthCommandError> for CliError {
fn from(error: auth::AuthCommandError) -> Self {
Self::Auth(Box::new(error))
}
}
impl From<blob_storage::BlobStorageCommandError> for CliError {
fn from(error: blob_storage::BlobStorageCommandError) -> Self {
Self::BlobStorage(Box::new(error))
}
}
impl From<cycles::CyclesCommandError> for CliError {
fn from(error: cycles::CyclesCommandError) -> Self {
Self::Cycles(Box::new(error))
}
}
impl From<inspect::InspectCommandError> for CliError {
fn from(error: inspect::InspectCommandError) -> Self {
Self::Inspect(Box::new(error))
}
}
impl From<apps::AppCommandError> for CliError {
fn from(error: apps::AppCommandError) -> Self {
Self::Apps(Box::new(error))
}
}
impl From<token::TokenCommandError> for CliError {
fn from(error: token::TokenCommandError) -> Self {
Self::Token(Box::new(error))
}
}
pub fn run_from_env() -> Result<(), CliError> {
let argv = std::env::args_os().collect::<Vec<_>>();
trace_if_enabled(&argv);
run(argv.into_iter().skip(1))
}
pub fn run<I>(args: I) -> Result<(), CliError>
where
I: IntoIterator<Item = OsString>,
{
let args = args.into_iter().collect::<Vec<_>>();
if let Some(option) = misplaced_global_option(&args) {
let error = top_level_command().error(
ErrorKind::UnknownArgument,
format!("unexpected argument '{option}' found; put it before the command"),
);
return Err(CliError::Clap(error));
}
let matches = match parse_matches(top_level_command(), args) {
Ok(matches) => matches,
Err(error) if !error.use_stderr() => {
let _ = error.print();
return Ok(());
}
Err(error) => return Err(CliError::Clap(error)),
};
let global_icp = cli::clap::string_option(&matches, "icp");
let global_environment = cli::clap::string_option(&matches, "environment");
let (command, subcommand_matches) = matches
.subcommand()
.unwrap_or_else(|| unreachable!("Clap requires one top-level command"));
let mut tail = subcommand_matches
.get_many::<OsString>(DISPATCH_ARGS)
.map(|values| values.cloned().collect::<Vec<_>>())
.unwrap_or_default();
if let Some(conflict) =
global_environment_conflict(command, &tail, global_environment.as_deref())
{
return Err(CliError::Clap(
top_level_command().error(ErrorKind::InvalidValue, conflict.to_string()),
));
}
apply_global_icp(command, &mut tail, global_icp);
apply_global_environment(command, &mut tail, global_environment);
let tail = tail.into_iter();
match command {
"admission" => admission::run(tail).map_err(CliError::from),
"app" => apps::run(tail).map_err(CliError::from),
"auth" => auth::run(tail).map_err(CliError::from),
"backup" => backup::run(tail).map_err(CliError::from),
"blob-storage" => blob_storage::run(tail).map_err(CliError::from),
"build" => build::run(tail).map_err(CliError::from),
"cycles" => cycles::run(tail).map_err(CliError::from),
"deploy" => deploy::run(tail).map_err(CliError::from),
"diagnostic" => diagnostic::run(tail).map_err(CliError::from),
"evidence" => evidence::run(tail).map_err(CliError::from),
"info" => info::run(tail).map_err(CliError::from),
"inspect" => inspect::run(tail).map_err(CliError::from),
"install" => install::run(tail).map_err(CliError::from),
"medic" => medic::run(tail).map_err(CliError::from),
"network" => network::run(tail).map_err(CliError::from),
"replica" => replica::run(tail).map_err(CliError::from),
"restore" => restore::run(tail).map_err(CliError::from),
"scaffold" => scaffold::run(tail).map_err(CliError::from),
"state" => state::run(tail).map_err(CliError::from),
"status" => status::run(tail).map_err(CliError::from),
"token" => token::run(tail).map_err(CliError::from),
_ => unreachable!("top-level dispatch command only defines known commands"),
}
}
#[must_use]
pub const fn version_text() -> &'static str {
VERSION_TEXT
}
#[must_use]
pub fn render_cli_error(error: &CliError) -> String {
match error {
CliError::BlobStorage(err) => err.json_error_report().unwrap_or_else(|| error.to_string()),
CliError::Build(build::BuildCommandError::Clap(err)) | CliError::Clap(err) => {
err.to_string().trim_end().to_string()
}
CliError::Deploy(err) if err.suppress_stderr() => String::new(),
CliError::Inspect(err) if err.suppress_stderr() => String::new(),
CliError::Medic(err) if err.suppress_stderr() => String::new(),
CliError::State(err) if err.suppress_stderr() => String::new(),
_ => error.to_string(),
}
}
#[must_use]
pub fn cli_error_exit_code(err: &CliError) -> i32 {
match err {
CliError::Auth(err) => i32::from(err.exit_code()),
CliError::BlobStorage(err) => i32::from(err.exit_code()),
CliError::Build(err) => err.exit_code(),
CliError::Clap(err) => err.exit_code(),
CliError::Deploy(err) => i32::from(err.exit_code()),
CliError::Info(err) => i32::from(err.exit_code()),
CliError::Inspect(err) => i32::from(err.exit_code()),
CliError::Medic(err) => i32::from(err.exit_code()),
CliError::State(err) => i32::from(err.exit_code()),
_ => 1,
}
}
#[cfg(test)]
mod tests;