pub mod api;
pub mod cli;
pub mod config;
pub mod model;
pub mod service;
pub mod tui;
use anyhow::{bail, Result};
use clap::Parser;
use crate::cli::args::{Cli, Commands};
use crate::config::manager::ConfigManager;
use crate::model::loader::ModelLoader;
pub fn main_impl() {
let output_format = std::env::args()
.collect::<Vec<_>>()
.windows(2)
.find_map(|w| {
if w[0] == "-o" || w[0] == "--output" {
Some(w[1].clone())
} else {
None
}
})
.unwrap_or_else(|| "table".to_string());
let rt = tokio::runtime::Runtime::new().expect("Failed to create tokio runtime");
if let Err(e) = rt.block_on(async_main()) {
let api_err = e
.chain()
.find_map(|cause| cause.downcast_ref::<api::error::ApiError>());
let msg = if let Some(api_err) = api_err {
match api_err {
api::error::ApiError::Api { code, message } => {
cli::formatter::format_error(&output_format, *code, message)
}
other => cli::formatter::format_error_simple(&output_format, &format!("{}", other)),
}
} else {
cli::formatter::format_error_simple(&output_format, &format!("{:#}", e))
};
eprintln!("{}", msg);
std::process::exit(1);
}
}
async fn async_main() -> Result<()> {
let cli_args = Cli::parse();
tracing_subscriber::fmt()
.with_env_filter(
tracing_subscriber::EnvFilter::from_default_env()
.add_directive("zilliz=info".parse()?),
)
.with_writer(std::io::stderr)
.init();
let config_mgr = ConfigManager::new(None)?;
let models = ModelLoader::load_builtin()?;
if cli_args.help {
match &cli_args.command {
None => {
print!("{}", cli::help::render_help(&models));
}
Some(Commands::External(args)) => {
let resource_name = args.first().map(|s| s.as_str()).unwrap_or("");
let op_name = args.get(1).map(|s| s.as_str());
if let Some(resource) = cli::help::find_resource(&models, resource_name) {
match op_name {
Some(op) if resource.operations.contains_key(op) => {
print!(
"{}",
cli::help::render_operation_help(
resource_name,
op,
&resource.operations[op]
)
);
}
Some(op) => {
let ops: Vec<_> = resource.operations.keys().map(|s| s.as_str()).collect();
bail!(
"Unknown operation '{}' for resource '{}'. Available operations: {}",
op, resource_name, ops.join(", ")
);
}
None => {
print!(
"{}",
cli::help::render_resource_help(resource_name, resource)
);
}
}
} else {
let names = cli::help::available_resources(&models);
bail!(
"Unknown resource '{}'. Available resources: {}",
resource_name, names.join(", ")
);
}
}
Some(cmd) => {
use clap::CommandFactory;
let name = cli::help::subcommand_name(cmd);
let mut root = Cli::command();
if let Some(sub) = root.find_subcommand_mut(name) {
sub.print_help()?;
}
}
}
return Ok(());
}
let output_format = cli_args.output.as_deref().unwrap_or("table");
let fetch_all = cli_args.all;
let output_opts = cli::dispatch::OutputOpts {
format: output_format,
query: cli_args.query.as_deref(),
no_header: cli_args.no_header,
wait: cli_args.wait,
};
match cli_args.command {
None => {
tui::run(models, config_mgr).await?;
}
Some(Commands::Configure { subcmd }) => {
cli::configure::run(&config_mgr, subcmd).await?;
}
Some(Commands::Context(ctx_cmd)) => {
cli::context::run(&models, &config_mgr, ctx_cmd, output_format).await?;
}
Some(Commands::Version) => {
cli::version::run(output_format);
}
Some(Commands::Login {
no_browser,
with_api_key: api_key_mode,
}) => {
let auth_config = models
.control_plane
.auth
.as_ref()
.expect("Auth config not found in control-plane.json");
cli::auth::login(&config_mgr, auth_config, no_browser, api_key_mode).await?;
}
Some(Commands::Logout) => {
cli::auth::logout(&config_mgr)?;
}
Some(Commands::Auth(auth_cmd)) => {
cli::auth_cmd::run(&config_mgr, auth_cmd)?;
}
Some(Commands::Completion(comp_cmd)) => {
cli::completion::run(comp_cmd)?;
}
Some(Commands::Alert { subcmd: Some(alert_cmd) }) => {
cli::alert::run(&config_mgr, alert_cmd, &output_opts).await?;
}
Some(Commands::Alert { subcmd: None }) => {
use clap::CommandFactory;
let mut root = Cli::command();
if let Some(sub) = root.find_subcommand_mut("alert") {
sub.print_help()?;
}
}
Some(Commands::External(args)) => {
let wants_help = args.iter().any(|a| a == "-h" || a == "--help");
const GLOBAL_VALUE_FLAGS: &[&str] = &[
"-o", "--output", "--query", "--api-key",
];
const GLOBAL_BOOL_FLAGS: &[&str] = &[
"-h", "--help", "-a", "--all", "--no-header", "--wait",
];
let mut filtered_args: Vec<&str> = Vec::new();
let mut ext_output: Option<&str> = None;
let mut ext_query: Option<&str> = None;
let mut ext_all = false;
let mut ext_no_header = false;
let mut ext_wait = false;
{
let str_args: Vec<&str> = args.iter().map(|s| s.as_str()).collect();
let mut i = 0;
while i < str_args.len() {
if GLOBAL_BOOL_FLAGS.contains(&str_args[i]) {
match str_args[i] {
"-a" | "--all" => ext_all = true,
"--no-header" => ext_no_header = true,
"--wait" => ext_wait = true,
_ => {}
}
i += 1;
} else if GLOBAL_VALUE_FLAGS.contains(&str_args[i]) {
if let Some(&val) = str_args.get(i + 1) {
match str_args[i] {
"-o" | "--output" => ext_output = Some(val),
"--query" => ext_query = Some(val),
_ => {}
}
}
i += 2;
} else {
filtered_args.push(str_args[i]);
i += 1;
}
}
}
let ext_format_owned = ext_output.map(|s| s.to_string());
let ext_query_owned = ext_query.map(|s| s.to_string());
let output_opts = cli::dispatch::OutputOpts {
format: ext_format_owned.as_deref().unwrap_or(output_opts.format),
query: ext_query_owned.as_deref().or(output_opts.query),
no_header: output_opts.no_header || ext_no_header,
wait: output_opts.wait || ext_wait,
};
let fetch_all = fetch_all || ext_all;
if wants_help {
let resource_name = filtered_args.first().copied().unwrap_or("");
let op_name = filtered_args.get(1).copied();
if let Some(resource) = cli::help::find_resource(&models, resource_name) {
match op_name {
Some(op) if resource.operations.contains_key(op) => {
print!(
"{}",
cli::help::render_operation_help(
resource_name,
op,
&resource.operations[op]
)
);
}
Some(op) => {
let ops: Vec<_> = resource.operations.keys().map(|s| s.as_str()).collect();
bail!(
"Unknown operation '{}' for resource '{}'. Available operations: {}",
op, resource_name, ops.join(", ")
);
}
None => {
print!(
"{}",
cli::help::render_resource_help(resource_name, resource)
);
}
}
} else {
let names = cli::help::available_resources(&models);
bail!(
"Unknown resource '{}'. Available resources: {}",
resource_name, names.join(", ")
);
}
return Ok(());
}
let resource = filtered_args.first().copied().unwrap_or("");
let operation = filtered_args.get(1).copied().unwrap_or("");
let rest: Vec<String> = if filtered_args.len() > 2 {
filtered_args[2..].iter().map(|s| s.to_string()).collect()
} else {
vec![]
};
if operation.is_empty() {
if let Some(res) = cli::help::find_resource(&models, resource) {
print!("{}", cli::help::render_resource_help(resource, res));
} else {
let names = cli::help::available_resources(&models);
bail!(
"Unknown resource '{}'. Available resources: {}",
resource, names.join(", ")
);
}
return Ok(());
}
match (resource, operation) {
("cluster", "create") => {
cli::cluster::create(&models, &config_mgr, &rest, &output_opts).await?;
}
("cluster", "metrics") => {
cli::metrics::run_from_args(&config_mgr, &rest, &output_opts).await?;
}
("billing", "usage") => {
cli::billing::usage(&models, &config_mgr, &rest, &output_opts).await?;
}
("billing", "invoices") => {
cli::billing::invoices(&models, &config_mgr, &rest, &output_opts).await?;
}
_ => {
cli::dispatch::run(
&models,
&config_mgr,
resource,
operation,
&rest,
&output_opts,
fetch_all,
)
.await?;
}
}
}
}
Ok(())
}