Public release release-ff7f847f143d
Source commit: ff7f847f143dc67864bed68a86cf259114384bd5 Public tree identity: sha256:bb7dcd2ac78bdbad2f4eba0f49be649d446d67f96f4eb2796941c026412fc032
This commit is contained in:
commit
70319cde15
210 changed files with 78958 additions and 0 deletions
689
crates/clusterflux-cli/src/main.rs
Normal file
689
crates/clusterflux-cli/src/main.rs
Normal file
|
|
@ -0,0 +1,689 @@
|
|||
#[cfg(test)]
|
||||
use std::io::{BufRead, BufReader, Write};
|
||||
#[cfg(test)]
|
||||
use std::net::TcpListener;
|
||||
#[cfg(test)]
|
||||
use std::path::Path;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use clap::{Args, Parser, Subcommand};
|
||||
#[cfg(test)]
|
||||
use clusterflux_core::{Capability, Digest, ProjectModel, SourceProviderKind};
|
||||
#[cfg(test)]
|
||||
use serde_json::json;
|
||||
use serde_json::Value;
|
||||
|
||||
mod admin;
|
||||
mod agent;
|
||||
mod artifact;
|
||||
mod auth;
|
||||
mod auth_scope;
|
||||
mod build;
|
||||
mod bundle;
|
||||
mod client;
|
||||
mod config;
|
||||
mod confirm;
|
||||
mod debug;
|
||||
mod dispatch;
|
||||
mod doctor;
|
||||
mod errors;
|
||||
mod key;
|
||||
mod logout;
|
||||
mod logs;
|
||||
mod node;
|
||||
mod output;
|
||||
mod process;
|
||||
mod process_events;
|
||||
mod project;
|
||||
mod quota;
|
||||
mod run;
|
||||
mod task;
|
||||
mod tools;
|
||||
|
||||
use admin::{admin_bootstrap_report, admin_status_report, admin_suspend_tenant_report};
|
||||
use agent::agent_enrollment_plan;
|
||||
#[cfg(test)]
|
||||
use artifact::{
|
||||
artifact_download_report, artifact_export_report, artifact_list_report,
|
||||
artifact_stream_summary, DEFAULT_ARTIFACT_EXPORT_MAX_BYTES,
|
||||
};
|
||||
use artifact::{
|
||||
artifact_download_report_with_session, artifact_export_report_with_session,
|
||||
artifact_list_report_with_session,
|
||||
};
|
||||
use auth::{
|
||||
auth_status_report, connect_self_hosted_report, execute_interactive_browser_login, login_plan,
|
||||
non_interactive_browser_login_report, print_browser_login_success,
|
||||
read_session_secret_from_stdin,
|
||||
};
|
||||
#[cfg(test)]
|
||||
use auth::{contains_provider_token_field, stored_cli_session_from_login_response, LoginFlowPlan};
|
||||
#[cfg(test)]
|
||||
use auth_scope::login_args_for_project;
|
||||
use build::build_report;
|
||||
#[cfg(test)]
|
||||
use bundle::task_abi_digest;
|
||||
pub(crate) use bundle::{bundle_inspection, discovered_environment_names};
|
||||
#[cfg(test)]
|
||||
use client::control_endpoint_identity;
|
||||
use config::{default_hosted_coordinator_endpoint, read_cli_session};
|
||||
#[cfg(test)]
|
||||
use config::{
|
||||
read_project_config, session_config_file, write_cli_session, write_project_config,
|
||||
ProjectConfig, StoredCliSession, DEFAULT_HOSTED_COORDINATOR_ENDPOINT,
|
||||
};
|
||||
pub(crate) use confirm::confirmation_required_report;
|
||||
#[cfg(test)]
|
||||
use debug::debug_attach_report_with_dap;
|
||||
use debug::{dap_plan, debug_attach_report_with_dap_and_session, exec_dap};
|
||||
use doctor::doctor_report;
|
||||
use errors::cli_error_summary;
|
||||
#[cfg(test)]
|
||||
use errors::cli_error_summary_for_category;
|
||||
#[cfg(test)]
|
||||
use key::{key_add_report, key_list_report, key_revoke_report};
|
||||
use key::{
|
||||
key_add_report_with_session, key_list_report_with_session, key_revoke_report_with_session,
|
||||
};
|
||||
use logout::{auth_logout_report, logout_report};
|
||||
#[cfg(test)]
|
||||
use logs::logs_report;
|
||||
use logs::logs_report_with_session;
|
||||
use node::{
|
||||
attach_plan, execute_node_attach, node_enroll_report, node_list_report, node_revoke_report,
|
||||
node_status_report,
|
||||
};
|
||||
use output::emit_report;
|
||||
#[cfg(test)]
|
||||
use output::{apply_command_report_exit_code, human_report};
|
||||
use process::{
|
||||
process_abort_report_with_session, process_cancel_report, process_cancel_report_with_session,
|
||||
process_list_report_with_session, process_restart_report_with_session,
|
||||
process_status_report_with_session,
|
||||
};
|
||||
#[cfg(test)]
|
||||
use process::{process_restart_report, process_status_report};
|
||||
#[cfg(test)]
|
||||
use process_events::task_summaries;
|
||||
#[cfg(test)]
|
||||
use process_events::{
|
||||
artifact_download_session_summary, artifact_export_plan_summary, log_entries,
|
||||
};
|
||||
use project::{
|
||||
project_init_report, project_list_report, project_select_report, project_status_report,
|
||||
};
|
||||
use quota::quota_status_report;
|
||||
#[cfg(test)]
|
||||
use run::{
|
||||
agent_session_from_keys, run_plan, run_start_summary, should_execute_local_node, CliSession,
|
||||
CoordinatorSelection,
|
||||
};
|
||||
use run::{run_report, session_from_sources};
|
||||
#[cfg(test)]
|
||||
use task::{task_list_report, task_restart_report};
|
||||
use task::{task_list_report_with_session, task_restart_report_with_session};
|
||||
use tools::dap_binary_path;
|
||||
|
||||
#[derive(Clone, Debug, Parser)]
|
||||
#[command(
|
||||
name = "clusterflux",
|
||||
version,
|
||||
arg_required_else_help = true,
|
||||
about = "Clusterflux distributed Wasm runtime CLI.",
|
||||
after_help = "Primary workflow:
|
||||
1. clusterflux login --browser
|
||||
2. clusterflux project init
|
||||
3. clusterflux node enroll; clusterflux node attach; clusterflux-node --worker
|
||||
4. clusterflux run [entry] --project <path>
|
||||
5. Debug with VS Code \"Clusterflux: Launch Virtual Process\" or clusterflux dap
|
||||
6. Inspect with clusterflux process list, clusterflux process status, task list, logs, and artifact list
|
||||
7. Request cooperative shutdown with process cancel; force termination with process abort
|
||||
|
||||
Use --json on primary commands for scriptable output. Hosted account creation happens in the browser login flow."
|
||||
)]
|
||||
struct Cli {
|
||||
#[command(subcommand)]
|
||||
command: Commands,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Subcommand)]
|
||||
enum Commands {
|
||||
Doctor(DoctorArgs),
|
||||
Login(LoginArgs),
|
||||
Logout(AuthLogoutArgs),
|
||||
Auth {
|
||||
#[command(subcommand)]
|
||||
command: AuthCommands,
|
||||
},
|
||||
Agent {
|
||||
#[command(subcommand)]
|
||||
command: AgentCommands,
|
||||
},
|
||||
Key {
|
||||
#[command(subcommand)]
|
||||
command: KeyCommands,
|
||||
},
|
||||
Project {
|
||||
#[command(subcommand)]
|
||||
command: ProjectCommands,
|
||||
},
|
||||
Inspect(BundleInspectArgs),
|
||||
Build(BuildArgs),
|
||||
Bundle {
|
||||
#[command(subcommand)]
|
||||
command: BundleCommands,
|
||||
},
|
||||
Run(RunArgs),
|
||||
Node {
|
||||
#[command(subcommand)]
|
||||
command: NodeCommands,
|
||||
},
|
||||
Process {
|
||||
#[command(subcommand)]
|
||||
command: ProcessCommands,
|
||||
},
|
||||
Task {
|
||||
#[command(subcommand)]
|
||||
command: TaskCommands,
|
||||
},
|
||||
Logs(LogsArgs),
|
||||
Artifact {
|
||||
#[command(subcommand)]
|
||||
command: ArtifactCommands,
|
||||
},
|
||||
Dap(DapArgs),
|
||||
Debug {
|
||||
#[command(subcommand)]
|
||||
command: DebugCommands,
|
||||
},
|
||||
Quota {
|
||||
#[command(subcommand)]
|
||||
command: QuotaCommands,
|
||||
},
|
||||
Admin {
|
||||
#[command(subcommand)]
|
||||
command: AdminCommands,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Parser)]
|
||||
struct DoctorArgs {
|
||||
#[command(flatten)]
|
||||
scope: CliScopeArgs,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Parser)]
|
||||
struct LoginArgs {
|
||||
#[arg(long = "browser")]
|
||||
_browser: bool,
|
||||
#[arg(long)]
|
||||
non_interactive: bool,
|
||||
#[arg(long)]
|
||||
plan: bool,
|
||||
#[arg(long)]
|
||||
json: bool,
|
||||
#[arg(long, default_value_t = default_hosted_coordinator_endpoint())]
|
||||
coordinator: String,
|
||||
#[arg(long = "project-id", default_value = "project")]
|
||||
project: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Subcommand)]
|
||||
enum AuthCommands {
|
||||
Status(AuthStatusArgs),
|
||||
ConnectSelfHosted(ConnectSelfHostedArgs),
|
||||
Logout(AuthLogoutArgs),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Parser)]
|
||||
struct AuthStatusArgs {
|
||||
#[command(flatten)]
|
||||
scope: CliScopeArgs,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Parser)]
|
||||
struct ConnectSelfHostedArgs {
|
||||
#[command(flatten)]
|
||||
scope: CliScopeArgs,
|
||||
#[arg(long, required = true)]
|
||||
session_secret_stdin: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Parser)]
|
||||
struct AuthLogoutArgs {
|
||||
#[arg(long)]
|
||||
yes: bool,
|
||||
#[command(flatten)]
|
||||
scope: CliScopeArgs,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Subcommand)]
|
||||
enum AgentCommands {
|
||||
Enroll(AgentEnrollArgs),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Subcommand)]
|
||||
enum KeyCommands {
|
||||
Add(KeyAddArgs),
|
||||
List(KeyListArgs),
|
||||
Revoke(KeyRevokeArgs),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Subcommand)]
|
||||
enum BundleCommands {
|
||||
Inspect(BundleInspectArgs),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Parser)]
|
||||
struct AgentEnrollArgs {
|
||||
#[arg(long)]
|
||||
json: bool,
|
||||
#[arg(long = "public-key")]
|
||||
public_key: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Parser)]
|
||||
struct KeyAddArgs {
|
||||
#[command(flatten)]
|
||||
scope: CliScopeArgs,
|
||||
#[arg(long, default_value = "agent")]
|
||||
agent: String,
|
||||
#[arg(long = "public-key")]
|
||||
public_key: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Parser)]
|
||||
struct KeyListArgs {
|
||||
#[command(flatten)]
|
||||
scope: CliScopeArgs,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Parser)]
|
||||
struct KeyRevokeArgs {
|
||||
#[command(flatten)]
|
||||
scope: CliScopeArgs,
|
||||
#[arg(long, default_value = "agent")]
|
||||
agent: String,
|
||||
#[arg(long)]
|
||||
yes: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Subcommand)]
|
||||
enum ProjectCommands {
|
||||
Init(ProjectInitArgs),
|
||||
Status(ProjectStatusArgs),
|
||||
List(ProjectListArgs),
|
||||
Select(ProjectSelectArgs),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Parser)]
|
||||
struct ProjectInitArgs {
|
||||
#[command(flatten)]
|
||||
scope: CliScopeArgs,
|
||||
#[arg(long, default_value = "project")]
|
||||
new_project: String,
|
||||
#[arg(long, default_value = "Clusterflux Project")]
|
||||
name: String,
|
||||
#[arg(long)]
|
||||
yes: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Parser)]
|
||||
struct ProjectStatusArgs {
|
||||
#[command(flatten)]
|
||||
scope: CliScopeArgs,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Parser)]
|
||||
struct ProjectListArgs {
|
||||
#[command(flatten)]
|
||||
scope: CliScopeArgs,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Parser)]
|
||||
struct ProjectSelectArgs {
|
||||
#[command(flatten)]
|
||||
scope: CliScopeArgs,
|
||||
#[arg(value_name = "PROJECT")]
|
||||
selected_project: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Parser)]
|
||||
struct BundleInspectArgs {
|
||||
#[arg(long)]
|
||||
project: Option<PathBuf>,
|
||||
#[arg(long = "source-provider")]
|
||||
source_provider: Option<String>,
|
||||
#[arg(long = "disable-source-provider")]
|
||||
disabled_source_providers: Vec<String>,
|
||||
#[arg(long)]
|
||||
json: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Parser)]
|
||||
struct BuildArgs {
|
||||
#[arg(long)]
|
||||
project: Option<PathBuf>,
|
||||
#[arg(long = "source-provider")]
|
||||
source_provider: Option<String>,
|
||||
#[arg(long = "disable-source-provider")]
|
||||
disabled_source_providers: Vec<String>,
|
||||
#[arg(long)]
|
||||
output: Option<PathBuf>,
|
||||
#[arg(long)]
|
||||
json: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Parser)]
|
||||
struct RunArgs {
|
||||
entry: Option<String>,
|
||||
#[arg(long)]
|
||||
project: Option<PathBuf>,
|
||||
#[arg(long)]
|
||||
coordinator: Option<String>,
|
||||
#[arg(long)]
|
||||
local: bool,
|
||||
#[arg(long)]
|
||||
non_interactive: bool,
|
||||
#[arg(long)]
|
||||
json: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Subcommand)]
|
||||
enum NodeCommands {
|
||||
Attach(AttachArgs),
|
||||
Enroll(NodeEnrollArgs),
|
||||
List(NodeListArgs),
|
||||
Status(NodeStatusArgs),
|
||||
Revoke(NodeRevokeArgs),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Parser)]
|
||||
struct AttachArgs {
|
||||
#[arg(long)]
|
||||
coordinator: Option<String>,
|
||||
#[arg(long, default_value = "tenant")]
|
||||
tenant: String,
|
||||
#[arg(long = "project-id", default_value = "project")]
|
||||
project: String,
|
||||
#[arg(long)]
|
||||
node: Option<String>,
|
||||
#[arg(long = "cap")]
|
||||
caps: Vec<String>,
|
||||
#[arg(long = "enrollment-grant")]
|
||||
enrollment_grant: Option<String>,
|
||||
#[arg(long = "public-key")]
|
||||
public_key: Option<String>,
|
||||
#[arg(long)]
|
||||
json: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Parser)]
|
||||
struct NodeEnrollArgs {
|
||||
#[command(flatten)]
|
||||
scope: CliScopeArgs,
|
||||
#[arg(long, default_value_t = 900)]
|
||||
ttl_seconds: u64,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Parser)]
|
||||
struct NodeListArgs {
|
||||
#[command(flatten)]
|
||||
scope: CliScopeArgs,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Parser)]
|
||||
struct NodeStatusArgs {
|
||||
#[command(flatten)]
|
||||
scope: CliScopeArgs,
|
||||
#[arg(long)]
|
||||
node: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Parser)]
|
||||
struct NodeRevokeArgs {
|
||||
#[command(flatten)]
|
||||
scope: CliScopeArgs,
|
||||
#[arg(long)]
|
||||
node: String,
|
||||
#[arg(long)]
|
||||
yes: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Subcommand)]
|
||||
enum ProcessCommands {
|
||||
List(ProcessListArgs),
|
||||
Status(ProcessStatusArgs),
|
||||
Restart(ProcessRestartArgs),
|
||||
Cancel(ProcessCancelArgs),
|
||||
Abort(ProcessAbortArgs),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Parser)]
|
||||
struct ProcessListArgs {
|
||||
#[command(flatten)]
|
||||
scope: CliScopeArgs,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Parser)]
|
||||
struct ProcessStatusArgs {
|
||||
#[command(flatten)]
|
||||
scope: CliScopeArgs,
|
||||
#[arg(long, default_value = "vp-current")]
|
||||
process: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Parser)]
|
||||
struct ProcessRestartArgs {
|
||||
#[command(flatten)]
|
||||
scope: CliScopeArgs,
|
||||
#[arg(long, default_value = "vp-current")]
|
||||
process: String,
|
||||
#[arg(long)]
|
||||
yes: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Parser)]
|
||||
struct ProcessCancelArgs {
|
||||
#[command(flatten)]
|
||||
scope: CliScopeArgs,
|
||||
#[arg(long, default_value = "vp-current")]
|
||||
process: String,
|
||||
#[arg(long)]
|
||||
node: Option<String>,
|
||||
#[arg(long)]
|
||||
task: Option<String>,
|
||||
#[arg(long)]
|
||||
yes: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Parser)]
|
||||
struct ProcessAbortArgs {
|
||||
#[command(flatten)]
|
||||
scope: CliScopeArgs,
|
||||
#[arg(long, default_value = "vp-current")]
|
||||
process: String,
|
||||
#[arg(long)]
|
||||
yes: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Subcommand)]
|
||||
enum TaskCommands {
|
||||
List(TaskListArgs),
|
||||
Restart(TaskRestartArgs),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Parser)]
|
||||
struct TaskListArgs {
|
||||
#[command(flatten)]
|
||||
scope: CliScopeArgs,
|
||||
#[arg(long)]
|
||||
process: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Parser)]
|
||||
struct TaskRestartArgs {
|
||||
#[command(flatten)]
|
||||
scope: CliScopeArgs,
|
||||
task: String,
|
||||
#[arg(long, default_value = "vp-current")]
|
||||
process: String,
|
||||
#[arg(long)]
|
||||
yes: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Parser)]
|
||||
struct LogsArgs {
|
||||
#[command(flatten)]
|
||||
scope: CliScopeArgs,
|
||||
#[arg(long)]
|
||||
process: Option<String>,
|
||||
#[arg(long)]
|
||||
task: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Subcommand)]
|
||||
enum ArtifactCommands {
|
||||
List(ArtifactListArgs),
|
||||
Download(ArtifactDownloadArgs),
|
||||
Export(ArtifactExportArgs),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Parser)]
|
||||
struct ArtifactListArgs {
|
||||
#[command(flatten)]
|
||||
scope: CliScopeArgs,
|
||||
#[arg(long)]
|
||||
process: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Parser)]
|
||||
struct ArtifactDownloadArgs {
|
||||
#[command(flatten)]
|
||||
scope: CliScopeArgs,
|
||||
artifact: String,
|
||||
/// Write the verified artifact bytes to this local path.
|
||||
#[arg(long)]
|
||||
to: Option<PathBuf>,
|
||||
#[arg(long, default_value_t = 64 * 1024 * 1024)]
|
||||
max_bytes: u64,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Parser)]
|
||||
struct ArtifactExportArgs {
|
||||
#[command(flatten)]
|
||||
scope: CliScopeArgs,
|
||||
artifact: String,
|
||||
#[arg(long)]
|
||||
to: PathBuf,
|
||||
#[arg(long, default_value = "node-local")]
|
||||
receiver_node: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Parser)]
|
||||
struct DapArgs {
|
||||
#[arg(long)]
|
||||
plan: bool,
|
||||
#[arg(long)]
|
||||
json: bool,
|
||||
#[arg(last = true)]
|
||||
args: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Subcommand)]
|
||||
enum DebugCommands {
|
||||
Attach(DebugAttachArgs),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Parser)]
|
||||
struct DebugAttachArgs {
|
||||
#[command(flatten)]
|
||||
scope: CliScopeArgs,
|
||||
#[arg(long, default_value = "vp-current")]
|
||||
process: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Subcommand)]
|
||||
enum QuotaCommands {
|
||||
Status(QuotaStatusArgs),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Parser)]
|
||||
struct QuotaStatusArgs {
|
||||
#[command(flatten)]
|
||||
scope: CliScopeArgs,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Subcommand)]
|
||||
enum AdminCommands {
|
||||
Status(AdminStatusArgs),
|
||||
Bootstrap(AdminBootstrapArgs),
|
||||
RevokeNode(NodeRevokeArgs),
|
||||
StopProcess(ProcessCancelArgs),
|
||||
SuspendTenant(AdminSuspendTenantArgs),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Parser)]
|
||||
struct AdminStatusArgs {
|
||||
#[command(flatten)]
|
||||
scope: CliScopeArgs,
|
||||
#[arg(long)]
|
||||
admin_token: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Parser)]
|
||||
struct AdminBootstrapArgs {
|
||||
#[command(flatten)]
|
||||
scope: CliScopeArgs,
|
||||
#[arg(long, default_value = "Clusterflux Project")]
|
||||
name: String,
|
||||
#[arg(long)]
|
||||
yes: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Parser)]
|
||||
struct AdminSuspendTenantArgs {
|
||||
#[command(flatten)]
|
||||
scope: CliScopeArgs,
|
||||
#[arg(long = "target-tenant")]
|
||||
target_tenant: Option<String>,
|
||||
#[arg(long)]
|
||||
admin_token: Option<String>,
|
||||
#[arg(long)]
|
||||
yes: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Args)]
|
||||
struct CliScopeArgs {
|
||||
#[arg(long)]
|
||||
coordinator: Option<String>,
|
||||
#[arg(long, default_value = "tenant")]
|
||||
tenant: String,
|
||||
#[arg(long = "project-id", default_value = "project")]
|
||||
project: String,
|
||||
#[arg(long, default_value = "user")]
|
||||
user: String,
|
||||
#[arg(long)]
|
||||
json: bool,
|
||||
}
|
||||
|
||||
fn main() {
|
||||
if let Err(error) = dispatch::run_cli() {
|
||||
let message = error.to_string();
|
||||
let machine_error = cli_error_summary(&message);
|
||||
let category = machine_error
|
||||
.get("category")
|
||||
.and_then(Value::as_str)
|
||||
.unwrap_or("unknown");
|
||||
let exit_code = machine_error
|
||||
.get("stable_exit_code")
|
||||
.and_then(Value::as_i64)
|
||||
.and_then(|code| i32::try_from(code).ok())
|
||||
.unwrap_or(1);
|
||||
eprintln!("Error ({category}, exit {exit_code}): {error:#}");
|
||||
std::process::exit(exit_code);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
Loading…
Add table
Add a link
Reference in a new issue