use clap::{Parser, Subcommand, ValueHint};
use clap_complete::engine::{ArgValueCandidates, CompletionCandidate};
use redisctl_core::DeploymentType;
pub mod cloud;
pub mod enterprise;
pub use cloud::*;
pub use enterprise::*;
#[derive(Parser, Debug)]
#[command(name = "redisctl")]
#[command(
version,
about = "Redis management CLI for Cloud and Enterprise deployments"
)]
#[command(long_about = "
Redis management CLI for Cloud and Enterprise deployments
Commands infer platform from your profile — no prefix needed:
redisctl database list # uses your configured profile
redisctl subscription list # cloud-only, no prefix needed
redisctl cluster get # enterprise-only, no prefix needed
Or be explicit:
redisctl cloud database list
redisctl enterprise database list
PROFILE TYPES:
redisctl uses profiles to store connection credentials. Each profile has a
type that determines which commands it can be used with:
cloud Redis Cloud API credentials (api-key + api-secret).
Unlocks: cloud, subscription, database list, task, acl, ...
enterprise Redis Enterprise cluster credentials (url + username).
Unlocks: enterprise, cluster, node, database list, shard, ...
database Direct Redis connection (host + port).
Unlocks: db open (launches redis-cli with saved credentials)
Create a profile with: redisctl profile set <name> --type <type> ...
See all profiles: redisctl profile list
Full setup help: redisctl profile set --help
EXAMPLES:
# Set up a Cloud profile
redisctl profile set mycloud --type cloud --api-key KEY --api-secret SECRET
# Set up an Enterprise profile
redisctl profile set myenterprise --type enterprise --url https://cluster:9443 --username admin
# Set up a Database profile (direct Redis connection)
redisctl profile set mycache --type database --host localhost --port 6379
# Get JSON output for scripting
redisctl subscription list -o json
# Filter output with JMESPath
redisctl database list -q 'databases[?status==`active`]'
# Filter with a query from a file
redisctl cloud sub list -q @queries/active-dbs.jmespath
# Direct API access
redisctl api cloud get /subscriptions
redisctl api enterprise get /v1/cluster
For more help on a specific command, run:
redisctl <command> --help
")]
pub struct Cli {
#[arg(long, short, global = true, env = "REDISCTL_PROFILE", add = ArgValueCandidates::new(profile_candidates))]
pub profile: Option<String>,
#[arg(long, global = true, env = "REDISCTL_CONFIG_FILE", value_hint = ValueHint::FilePath)]
pub config_file: Option<String>,
#[arg(long, short = 'o', global = true, value_enum, default_value = "auto")]
pub output: OutputFormat,
#[arg(long, short = 'q', global = true)]
pub query: Option<String>,
#[arg(long, short, global = true, action = clap::ArgAction::Count)]
pub verbose: u8,
#[arg(long, global = true)]
pub no_resilience: bool,
#[arg(long, global = true)]
pub no_circuit_breaker: bool,
#[arg(long, global = true)]
pub no_retry: bool,
#[arg(long, global = true)]
pub retry_attempts: Option<u32>,
#[arg(long, global = true)]
pub rate_limit: Option<u32>,
#[command(subcommand)]
pub command: Commands,
}
#[derive(Debug, Clone, Copy, clap::ValueEnum)]
pub enum OutputFormat {
Auto,
Json,
Yaml,
Table,
}
impl OutputFormat {
pub fn is_json(&self) -> bool {
matches!(self, Self::Json)
}
pub fn is_yaml(&self) -> bool {
matches!(self, Self::Yaml)
}
}
#[derive(Subcommand, Debug)]
pub enum Commands {
#[command(name = "api")]
#[command(after_help = "EXAMPLES:
# GET request to Cloud API
redisctl api cloud get /subscriptions
# GET request to Enterprise API
redisctl api enterprise get /v1/cluster
# POST request with JSON data
redisctl api cloud post /subscriptions --data '{\"name\":\"my-sub\"}'
# POST request from file
redisctl api cloud post /subscriptions --data @subscription.json
# Output as JSON for scripting
redisctl api enterprise get /v1/bdbs -o json
")]
Api {
#[arg(value_enum)]
deployment: DeploymentType,
#[arg(value_parser = parse_http_method)]
method: HttpMethod,
path: String,
#[arg(long)]
data: Option<String>,
#[arg(long)]
curl: bool,
},
#[command(subcommand, visible_alias = "prof", visible_alias = "pr")]
#[command(after_help = "EXAMPLES:
# Create a Cloud profile
redisctl profile set mycloud --type cloud --api-key KEY --api-secret SECRET
# Create an Enterprise profile
redisctl profile set myenterprise --type enterprise --url https://cluster:9443 --username admin
# Create a Database profile
redisctl profile set mycache --type database --host localhost --port 6379
# List all profiles
redisctl profile list
# Show profile details
redisctl profile show mycloud
# Validate configuration
redisctl profile validate
# Set default profiles
redisctl profile default-cloud mycloud
redisctl profile default-enterprise myenterprise
redisctl profile default-database mycache
")]
Profile(ProfileCommands),
#[command(subcommand, visible_alias = "cl")]
#[command(before_long_help = "\
COMMAND GROUPS:
Core: database (db), subscription (sub), fixed-database (fixed-db),
fixed-subscription (fixed-sub)
Access: user, acl
Billing: account (acct), payment-method, cost-report
Networking: connectivity (conn), provider-account
Operations: task, workflow")]
#[command(after_help = "\
PROFILE REQUIREMENT:
These commands require a 'cloud' profile. Set one up with:
redisctl profile set <name> --type cloud --api-key <KEY> --api-secret <SECRET>
List existing profiles: redisctl profile list")]
Cloud(CloudCommands),
#[command(subcommand, visible_alias = "ent", visible_alias = "en")]
#[command(before_long_help = "\
COMMAND GROUPS:
Core: database (db), cluster, node, shard, endpoint
Access: user, role, acl, ldap, ldap-mappings, auth
Monitoring: stats, status, alerts (alert), logs (log), diagnostics (diag),
debug-info
Admin: license (lic), module, proxy, services (svc), cm-settings, suffix
Advanced: crdb, crdb-task, bdb-group, migration, bootstrap, job-scheduler
Troubleshoot: support-package, ocsp, usage-report, local
Other: action, jsonschema, workflow")]
#[command(after_help = "\
PROFILE REQUIREMENT:
These commands require an 'enterprise' profile. Set one up with:
redisctl profile set <name> --type enterprise --url <URL> --username <USER>
List existing profiles: redisctl profile list")]
Enterprise(EnterpriseCommands),
#[command(subcommand, visible_alias = "fk")]
FilesKey(FilesKeyCommands),
#[command(subcommand)]
#[command(after_help = "\
PROFILE REQUIREMENT:
These commands require a 'database' profile. Set one up with:
redisctl profile set <name> --type database --host <HOST> --port <PORT>
List existing profiles: redisctl profile list")]
Db(DbCommands),
#[command(visible_alias = "ver", visible_alias = "v")]
Version,
#[command(visible_alias = "comp")]
Completions {
#[arg(value_enum)]
shell: Shell,
#[arg(long)]
register: bool,
},
}
#[derive(Debug, Clone, Copy, clap::ValueEnum)]
#[allow(clippy::enum_variant_names)]
pub enum Shell {
Bash,
Zsh,
Fish,
#[value(name = "powershell", alias = "power-shell")]
PowerShell,
Elvish,
}
#[derive(Debug, Clone)]
pub enum HttpMethod {
Get,
Post,
Put,
Patch,
Delete,
}
fn parse_http_method(s: &str) -> Result<HttpMethod, String> {
match s.to_lowercase().as_str() {
"get" => Ok(HttpMethod::Get),
"post" => Ok(HttpMethod::Post),
"put" => Ok(HttpMethod::Put),
"patch" => Ok(HttpMethod::Patch),
"delete" => Ok(HttpMethod::Delete),
_ => Err(format!(
"invalid HTTP method: {} (valid: get, post, put, patch, delete)",
s
)),
}
}
impl std::fmt::Display for HttpMethod {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
HttpMethod::Get => write!(f, "GET"),
HttpMethod::Post => write!(f, "POST"),
HttpMethod::Put => write!(f, "PUT"),
HttpMethod::Patch => write!(f, "PATCH"),
HttpMethod::Delete => write!(f, "DELETE"),
}
}
}
#[derive(Subcommand, Debug)]
#[allow(clippy::large_enum_variant)]
pub enum ProfileCommands {
#[command(visible_alias = "ls", visible_alias = "l")]
List {
#[arg(long = "tag", action = clap::ArgAction::Append)]
tags: Vec<String>,
},
Path,
#[command(visible_alias = "active")]
#[command(
after_help = "Prints the profile that would be used for the given deployment type.
Useful for embedding in your shell prompt (PS1).
EXAMPLES:
# Show active cloud profile
redisctl profile current --type cloud
# Show active enterprise profile
redisctl profile current --type enterprise
# Use in shell prompt (bash)
PS1='[\\$(redisctl profile current --type cloud 2>/dev/null)]\\$ '"
)]
Current {
#[arg(long, value_enum)]
r#type: DeploymentType,
},
#[command(visible_alias = "sh", visible_alias = "get")]
Show {
name: String,
},
#[command(visible_alias = "add", visible_alias = "create")]
#[command(after_help = "CHOOSING A PROFILE TYPE:
cloud -- You manage databases through the Redis Cloud console/API.
Requires an API key and secret from cloud.redis.io.
enterprise -- You run Redis Enterprise Software on your own infrastructure.
Requires the cluster URL and admin credentials.
database -- You want to connect directly to a Redis instance (any provider).
Requires host, port, and optionally a password.
EXAMPLES:
# Create a Cloud profile
redisctl profile set mycloud --type cloud \\
--api-key A3qcymrvqpn9rrgdt40sv5f9yfxob26vx64hwddh8vminqnkgfq \\
--api-secret S3s8ecrrnaguqkvwfvealoe3sn25zqs4wc4lwgo4rb0ud3qm77c
# Create an Enterprise profile (password will be prompted)
redisctl profile set prod --type enterprise \\
--url https://cluster.example.com:9443 \\
--username admin@example.com
# Create Enterprise profile with password
redisctl profile set staging --type enterprise \\
--url https://staging:9443 \\
--username admin \\
--password mypassword
# Create Enterprise profile allowing insecure connections
redisctl profile set local --type enterprise \\
--url https://localhost:9443 \\
--username admin@redis.local \\
--insecure
# Create a Database profile (direct Redis connection)
redisctl profile set my-cache --type database \\
--host redis-12345.cloud.redislabs.com \\
--port 12345 \\
--password mypassword
# Create Database profile without TLS (local dev)
redisctl profile set local-redis --type database \\
--host localhost \\
--port 6379 \\
--no-tls
# Tag a profile for organization
redisctl profile set prod-cloud --type cloud \\
--api-key KEY --api-secret SECRET \\
--tag prod --tag us-east
")]
Set {
name: String,
#[arg(long, value_enum, visible_alias = "deployment")]
r#type: DeploymentType,
#[arg(long, required_if_eq("type", "cloud"))]
api_key: Option<String>,
#[arg(long, required_if_eq("type", "cloud"))]
api_secret: Option<String>,
#[arg(long, default_value = "https://api.redislabs.com/v1", value_hint = ValueHint::Url)]
api_url: String,
#[arg(long, required_if_eq("type", "enterprise"), value_hint = ValueHint::Url)]
url: Option<String>,
#[arg(long, required_if_eq("type", "enterprise"))]
username: Option<String>,
#[arg(long)]
password: Option<String>,
#[arg(long)]
insecure: bool,
#[arg(long, value_hint = ValueHint::FilePath)]
ca_cert: Option<String>,
#[arg(long, required_if_eq("type", "database"))]
host: Option<String>,
#[arg(long, required_if_eq("type", "database"))]
port: Option<u16>,
#[arg(long)]
no_tls: bool,
#[arg(long)]
db: Option<u8>,
#[cfg(feature = "secure-storage")]
#[arg(long)]
use_keyring: bool,
#[arg(long = "tag", action = clap::ArgAction::Append)]
tags: Vec<String>,
},
#[command(visible_alias = "rm", visible_alias = "del", visible_alias = "delete")]
Remove {
name: String,
},
#[command(name = "default-enterprise", visible_alias = "def-ent")]
DefaultEnterprise {
name: String,
},
#[command(name = "default-cloud", visible_alias = "def-cloud")]
DefaultCloud {
name: String,
},
#[command(name = "default-database", visible_alias = "def-db")]
DefaultDatabase {
name: String,
},
#[command(visible_alias = "check")]
#[command(after_help = "EXAMPLES:
# Validate all profiles and configuration
redisctl profile validate
# Validate and test connectivity to all profiles
redisctl profile validate --connect
# Machine-readable validation output
redisctl profile validate --connect -o json
# Example output:
# Configuration file: /Users/user/.config/redisctl/config.toml
# ✓ Configuration file exists and is readable
# ✓ Found 2 profile(s)
#
# Profile 'mycloud' (cloud): ✓ Valid
# Profile 'myenterprise' (enterprise): ✓ Valid
#
# ✓ Default enterprise profile: myenterprise
# ✓ Default cloud profile: mycloud
#
# ✓ Configuration is valid
")]
Validate {
#[arg(long, short = 'c')]
connect: bool,
},
#[command(visible_alias = "setup")]
#[command(after_help = "Walks you through creating a profile step by step.
Prompts for the profile type, name, and required credentials.
Optionally tests connectivity before saving.")]
Init,
}
#[derive(Subcommand, Debug)]
pub enum FilesKeyCommands {
#[command(visible_alias = "add")]
Set {
api_key: String,
#[cfg(feature = "secure-storage")]
#[arg(long)]
use_keyring: bool,
#[arg(long, conflicts_with = "use_keyring")]
global: bool,
#[arg(long, conflicts_with_all = ["use_keyring", "global"])]
profile: Option<String>,
},
#[command(visible_alias = "show")]
Get {
#[arg(long)]
profile: Option<String>,
},
#[command(visible_alias = "rm", visible_alias = "delete")]
Remove {
#[cfg(feature = "secure-storage")]
#[arg(long)]
keyring: bool,
#[arg(long, conflicts_with = "keyring")]
global: bool,
#[arg(long, conflicts_with_all = ["keyring", "global"])]
profile: Option<String>,
},
}
#[derive(Subcommand, Debug)]
pub enum DbCommands {
#[command(visible_alias = "connect", visible_alias = "cli")]
#[command(after_help = "EXAMPLES:
# Open redis-cli using a database profile
redisctl db open --profile my-cache
# Print the command without executing (for debugging)
redisctl db open --profile my-cache --dry-run
# Pass additional arguments to redis-cli
redisctl db open --profile my-cache -- -n 1
# Use a specific redis-cli binary
redisctl db open --profile my-cache --redis-cli /usr/local/bin/redis-cli
")]
Open {
#[arg(long, short)]
profile: String,
#[arg(long)]
dry_run: bool,
#[arg(long, default_value = "redis-cli", value_hint = ValueHint::ExecutablePath)]
redis_cli: String,
#[arg(last = true)]
args: Vec<String>,
},
}
fn profile_candidates() -> Vec<CompletionCandidate> {
let config = redisctl_core::Config::load().unwrap_or_default();
config
.list_profiles()
.into_iter()
.map(|(name, profile)| {
CompletionCandidate::new(name.as_str())
.help(Some(format!("{}", profile.deployment_type).into()))
})
.collect()
}