use crate::error::RedisCtlError;
use anyhow::Context;
use clap::Subcommand;
use crate::cli::OutputFormat;
use crate::connection::ConnectionManager;
use crate::error::Result as CliResult;
#[derive(Debug, Clone, Subcommand)]
pub enum CmSettingsCommands {
Get {
#[arg(long)]
setting: Option<String>,
},
#[command(after_help = "EXAMPLES:
# Update a specific setting
redisctl enterprise cm-settings set --watchdog-enabled true
# Update multiple settings
redisctl enterprise cm-settings set --cm-port 8443 --watchdog-enabled false
# Using JSON for full configuration
redisctl enterprise cm-settings set --data @settings.json")]
Set {
#[arg(long)]
watchdog_enabled: Option<bool>,
#[arg(long)]
cm_port: Option<u16>,
#[arg(short, long, value_name = "FILE|JSON")]
data: Option<String>,
#[arg(short, long)]
force: bool,
},
#[command(name = "set-value")]
SetValue {
name: String,
#[arg(long)]
value: String,
#[arg(short, long)]
force: bool,
},
Reset {
#[arg(short, long)]
force: bool,
},
Export {
#[arg(short, long, default_value = "-")]
output: String,
},
Import {
#[arg(short, long)]
file: String,
#[arg(short, long)]
force: bool,
},
Validate {
#[arg(short, long)]
file: String,
},
#[command(name = "list-categories")]
ListCategories,
#[command(name = "get-category")]
GetCategory {
category: String,
},
}
impl CmSettingsCommands {
#[allow(dead_code)]
pub async fn execute(
&self,
conn_mgr: &ConnectionManager,
profile_name: Option<&str>,
output_format: OutputFormat,
query: Option<&str>,
) -> CliResult<()> {
let client = conn_mgr.create_enterprise_client(profile_name).await?;
match self {
CmSettingsCommands::Get { setting } => {
let response: serde_json::Value = client
.get("/v1/cm_settings")
.await
.map_err(RedisCtlError::from)?;
let output_data = if let Some(s) = setting {
super::utils::apply_jmespath(&response, s)?
} else if let Some(q) = query {
super::utils::apply_jmespath(&response, q)?
} else {
response
};
super::utils::print_formatted_output(output_data, output_format)?;
}
CmSettingsCommands::Set {
watchdog_enabled,
cm_port,
data,
force,
} => {
if !*force && !super::utils::confirm_action("Update cluster manager settings?")? {
return Ok(());
}
let mut json_data = if let Some(data_str) = data {
super::utils::read_json_data(data_str)?
} else {
serde_json::json!({})
};
let data_obj = json_data.as_object_mut().unwrap();
if let Some(we) = watchdog_enabled {
data_obj.insert("watchdog_enabled".to_string(), serde_json::json!(we));
}
if let Some(port) = cm_port {
data_obj.insert("cm_port".to_string(), serde_json::json!(port));
}
let response: serde_json::Value = client
.put("/v1/cm_settings", &json_data)
.await
.map_err(RedisCtlError::from)?;
println!("Cluster manager settings updated successfully");
let output_data = if let Some(q) = query {
super::utils::apply_jmespath(&response, q)?
} else {
response
};
super::utils::print_formatted_output(output_data, output_format)?;
}
CmSettingsCommands::SetValue { name, value, force } => {
if !force && !super::utils::confirm_action(&format!("Update setting '{}'?", name))?
{
return Ok(());
}
let mut settings: serde_json::Value = client
.get("/v1/cm_settings")
.await
.map_err(RedisCtlError::from)?;
let parsed_value: serde_json::Value =
serde_json::from_str(value).unwrap_or_else(|_| serde_json::json!(value));
if name.contains('.') {
let parts: Vec<&str> = name.split('.').collect();
let mut current = &mut settings;
for (i, part) in parts.iter().enumerate() {
if i == parts.len() - 1 {
current[part] = parsed_value.clone();
} else {
current = &mut current[part];
}
}
} else {
settings[name] = parsed_value;
}
let response: serde_json::Value = client
.put("/v1/cm_settings", &settings)
.await
.map_err(RedisCtlError::from)?;
println!("Setting '{}' updated to: {}", name, value);
let output_data = if let Some(q) = query {
super::utils::apply_jmespath(&response, q)?
} else {
response
};
super::utils::print_formatted_output(output_data, output_format)?;
}
CmSettingsCommands::Reset { force } => {
if !force
&& !super::utils::confirm_action(
"Reset all cluster manager settings to defaults?",
)?
{
return Ok(());
}
let response: serde_json::Value = client
.put("/v1/cm_settings", &serde_json::json!({}))
.await
.map_err(RedisCtlError::from)?;
println!("Cluster manager settings reset to defaults");
let output_data = if let Some(q) = query {
super::utils::apply_jmespath(&response, q)?
} else {
response
};
super::utils::print_formatted_output(output_data, output_format)?;
}
CmSettingsCommands::Export { output } => {
let settings: serde_json::Value = client
.get("/v1/cm_settings")
.await
.map_err(RedisCtlError::from)?;
if output == "-" {
super::utils::print_formatted_output(settings, output_format)?;
} else {
let json_str = serde_json::to_string_pretty(&settings)
.context("Failed to serialize settings")?;
std::fs::write(output, json_str).context("Failed to write settings to file")?;
println!("Settings exported to: {}", output);
}
}
CmSettingsCommands::Import { file, force } => {
if !force
&& !super::utils::confirm_action("Import cluster manager settings from file?")?
{
return Ok(());
}
let json_data = super::utils::read_json_data(file)?;
let response: serde_json::Value = client
.put("/v1/cm_settings", &json_data)
.await
.map_err(RedisCtlError::from)?;
println!("Settings imported successfully");
let output_data = if let Some(q) = query {
super::utils::apply_jmespath(&response, q)?
} else {
response
};
super::utils::print_formatted_output(output_data, output_format)?;
}
CmSettingsCommands::Validate { file } => {
let json_data = super::utils::read_json_data(file)?;
if json_data.is_object() {
println!("Settings file is valid JSON");
let obj = json_data.as_object().unwrap();
println!("\nFound settings categories:");
for key in obj.keys() {
println!(" - {}", key);
}
} else {
return Err(
anyhow::anyhow!("Invalid settings format: expected JSON object").into(),
);
}
}
CmSettingsCommands::ListCategories => {
let settings: serde_json::Value = client
.get("/v1/cm_settings")
.await
.map_err(RedisCtlError::from)?;
let categories = if let Some(obj) = settings.as_object() {
let cats: Vec<String> = obj.keys().cloned().collect();
serde_json::json!(cats)
} else {
serde_json::json!([])
};
let output_data = if let Some(q) = query {
super::utils::apply_jmespath(&categories, q)?
} else {
categories
};
super::utils::print_formatted_output(output_data, output_format)?;
}
CmSettingsCommands::GetCategory { category } => {
let settings: serde_json::Value = client
.get("/v1/cm_settings")
.await
.map_err(RedisCtlError::from)?;
let category_data = &settings[category];
if category_data.is_null() {
return Err(anyhow::anyhow!("Category '{}' not found", category).into());
}
let output_data = if let Some(q) = query {
super::utils::apply_jmespath(category_data, q)?
} else {
category_data.clone()
};
super::utils::print_formatted_output(output_data, output_format)?;
}
}
Ok(())
}
}
#[allow(dead_code)]
pub async fn handle_cm_settings_command(
conn_mgr: &ConnectionManager,
profile_name: Option<&str>,
cm_settings_cmd: CmSettingsCommands,
output_format: OutputFormat,
query: Option<&str>,
) -> CliResult<()> {
cm_settings_cmd
.execute(conn_mgr, profile_name, output_format, query)
.await
}