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 if let Some(help) = cli::help::render_hand_written_resource_help(resource_name) {
match op_name {
Some(op) if cli::help::is_hand_written_op(resource_name, op) => {
if let Some(desc) = cli::help::hand_written_op_description(resource_name, op) {
println!("{}\n\nUsage: zilliz {} {} [OPTIONS]", desc, resource_name, op);
}
}
Some(op) => {
bail!(
"Unknown operation '{}' for resource '{}'.",
op, resource_name
);
}
None => {
print!("{}", help);
}
}
} else {
let names = cli::help::available_resources(&models);
bail!(
"Unknown resource '{}'. Available resources: {}",
resource_name, names.join(", ")
);
}
}
Some(cmd) => {
cli::help::print_subcommand_help(cli::help::subcommand_name(cmd))?;
}
}
return Ok(());
}
let output_format = cli_args.output.as_deref().unwrap_or("table");
let output_opts = cli::dispatch::OutputOpts {
format: output_format,
query: cli_args.query.as_deref(),
no_header: cli_args.no_header,
wait: false,
api_key: None,
};
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 { subcmd: Some(ctx_cmd) }) => {
cli::context::run(&models, &config_mgr, ctx_cmd, output_format, output_opts.api_key).await?;
}
Some(Commands::Context { subcmd: None }) => {
cli::help::print_subcommand_help("context")?;
}
Some(Commands::Version) => {
cli::version::run(output_format);
}
Some(Commands::Login {
no_browser,
api_key,
}) => {
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.as_deref()).await?;
}
Some(Commands::Logout) => {
cli::auth::logout(&config_mgr)?;
}
Some(Commands::Auth { subcmd: Some(auth_cmd) }) => {
cli::auth_cmd::run(&config_mgr, auth_cmd)?;
}
Some(Commands::Auth { subcmd: None }) => {
cli::help::print_subcommand_help("auth")?;
}
Some(Commands::Completion { subcmd: Some(comp_cmd) }) => {
cli::completion::run(comp_cmd)?;
}
Some(Commands::Completion { subcmd: None }) => {
cli::help::print_subcommand_help("completion")?;
}
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", "--no-header",
];
const EXT_BOOL_FLAGS: &[&str] = &["-a", "--all", "--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_api_key: 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]) || EXT_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).filter(|v| !v.starts_with('-')) {
match str_args[i] {
"-o" | "--output" => ext_output = Some(val),
"--query" => ext_query = Some(val),
"--api-key" => ext_api_key = Some(val),
_ => {}
}
i += 2;
} else {
i += 1;
}
} 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 ext_api_key_owned = ext_api_key.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,
api_key: ext_api_key_owned.as_deref().or(output_opts.api_key),
};
let 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) if cli::help::is_hand_written_op(resource_name, op) => {
let help_args = vec!["--help".to_string()];
match (resource_name, op) {
("backup", "create") => {
cli::backup::create(&models, &config_mgr, &help_args, &output_opts).await?;
}
("cluster", "create") => {
cli::cluster::create(&models, &config_mgr, &help_args, &output_opts).await?;
}
("cluster", "metrics") => {
cli::metrics::run_from_args(&config_mgr, &help_args, &output_opts).await?;
}
("billing", "usage") => {
cli::billing::usage(&models, &config_mgr, &help_args, &output_opts).await?;
}
("billing", "invoices") => {
cli::billing::invoices(&models, &config_mgr, &help_args, &output_opts).await?;
}
_ => {
if let Some(desc) = cli::help::hand_written_op_description(resource_name, op) {
println!("{}\n\nUsage: zilliz {} {} [OPTIONS]", desc, resource_name, op);
}
}
}
}
Some(op) => {
let mut ops: Vec<_> = resource.operations.keys().map(|s| s.as_str()).collect();
for &(res, hw_op, _) in cli::help::hand_written_ops() {
if res == resource_name && !ops.contains(&hw_op) {
ops.push(hw_op);
}
}
bail!(
"Unknown operation '{}' for resource '{}'. Available operations: {}",
op, resource_name, ops.join(", ")
);
}
None => {
print!(
"{}",
cli::help::render_resource_help(resource_name, resource)
);
}
}
} else if cli::help::is_hand_written_resource(resource_name) {
match op_name {
Some(op) if cli::help::is_hand_written_op(resource_name, op) => {
let help_args = vec![op.to_string(), "--help".to_string()];
match resource_name {
"alert" => {
cli::alert::run_from_args(&config_mgr, &help_args, &output_opts).await?;
}
_ => {
if let Some(desc) = cli::help::hand_written_op_description(resource_name, op) {
println!("{}\n\nUsage: zilliz {} {} [OPTIONS]", desc, resource_name, op);
}
}
}
}
Some(op) => {
bail!(
"Unknown operation '{}' for resource '{}'. Available: list, create, update, delete, enable, disable",
op, resource_name
);
}
None => {
if let Some(help) = cli::help::render_hand_written_resource_help(resource_name) {
print!("{}", help);
}
}
}
} 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 if let Some(help) = cli::help::render_hand_written_resource_help(resource) {
print!("{}", help);
} else {
let names = cli::help::available_resources(&models);
bail!(
"Unknown resource '{}'. Available resources: {}",
resource, names.join(", ")
);
}
return Ok(());
}
match (resource, operation) {
("alert", op) => {
let mut alert_args = vec![op.to_string()];
alert_args.extend(rest);
cli::alert::run_from_args(&config_mgr, &alert_args, &output_opts).await?;
}
("backup", "create") => {
cli::backup::create(&models, &config_mgr, &rest, &output_opts).await?;
}
("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(())
}