canic-cli 0.65.0

Operator CLI for Canic fleet setup, builds, evidence, catalog, backup, and restore workflows
Documentation
use crate::{
    cli::{
        clap::{parse_subcommand, passthrough_subcommand},
        help::print_help_or_version,
    },
    cycles, endpoints, info_env, list, medic, metrics, version_text,
};
use clap::Command as ClapCommand;
use std::ffi::OsString;
use thiserror::Error as ThisError;

const INFO_USAGE: &str = "\
Group read-only installed-deployment information commands

Usage: canic info <command> [OPTIONS]

Commands:
  list       List installed deployment canisters
  cycles     Summarize deployment cycle history
  metrics    Query Canic runtime telemetry
  endpoints  List callable Candid endpoints
  medic      Diagnose local deployment target setup
  env        Print sourceable canister ID exports
  help       Print this message or the help of the given subcommand(s)

Examples:
  canic info list test --subtree scale_hub
  canic info cycles test --subtree scale_hub
  canic info metrics test
  canic info endpoints test app
  canic info medic test
  canic info env test";

///
/// InfoCommandError
///

#[derive(Debug, ThisError)]
pub enum InfoCommandError {
    #[error("{0}")]
    Usage(String),

    #[error("list: {0}")]
    List(#[from] list::ListCommandError),

    #[error("cycles: {0}")]
    Cycles(#[from] cycles::CyclesCommandError),

    #[error("env: {0}")]
    Env(#[from] info_env::InfoEnvCommandError),

    #[error("endpoints: {0}")]
    Endpoints(#[from] endpoints::EndpointsCommandError),

    #[error("medic: {0}")]
    Medic(#[from] medic::MedicCommandError),

    #[error("metrics: {0}")]
    Metrics(#[from] metrics::MetricsCommandError),
}

/// Run the installed-deployment information command group.
pub fn run<I>(args: I) -> Result<(), InfoCommandError>
where
    I: IntoIterator<Item = OsString>,
{
    let args = args.into_iter().collect::<Vec<_>>();
    if print_help_or_version(&args, usage, version_text()) {
        return Ok(());
    }

    match parse_info_command(args)? {
        ("list", tail) => list::run_info(tail).map_err(InfoCommandError::from),
        ("cycles", tail) => cycles::run_info(tail).map_err(InfoCommandError::from),
        ("metrics", tail) => metrics::run_info(tail).map_err(InfoCommandError::from),
        ("endpoints", tail) => endpoints::run_info(tail).map_err(InfoCommandError::from),
        ("medic", tail) => medic::run_info(tail).map_err(InfoCommandError::from),
        ("env", tail) => info_env::run(tail).map_err(InfoCommandError::from),
        _ => unreachable!("clap restricts info subcommands"),
    }
}

fn parse_info_command(
    args: Vec<OsString>,
) -> Result<(&'static str, Vec<OsString>), InfoCommandError> {
    let (command, tail) = parse_subcommand(command(), args)
        .map_err(|_| InfoCommandError::Usage(usage()))?
        .ok_or_else(|| InfoCommandError::Usage(usage()))?;
    match command.as_str() {
        "list" => Ok(("list", tail)),
        "cycles" => Ok(("cycles", tail)),
        "metrics" => Ok(("metrics", tail)),
        "endpoints" => Ok(("endpoints", tail)),
        "medic" => Ok(("medic", tail)),
        "env" => Ok(("env", tail)),
        _ => unreachable!("clap restricts info subcommands"),
    }
}

fn command() -> ClapCommand {
    ClapCommand::new("info")
        .bin_name("canic info")
        .about("Group read-only installed-deployment information commands")
        .disable_help_flag(true)
        .subcommand(passthrough_subcommand(ClapCommand::new("list")))
        .subcommand(passthrough_subcommand(ClapCommand::new("cycles")))
        .subcommand(passthrough_subcommand(ClapCommand::new("metrics")))
        .subcommand(passthrough_subcommand(ClapCommand::new("endpoints")))
        .subcommand(passthrough_subcommand(ClapCommand::new("medic")))
        .subcommand(passthrough_subcommand(ClapCommand::new("env")))
}

#[must_use]
fn usage() -> String {
    INFO_USAGE.to_string()
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn parses_info_subcommands_with_passthrough_args() {
        let (command, tail) = parse_info_command(vec![
            OsString::from("list"),
            OsString::from("demo"),
            OsString::from("--subtree"),
            OsString::from("app"),
        ])
        .expect("parse info list");

        assert_eq!(command, "list");
        assert_eq!(
            tail,
            vec![
                OsString::from("demo"),
                OsString::from("--subtree"),
                OsString::from("app")
            ]
        );
    }

    #[test]
    fn rejects_missing_or_unknown_info_subcommand() {
        std::assert_matches!(
            parse_info_command(Vec::new()),
            Err(InfoCommandError::Usage(_))
        );
        std::assert_matches!(
            parse_info_command(vec![OsString::from("unknown")]),
            Err(InfoCommandError::Usage(_))
        );
    }
}