#!/usr/bin/env rust
mod handlers_admin;
mod handlers_bucket;
mod handlers_preprocessing;
mod handlers_replication;
mod handlers_transform;
mod handlers_versioning;
mod types;
use anyhow::{Context, Result};
use clap::{Parser, Subcommand};
use reqwest::Client;
use std::time::Duration;
use types::{
BatchAction, BucketAction, ConfigAction, LifecycleAction, MaintenanceAction, MetricsAction,
ObjectAction, ObservabilityAction, PreprocessingAction, ReplicationAction, ServerInfoAction,
TransformAction, VersioningAction,
};
mod handlers_multipart_gc;
#[derive(Parser)]
#[command(name = "rs3ctl")]
#[command(version, about, long_about = None)]
pub struct Cli {
#[arg(short, long, default_value = "http://localhost:9000")]
pub endpoint: String,
#[arg(short, long)]
pub access_key: Option<String>,
#[arg(short, long)]
pub secret_key: Option<String>,
#[command(subcommand)]
command: Commands,
}
#[derive(Subcommand)]
enum Commands {
Bucket {
#[command(subcommand)]
action: BucketAction,
},
Object {
#[command(subcommand)]
action: ObjectAction,
},
Replication {
#[command(subcommand)]
action: ReplicationAction,
},
Metrics {
#[command(subcommand)]
action: MetricsAction,
},
Maintenance {
#[command(subcommand)]
action: MaintenanceAction,
},
Versioning {
#[command(subcommand)]
action: VersioningAction,
},
Observability {
#[command(subcommand)]
action: ObservabilityAction,
},
Transform {
#[command(subcommand)]
action: TransformAction,
},
Batch {
#[command(subcommand)]
action: BatchAction,
},
Lifecycle {
#[command(subcommand)]
action: LifecycleAction,
},
Preprocessing {
#[command(subcommand)]
action: PreprocessingAction,
},
Health,
Config {
#[command(subcommand)]
action: ConfigAction,
},
ServerInfo {
#[command(subcommand)]
action: ServerInfoAction,
},
Benchmark {
#[arg(long, default_value = "100")]
iterations: u32,
#[arg(long, default_value = "1024")]
size: usize,
#[arg(long)]
bucket: String,
},
Diagnose {
#[arg(long)]
connectivity_only: bool,
},
GcMultipart {
#[arg(short, long)]
bucket: Option<String>,
#[arg(long, default_value = "168")]
retention_hours: u64,
},
ShowMetrics {
#[arg(long)]
filter: Option<String>,
},
}
#[tokio::main]
async fn main() -> Result<()> {
let cli = Cli::parse();
let client = Client::builder()
.timeout(Duration::from_secs(300))
.build()
.context("Failed to create HTTP client")?;
match &cli.command {
Commands::Health => handle_health(&client, &cli.endpoint).await,
Commands::Bucket { action } => {
handlers_bucket::handle_bucket(&client, &cli, action).await
}
Commands::Object { action } => {
handlers_bucket::handle_object(&client, &cli, action).await
}
Commands::Replication { action } => {
handlers_replication::handle_replication(&client, &cli, action).await
}
Commands::Metrics { action } => {
handlers_replication::handle_metrics(&client, &cli, action).await
}
Commands::Maintenance { action } => {
handlers_replication::handle_maintenance(&client, &cli, action).await
}
Commands::Versioning { action } => {
handlers_versioning::handle_versioning(&client, &cli, action).await
}
Commands::Observability { action } => {
handlers_versioning::handle_observability(&client, &cli, action).await
}
Commands::Transform { action } => {
handlers_transform::handle_transform(&client, &cli, action).await
}
Commands::Batch { action } => {
handlers_transform::handle_batch(&client, &cli, action).await
}
Commands::Lifecycle { action } => {
handlers_transform::handle_lifecycle(&client, &cli, action).await
}
Commands::Preprocessing { action } => {
handlers_preprocessing::handle_preprocessing(&client, &cli, action).await
}
Commands::Config { action } => {
handlers_admin::handle_config(&client, &cli, action).await
}
Commands::ServerInfo { action } => {
handlers_admin::handle_server_info(&client, &cli, action).await
}
Commands::Benchmark {
iterations,
size,
bucket,
} => {
handlers_admin::handle_benchmark(&client, &cli, *iterations, *size, bucket).await
}
Commands::Diagnose { connectivity_only } => {
handlers_admin::handle_diagnose(&client, &cli, *connectivity_only).await
}
Commands::GcMultipart {
bucket,
retention_hours,
} => {
handlers_multipart_gc::handle_gc_multipart(
&client,
&cli,
bucket.as_deref(),
*retention_hours,
)
.await
}
Commands::ShowMetrics { filter } => {
handlers_multipart_gc::handle_show_metrics(&client, &cli, filter.as_deref()).await
}
}
}
async fn handle_health(client: &Client, endpoint: &str) -> Result<()> {
let health_url = format!("{}/health", endpoint);
let ready_url = format!("{}/ready", endpoint);
let health_resp = client
.get(&health_url)
.send()
.await
.context("Failed to send health check request")?;
let health_ok = health_resp.status().is_success();
if health_ok {
println!("✓ /health OK ({})", health_resp.status());
let body = health_resp.text().await.unwrap_or_default();
if let Ok(parsed) = serde_json::from_str::<serde_json::Value>(&body) {
println!("{}", serde_json::to_string_pretty(&parsed)?);
} else {
println!("{}", body);
}
} else {
println!("✗ /health FAILED ({})", health_resp.status());
}
let ready_resp = client
.get(&ready_url)
.send()
.await
.context("Failed to send readiness check request")?;
let ready_ok = ready_resp.status().is_success();
if ready_ok {
let status_text = ready_resp.text().await.unwrap_or_default();
println!("✓ /ready OK — {}", status_text.trim());
} else {
println!("✗ /ready FAILED ({})", ready_resp.status());
}
if health_ok && ready_ok {
Ok(())
} else {
Err(anyhow::anyhow!(
"Server health/readiness check failed — see output above"
))
}
}