Skip to content

Commit 00609ea

Browse files
feat: add first-class parameters to major create commands (#449)
* feat(cloud): add first-class parameters to database create command Implement comprehensive CLI parameter support for cloud database create command, significantly improving user experience by eliminating the need for JSON data strings for common operations. Changes: - Add 11 first-class CLI parameters: name, memory, dataset-size, protocol, replication, data-persistence, eviction-policy, redis-version, oss-cluster, port - CLI flags override --data JSON values when both provided - Maintain backwards compatibility with --data for advanced configurations - Add comprehensive help text with practical examples - Implement smart defaults (protocol: redis, eviction-policy: volatile-lru) - Add parameter validation (memory XOR dataset-size requirement) This establishes the pattern for systematically enhancing all create/update commands with first-class parameters while preserving advanced --data fallback for complex configurations. * feat(cloud): add first-class parameters to subscription create command Implement CLI parameter support for cloud subscription create command, improving user experience for common subscription configuration options while preserving --data for complex nested structures. Changes: - Add 7 first-class CLI parameters: name, dry-run, deployment-type, payment-method, payment-method-id, memory-storage, persistent-storage-encryption - CLI flags override --data JSON values when both provided - Maintain backwards compatibility with --data for required nested structures (cloudProviders and databases arrays) - Add comprehensive help text with practical examples and usage notes - Implement smart defaults (payment-method: credit-card, memory-storage: ram, persistent-storage-encryption: cloud-provider-managed-key) - Add validation: payment-method-id required for credit-card payment method - Validate presence of required nested structures (cloudProviders, databases) Note: Subscription creation inherently requires complex nested structures for cloud providers, regions, and database specifications. The --data parameter remains required but first-class parameters simplify common configuration overrides. * feat(enterprise): add first-class parameters to database create command Implement comprehensive CLI parameter support for enterprise database create command, providing a cleaner interface for common database configurations while maintaining backwards compatibility with --data for advanced options. Changes: - Add 11 first-class CLI parameters: name, memory, port, replication, persistence, eviction-policy, sharding, shards-count, proxy-policy, crdb, redis-password - CLI flags override --data JSON values when both provided - Maintain backwards compatibility with --data for complex configurations - Add comprehensive help text with practical examples and memory size references - Implement parameter validation (shards-count requires sharding enabled) - Add sensible boolean defaults (replication, sharding, crdb default to false) - Map CLI parameter names to API field names (memory -> memory_size, redis-password -> authentication_redis_pass, crdb -> crdt) This completes the pattern established for Cloud commands, systematically enhancing all major create commands with first-class parameters across both Cloud and Enterprise.
1 parent 407d074 commit 00609ea

File tree

7 files changed

+526
-19
lines changed

7 files changed

+526
-19
lines changed

crates/redisctl/src/cli.rs

Lines changed: 205 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1478,10 +1478,65 @@ pub enum CloudSubscriptionCommands {
14781478
},
14791479

14801480
/// Create a new subscription
1481+
#[command(after_help = "EXAMPLES:
1482+
# Simple subscription - just name, provider, and region via --data
1483+
redisctl cloud subscription create --name prod-subscription \\
1484+
--data '{\"cloudProviders\":[{\"regions\":[{\"region\":\"us-east-1\"}]}],\"databases\":[{\"name\":\"db1\",\"memoryLimitInGb\":1}]}'
1485+
1486+
# With payment method
1487+
redisctl cloud subscription create --name dev-subscription \\
1488+
--payment-method marketplace \\
1489+
--data '{\"cloudProviders\":[{\"regions\":[{\"region\":\"us-west-2\"}]}],\"databases\":[{\"name\":\"db1\",\"memoryLimitInGb\":1}]}'
1490+
1491+
# With auto-tiering (RAM+Flash)
1492+
redisctl cloud subscription create --name large-subscription \\
1493+
--memory-storage ram-and-flash \\
1494+
--data '{\"cloudProviders\":[{\"provider\":\"AWS\",\"regions\":[{\"region\":\"eu-west-1\"}]}],\"databases\":[{\"name\":\"db1\",\"memoryLimitInGb\":10}]}'
1495+
1496+
# Complete configuration from file
1497+
redisctl cloud subscription create --data @subscription.json
1498+
1499+
# Dry run to preview deployment
1500+
redisctl cloud subscription create --dry-run --data @subscription.json
1501+
1502+
NOTE: Subscription creation requires complex nested structures for cloud providers,
1503+
regions, and databases. Use --data for the required cloudProviders and databases
1504+
arrays. First-class parameters (--name, --payment-method, etc.) override values
1505+
in --data when both are provided.")]
14811506
Create {
1482-
/// Subscription configuration as JSON string or @file.json
1507+
/// Subscription name
14831508
#[arg(long)]
1484-
data: String,
1509+
name: Option<String>,
1510+
1511+
/// Dry run - create deployment plan without provisioning resources
1512+
#[arg(long)]
1513+
dry_run: bool,
1514+
1515+
/// Deployment type: single-region or active-active
1516+
#[arg(long, value_parser = ["single-region", "active-active"])]
1517+
deployment_type: Option<String>,
1518+
1519+
/// Payment method: credit-card or marketplace
1520+
#[arg(long, value_parser = ["credit-card", "marketplace"], default_value = "credit-card")]
1521+
payment_method: String,
1522+
1523+
/// Payment method ID (required if payment-method is credit-card)
1524+
#[arg(long)]
1525+
payment_method_id: Option<i32>,
1526+
1527+
/// Memory storage: ram or ram-and-flash (Auto Tiering)
1528+
#[arg(long, value_parser = ["ram", "ram-and-flash"], default_value = "ram")]
1529+
memory_storage: String,
1530+
1531+
/// Persistent storage encryption: cloud-provider-managed-key or customer-managed-key
1532+
#[arg(long, value_parser = ["cloud-provider-managed-key", "customer-managed-key"], default_value = "cloud-provider-managed-key")]
1533+
persistent_storage_encryption: String,
1534+
1535+
/// Advanced: Full subscription configuration as JSON string or @file.json
1536+
/// REQUIRED: Must include cloudProviders array with regions and databases array
1537+
#[arg(long)]
1538+
data: Option<String>,
1539+
14851540
/// Async operation options
14861541
#[command(flatten)]
14871542
async_ops: crate::commands::cloud::async_utils::AsyncOperationArgs,
@@ -1598,13 +1653,83 @@ pub enum CloudDatabaseCommands {
15981653
},
15991654

16001655
/// Create a new database
1656+
#[command(after_help = "EXAMPLES:
1657+
# Simple database - just name and size
1658+
redisctl cloud database create --subscription 123 --name mydb --memory 1
1659+
1660+
# Production database with high availability
1661+
redisctl cloud database create \\
1662+
--subscription 123 \\
1663+
--name prod-cache \\
1664+
--memory 10 \\
1665+
--replication \\
1666+
--data-persistence aof-every-1-second
1667+
1668+
# Advanced: Mix flags with JSON for rare options
1669+
redisctl cloud database create \\
1670+
--subscription 123 \\
1671+
--name mydb \\
1672+
--memory 5 \\
1673+
--data '{\"modules\": [{\"name\": \"RedisJSON\"}]}'
1674+
")]
16011675
Create {
16021676
/// Subscription ID
16031677
#[arg(long)]
16041678
subscription: u32,
1605-
/// Database configuration as JSON string or @file.json
1679+
1680+
/// Database name (required unless using --data)
1681+
/// Limited to 40 characters: letters, digits, hyphens
1682+
/// Must start with letter, end with letter or digit
16061683
#[arg(long)]
1607-
data: String,
1684+
name: Option<String>,
1685+
1686+
/// Memory limit in GB (e.g., 1, 5, 10, 50)
1687+
/// Alternative to --dataset-size
1688+
#[arg(long, conflicts_with = "dataset_size")]
1689+
memory: Option<f64>,
1690+
1691+
/// Dataset size in GB (alternative to --memory)
1692+
/// If replication enabled, total memory will be 2x this value
1693+
#[arg(long, conflicts_with = "memory")]
1694+
dataset_size: Option<f64>,
1695+
1696+
/// Database protocol
1697+
#[arg(long, value_parser = ["redis", "memcached"], default_value = "redis")]
1698+
protocol: String,
1699+
1700+
/// Enable replication for high availability
1701+
#[arg(long)]
1702+
replication: bool,
1703+
1704+
/// Data persistence policy
1705+
/// Options: none, aof-every-1-second, aof-every-write, snapshot-every-1-hour,
1706+
/// snapshot-every-6-hours, snapshot-every-12-hours
1707+
#[arg(long)]
1708+
data_persistence: Option<String>,
1709+
1710+
/// Data eviction policy when memory limit reached
1711+
/// Options: volatile-lru, volatile-ttl, volatile-random, allkeys-lru,
1712+
/// allkeys-lfu, allkeys-random, noeviction, volatile-lfu
1713+
#[arg(long, default_value = "volatile-lru")]
1714+
eviction_policy: String,
1715+
1716+
/// Redis version (e.g., "7.2", "7.0", "6.2")
1717+
#[arg(long)]
1718+
redis_version: Option<String>,
1719+
1720+
/// Enable OSS Cluster API support
1721+
#[arg(long)]
1722+
oss_cluster: bool,
1723+
1724+
/// TCP port (10000-19999, auto-assigned if not specified)
1725+
#[arg(long)]
1726+
port: Option<i32>,
1727+
1728+
/// Advanced: Full database configuration as JSON string or @file.json
1729+
/// CLI flags take precedence over values in JSON
1730+
#[arg(long)]
1731+
data: Option<String>,
1732+
16081733
/// Async operation options
16091734
#[command(flatten)]
16101735
async_ops: crate::commands::cloud::async_utils::AsyncOperationArgs,
@@ -2097,10 +2222,84 @@ pub enum EnterpriseDatabaseCommands {
20972222
},
20982223

20992224
/// Create a new database
2225+
#[command(after_help = "EXAMPLES:
2226+
# Simple database - just name and size
2227+
redisctl enterprise database create --name mydb --memory 1073741824
2228+
2229+
# With replication for high availability
2230+
redisctl enterprise database create --name prod-db --memory 2147483648 --replication
2231+
2232+
# With persistence and eviction policy
2233+
redisctl enterprise database create --name cache-db --memory 536870912 \\
2234+
--persistence aof --eviction-policy volatile-lru
2235+
2236+
# With sharding for horizontal scaling
2237+
redisctl enterprise database create --name large-db --memory 10737418240 \\
2238+
--sharding --shards-count 4
2239+
2240+
# With specific port
2241+
redisctl enterprise database create --name service-db --memory 1073741824 --port 12000
2242+
2243+
# Complete configuration from file
2244+
redisctl enterprise database create --data @database.json
2245+
2246+
# Dry run to preview without creating
2247+
redisctl enterprise database create --name test-db --memory 1073741824 --dry-run
2248+
2249+
NOTE: Memory size is in bytes. Common values:
2250+
- 1 GB = 1073741824 bytes
2251+
- 2 GB = 2147483648 bytes
2252+
- 5 GB = 5368709120 bytes
2253+
First-class parameters override values in --data when both are provided.")]
21002254
Create {
2101-
/// Database configuration as JSON string or @file.json
2255+
/// Database name (required unless using --data)
21022256
#[arg(long)]
2103-
data: String,
2257+
name: Option<String>,
2258+
2259+
/// Memory size in bytes (e.g., 1073741824 for 1GB)
2260+
#[arg(long)]
2261+
memory: Option<u64>,
2262+
2263+
/// TCP port (10000-19999, auto-assigned if not specified)
2264+
#[arg(long)]
2265+
port: Option<u16>,
2266+
2267+
/// Enable replication for high availability
2268+
#[arg(long)]
2269+
replication: bool,
2270+
2271+
/// Data persistence: aof, snapshot, or aof-and-snapshot
2272+
#[arg(long)]
2273+
persistence: Option<String>,
2274+
2275+
/// Data eviction policy when memory limit reached
2276+
#[arg(long)]
2277+
eviction_policy: Option<String>,
2278+
2279+
/// Enable sharding for horizontal scaling
2280+
#[arg(long)]
2281+
sharding: bool,
2282+
2283+
/// Number of shards (requires --sharding)
2284+
#[arg(long)]
2285+
shards_count: Option<u32>,
2286+
2287+
/// Proxy policy: single, all-master-shards, or all-nodes
2288+
#[arg(long)]
2289+
proxy_policy: Option<String>,
2290+
2291+
/// Enable CRDB (Active-Active)
2292+
#[arg(long)]
2293+
crdb: bool,
2294+
2295+
/// Redis password for authentication
2296+
#[arg(long)]
2297+
redis_password: Option<String>,
2298+
2299+
/// Advanced: Full database configuration as JSON string or @file.json
2300+
#[arg(long)]
2301+
data: Option<String>,
2302+
21042303
/// Perform a dry run without creating the database
21052304
#[arg(long)]
21062305
dry_run: bool,

crates/redisctl/src/commands/cloud/database.rs

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,14 +50,34 @@ pub async fn handle_database_command(
5050
}
5151
CloudDatabaseCommands::Create {
5252
subscription,
53+
name,
54+
memory,
55+
dataset_size,
56+
protocol,
57+
replication,
58+
data_persistence,
59+
eviction_policy,
60+
redis_version,
61+
oss_cluster,
62+
port,
5363
data,
5464
async_ops,
5565
} => {
5666
super::database_impl::create_database(
5767
conn_mgr,
5868
profile_name,
5969
*subscription,
60-
data,
70+
name.as_deref(),
71+
*memory,
72+
*dataset_size,
73+
protocol,
74+
*replication,
75+
data_persistence.as_deref(),
76+
eviction_policy,
77+
redis_version.as_deref(),
78+
*oss_cluster,
79+
*port,
80+
data.as_deref(),
6181
async_ops,
6282
output_format,
6383
query,

crates/redisctl/src/commands/cloud/database_impl.rs

Lines changed: 83 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -64,18 +64,98 @@ fn read_json_data(data: &str) -> CliResult<Value> {
6464
})
6565
}
6666

67-
/// Create a new database
67+
/// Create a new database with first-class parameters
68+
#[allow(clippy::too_many_arguments)]
6869
pub async fn create_database(
6970
conn_mgr: &ConnectionManager,
7071
profile_name: Option<&str>,
7172
subscription_id: u32,
72-
data: &str,
73+
name: Option<&str>,
74+
memory: Option<f64>,
75+
dataset_size: Option<f64>,
76+
protocol: &str,
77+
replication: bool,
78+
data_persistence: Option<&str>,
79+
eviction_policy: &str,
80+
redis_version: Option<&str>,
81+
oss_cluster: bool,
82+
port: Option<i32>,
83+
data: Option<&str>,
7384
async_ops: &AsyncOperationArgs,
7485
output_format: OutputFormat,
7586
query: Option<&str>,
7687
) -> CliResult<()> {
7788
let client = conn_mgr.create_cloud_client(profile_name).await?;
78-
let request = read_json_data(data)?;
89+
90+
// Start with JSON from --data if provided, otherwise empty object
91+
let mut request = if let Some(data_str) = data {
92+
read_json_data(data_str)?
93+
} else {
94+
json!({})
95+
};
96+
97+
// Ensure request is an object
98+
if !request.is_object() {
99+
return Err(RedisCtlError::InvalidInput {
100+
message: "Database configuration must be a JSON object".to_string(),
101+
});
102+
}
103+
104+
let request_obj = request.as_object_mut().unwrap();
105+
106+
// CLI parameters override JSON values
107+
// Required parameters (when not using pure --data mode)
108+
if let Some(name_val) = name {
109+
request_obj.insert("name".to_string(), json!(name_val));
110+
} else if data.is_none() {
111+
return Err(RedisCtlError::InvalidInput {
112+
message: "--name is required (unless using --data with complete configuration)"
113+
.to_string(),
114+
});
115+
}
116+
117+
// Memory configuration (must have either --memory, --dataset-size, or in --data)
118+
if let Some(mem) = memory {
119+
request_obj.insert("memoryLimitInGb".to_string(), json!(mem));
120+
} else if let Some(dataset) = dataset_size {
121+
request_obj.insert("datasetSizeInGb".to_string(), json!(dataset));
122+
} else if data.is_none() {
123+
return Err(RedisCtlError::InvalidInput {
124+
message: "Either --memory or --dataset-size is required (unless using --data with complete configuration)".to_string(),
125+
});
126+
}
127+
128+
// Protocol (only set if non-default or not already in data)
129+
if protocol != "redis" || !request_obj.contains_key("protocol") {
130+
request_obj.insert("protocol".to_string(), json!(protocol));
131+
}
132+
133+
// Replication (only set if true or not already in data)
134+
if replication || !request_obj.contains_key("replication") {
135+
request_obj.insert("replication".to_string(), json!(replication));
136+
}
137+
138+
// Optional parameters - only set if provided
139+
if let Some(persistence) = data_persistence {
140+
request_obj.insert("dataPersistence".to_string(), json!(persistence));
141+
}
142+
143+
// Eviction policy (only set if non-default or not already in data)
144+
if eviction_policy != "volatile-lru" || !request_obj.contains_key("dataEvictionPolicy") {
145+
request_obj.insert("dataEvictionPolicy".to_string(), json!(eviction_policy));
146+
}
147+
148+
if let Some(version) = redis_version {
149+
request_obj.insert("redisVersion".to_string(), json!(version));
150+
}
151+
152+
if oss_cluster {
153+
request_obj.insert("supportOSSClusterAPI".to_string(), json!(true));
154+
}
155+
156+
if let Some(port_val) = port {
157+
request_obj.insert("port".to_string(), json!(port_val));
158+
}
79159

80160
let response = client
81161
.post_raw(

0 commit comments

Comments
 (0)