Skip to content

Commit 3918be6

Browse files
feat(enterprise): implement remaining enterprise commands for complete API coverage (#309)
* feat(enterprise): implement remaining enterprise commands Adds the final 5 enterprise commands to achieve complete API coverage: - Bootstrap commands (#301) - Cluster initialization and setup - status: Get bootstrap status with node info - create-cluster: Bootstrap new cluster - join-cluster: Join existing cluster - validate: Validate bootstrap configuration - Debug info commands (#302) - Diagnostic data collection - all: Collect all debug info - node: Collect node-specific debug info - database: Collect database-specific debug info - LDAP commands (#303) - Enterprise authentication integration - ldap get/update/delete/test: LDAP configuration management - ldap-mappings CRUD: LDAP role mapping operations - OCSP commands (#305) - Certificate validation - get/update/status/test: OCSP configuration management - enable/disable: Quick toggle for OCSP validation - Service commands (#306) - Internal service management - list/get/update: Service configuration - restart/status: Service lifecycle management - enable/disable: Service state control All commands support standard output formats (JSON/YAML/Table) and JMESPath queries. Closes #301, #302, #303, #305, #306 * fix: resolve clippy dead code warnings in new enterprise commands - Add #[allow(dead_code)] annotations to prevent clippy warnings - Applied to bootstrap, debuginfo, ldap, ocsp, and services modules - Ensures CI pipeline passes for PR #309
1 parent dac469e commit 3918be6

File tree

8 files changed

+1306
-3
lines changed

8 files changed

+1306
-3
lines changed

crates/redisctl/src/cli.rs

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -979,6 +979,10 @@ pub enum EnterpriseCommands {
979979
#[command(subcommand)]
980980
Database(EnterpriseDatabaseCommands),
981981

982+
/// Debug info collection
983+
#[command(subcommand)]
984+
DebugInfo(crate::commands::enterprise::debuginfo::DebugInfoCommands),
985+
982986
/// Diagnostics operations
983987
#[command(subcommand)]
984988
Diagnostics(crate::commands::enterprise::diagnostics::DiagnosticsCommands),
@@ -1009,11 +1013,18 @@ pub enum EnterpriseCommands {
10091013

10101014
/// LDAP integration
10111015
#[command(subcommand)]
1012-
Ldap(EnterpriseLdapCommands),
1016+
Ldap(crate::commands::enterprise::ldap::LdapCommands),
1017+
1018+
/// LDAP mappings management
1019+
#[command(subcommand, name = "ldap-mappings")]
1020+
LdapMappings(crate::commands::enterprise::ldap::LdapMappingsCommands),
10131021

10141022
/// Authentication & sessions
10151023
#[command(subcommand)]
10161024
Auth(EnterpriseAuthCommands),
1025+
/// Bootstrap and initialization operations
1026+
#[command(subcommand)]
1027+
Bootstrap(crate::commands::enterprise::bootstrap::BootstrapCommands),
10171028

10181029
/// Active-Active database (CRDB) operations
10191030
#[command(subcommand)]
@@ -1045,6 +1056,14 @@ pub enum EnterpriseCommands {
10451056
#[command(subcommand)]
10461057
Module(crate::commands::enterprise::module::ModuleCommands),
10471058

1059+
/// OCSP certificate validation
1060+
#[command(subcommand)]
1061+
Ocsp(crate::commands::enterprise::ocsp::OcspCommands),
1062+
1063+
/// Service management
1064+
#[command(subcommand)]
1065+
Services(crate::commands::enterprise::services::ServicesCommands),
1066+
10481067
/// Workflow operations for multi-step tasks
10491068
#[command(subcommand)]
10501069
Workflow(EnterpriseWorkflowCommands),
Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
use crate::cli::OutputFormat;
2+
use crate::commands::enterprise::utils;
3+
use crate::connection::ConnectionManager;
4+
use crate::error::RedisCtlError;
5+
use anyhow::Context;
6+
use clap::Subcommand;
7+
use serde_json::Value;
8+
9+
#[derive(Debug, Clone, Subcommand)]
10+
pub enum BootstrapCommands {
11+
/// Get bootstrap status
12+
Status,
13+
14+
/// Bootstrap new cluster
15+
#[command(name = "create-cluster")]
16+
CreateCluster {
17+
/// JSON data for cluster creation
18+
#[arg(long, required = true)]
19+
data: String,
20+
},
21+
22+
/// Join existing cluster
23+
#[command(name = "join-cluster")]
24+
JoinCluster {
25+
/// JSON data for joining cluster
26+
#[arg(long, required = true)]
27+
data: String,
28+
},
29+
30+
/// Validate bootstrap configuration
31+
Validate {
32+
/// Action to validate (create_cluster, join_cluster)
33+
action: String,
34+
35+
/// JSON data to validate
36+
#[arg(long, required = true)]
37+
data: String,
38+
},
39+
}
40+
41+
#[allow(dead_code)]
42+
pub async fn handle_bootstrap_command(
43+
conn_mgr: &ConnectionManager,
44+
profile_name: Option<&str>,
45+
cmd: BootstrapCommands,
46+
output_format: OutputFormat,
47+
query: Option<&str>,
48+
) -> Result<(), RedisCtlError> {
49+
match cmd {
50+
BootstrapCommands::Status => {
51+
handle_bootstrap_status(conn_mgr, profile_name, output_format, query).await
52+
}
53+
BootstrapCommands::CreateCluster { data } => {
54+
handle_create_cluster(conn_mgr, profile_name, &data, output_format, query).await
55+
}
56+
BootstrapCommands::JoinCluster { data } => {
57+
handle_join_cluster(conn_mgr, profile_name, &data, output_format, query).await
58+
}
59+
BootstrapCommands::Validate { action, data } => {
60+
handle_validate_bootstrap(conn_mgr, profile_name, &action, &data, output_format, query)
61+
.await
62+
}
63+
}
64+
}
65+
66+
#[allow(dead_code)]
67+
async fn handle_bootstrap_status(
68+
conn_mgr: &ConnectionManager,
69+
profile_name: Option<&str>,
70+
output_format: OutputFormat,
71+
query: Option<&str>,
72+
) -> Result<(), RedisCtlError> {
73+
let client = conn_mgr.create_enterprise_client(profile_name).await?;
74+
75+
let response = client
76+
.get::<Value>("/v1/bootstrap")
77+
.await
78+
.context("Failed to get bootstrap status")?;
79+
80+
let result = if let Some(q) = query {
81+
utils::apply_jmespath(&response, q)?
82+
} else {
83+
response
84+
};
85+
86+
utils::print_formatted_output(result, output_format)
87+
}
88+
89+
#[allow(dead_code)]
90+
async fn handle_create_cluster(
91+
conn_mgr: &ConnectionManager,
92+
profile_name: Option<&str>,
93+
data: &str,
94+
output_format: OutputFormat,
95+
query: Option<&str>,
96+
) -> Result<(), RedisCtlError> {
97+
let client = conn_mgr.create_enterprise_client(profile_name).await?;
98+
99+
let payload: Value =
100+
serde_json::from_str(data).context("Invalid JSON data for cluster creation")?;
101+
102+
let response = client
103+
.post_raw("/v1/bootstrap/create_cluster", payload)
104+
.await
105+
.context("Failed to create cluster")?;
106+
107+
let result = if let Some(q) = query {
108+
utils::apply_jmespath(&response, q)?
109+
} else {
110+
response
111+
};
112+
113+
utils::print_formatted_output(result, output_format)
114+
}
115+
116+
#[allow(dead_code)]
117+
async fn handle_join_cluster(
118+
conn_mgr: &ConnectionManager,
119+
profile_name: Option<&str>,
120+
data: &str,
121+
output_format: OutputFormat,
122+
query: Option<&str>,
123+
) -> Result<(), RedisCtlError> {
124+
let client = conn_mgr.create_enterprise_client(profile_name).await?;
125+
126+
let payload: Value =
127+
serde_json::from_str(data).context("Invalid JSON data for joining cluster")?;
128+
129+
let response = client
130+
.post_raw("/v1/bootstrap/join_cluster", payload)
131+
.await
132+
.context("Failed to join cluster")?;
133+
134+
let result = if let Some(q) = query {
135+
utils::apply_jmespath(&response, q)?
136+
} else {
137+
response
138+
};
139+
140+
utils::print_formatted_output(result, output_format)
141+
}
142+
143+
#[allow(dead_code)]
144+
async fn handle_validate_bootstrap(
145+
conn_mgr: &ConnectionManager,
146+
profile_name: Option<&str>,
147+
action: &str,
148+
data: &str,
149+
output_format: OutputFormat,
150+
query: Option<&str>,
151+
) -> Result<(), RedisCtlError> {
152+
let client = conn_mgr.create_enterprise_client(profile_name).await?;
153+
154+
let payload: Value = serde_json::from_str(data).context("Invalid JSON data for validation")?;
155+
156+
let endpoint = format!("/v1/bootstrap/validate/{}", action);
157+
let response = client
158+
.post_raw(&endpoint, payload)
159+
.await
160+
.context(format!("Failed to validate {} configuration", action))?;
161+
162+
let result = if let Some(q) = query {
163+
utils::apply_jmespath(&response, q)?
164+
} else {
165+
response
166+
};
167+
168+
utils::print_formatted_output(result, output_format)
169+
}
170+
171+
#[cfg(test)]
172+
mod tests {
173+
use super::*;
174+
175+
#[test]
176+
fn test_bootstrap_commands() {
177+
use clap::CommandFactory;
178+
179+
#[derive(clap::Parser)]
180+
struct TestCli {
181+
#[command(subcommand)]
182+
cmd: BootstrapCommands,
183+
}
184+
185+
TestCli::command().debug_assert();
186+
}
187+
}
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
use crate::cli::OutputFormat;
2+
use crate::commands::enterprise::utils;
3+
use crate::connection::ConnectionManager;
4+
use crate::error::RedisCtlError;
5+
use anyhow::Context;
6+
use clap::Subcommand;
7+
use serde_json::Value;
8+
9+
#[derive(Debug, Clone, Subcommand)]
10+
pub enum DebugInfoCommands {
11+
/// Collect all debug info
12+
All,
13+
14+
/// Collect node debug info
15+
Node,
16+
17+
/// Collect database-specific debug info
18+
Database {
19+
/// Database UID
20+
bdb_uid: u64,
21+
},
22+
}
23+
24+
#[allow(dead_code)]
25+
pub async fn handle_debuginfo_command(
26+
conn_mgr: &ConnectionManager,
27+
profile_name: Option<&str>,
28+
cmd: DebugInfoCommands,
29+
output_format: OutputFormat,
30+
query: Option<&str>,
31+
) -> Result<(), RedisCtlError> {
32+
match cmd {
33+
DebugInfoCommands::All => {
34+
handle_debuginfo_all(conn_mgr, profile_name, output_format, query).await
35+
}
36+
DebugInfoCommands::Node => {
37+
handle_debuginfo_node(conn_mgr, profile_name, output_format, query).await
38+
}
39+
DebugInfoCommands::Database { bdb_uid } => {
40+
handle_debuginfo_database(conn_mgr, profile_name, bdb_uid, output_format, query).await
41+
}
42+
}
43+
}
44+
45+
#[allow(dead_code)]
46+
async fn handle_debuginfo_all(
47+
conn_mgr: &ConnectionManager,
48+
profile_name: Option<&str>,
49+
output_format: OutputFormat,
50+
query: Option<&str>,
51+
) -> Result<(), RedisCtlError> {
52+
let client = conn_mgr.create_enterprise_client(profile_name).await?;
53+
54+
let response = client
55+
.get::<Value>("/v1/debuginfo/all")
56+
.await
57+
.context("Failed to collect all debug info")?;
58+
59+
let result = if let Some(q) = query {
60+
utils::apply_jmespath(&response, q)?
61+
} else {
62+
response
63+
};
64+
65+
utils::print_formatted_output(result, output_format)
66+
}
67+
68+
#[allow(dead_code)]
69+
async fn handle_debuginfo_node(
70+
conn_mgr: &ConnectionManager,
71+
profile_name: Option<&str>,
72+
output_format: OutputFormat,
73+
query: Option<&str>,
74+
) -> Result<(), RedisCtlError> {
75+
let client = conn_mgr.create_enterprise_client(profile_name).await?;
76+
77+
let response = client
78+
.get::<Value>("/v1/debuginfo/node")
79+
.await
80+
.context("Failed to collect node debug info")?;
81+
82+
let result = if let Some(q) = query {
83+
utils::apply_jmespath(&response, q)?
84+
} else {
85+
response
86+
};
87+
88+
utils::print_formatted_output(result, output_format)
89+
}
90+
91+
#[allow(dead_code)]
92+
async fn handle_debuginfo_database(
93+
conn_mgr: &ConnectionManager,
94+
profile_name: Option<&str>,
95+
bdb_uid: u64,
96+
output_format: OutputFormat,
97+
query: Option<&str>,
98+
) -> Result<(), RedisCtlError> {
99+
let client = conn_mgr.create_enterprise_client(profile_name).await?;
100+
101+
let endpoint = format!("/v1/debuginfo/node/bdb/{}", bdb_uid);
102+
let response = client.get::<Value>(&endpoint).await.context(format!(
103+
"Failed to collect debug info for database {}",
104+
bdb_uid
105+
))?;
106+
107+
let result = if let Some(q) = query {
108+
utils::apply_jmespath(&response, q)?
109+
} else {
110+
response
111+
};
112+
113+
utils::print_formatted_output(result, output_format)
114+
}
115+
116+
#[cfg(test)]
117+
mod tests {
118+
use super::*;
119+
120+
#[test]
121+
fn test_debuginfo_commands() {
122+
use clap::CommandFactory;
123+
124+
#[derive(clap::Parser)]
125+
struct TestCli {
126+
#[command(subcommand)]
127+
cmd: DebugInfoCommands,
128+
}
129+
130+
TestCli::command().debug_assert();
131+
}
132+
}

0 commit comments

Comments
 (0)