Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions crates/redisctl/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -959,6 +959,9 @@ pub enum CloudCommands {
/// Enterprise-specific commands (placeholder for now)
#[derive(Subcommand, Debug)]
pub enum EnterpriseCommands {
/// Action (task) operations
#[command(subcommand)]
Action(crate::commands::enterprise::actions::ActionCommands),
/// Cluster operations
#[command(subcommand)]
Cluster(EnterpriseClusterCommands),
Expand Down
252 changes: 252 additions & 0 deletions crates/redisctl/src/commands/enterprise/actions.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,252 @@
use anyhow::Context;
use clap::Subcommand;
use redis_enterprise::ActionHandler;

use crate::cli::OutputFormat;
use crate::connection::ConnectionManager;
use crate::error::Result as CliResult;

#[derive(Debug, Clone, Subcommand)]
pub enum ActionCommands {
/// List all actions (tasks) in the cluster
List {
/// Filter by action status (running, completed, failed)
#[arg(long)]
status: Option<String>,

/// Filter by action type
#[arg(long)]
action_type: Option<String>,

/// Use v2 API endpoint (default: v1)
#[arg(long)]
v2: bool,
},

/// Get details of a specific action by UID
Get {
/// Action UID
uid: String,

/// Use v2 API endpoint (default: v1)
#[arg(long)]
v2: bool,
},

/// Get the status of a specific action
Status {
/// Action UID
uid: String,

/// Use v2 API endpoint (default: v1)
#[arg(long)]
v2: bool,
},

/// Cancel a running action
Cancel {
/// Action UID
uid: String,
},

/// List actions for a specific database
#[command(name = "list-for-bdb")]
ListForBdb {
/// Database UID
bdb_uid: u32,
},
}

impl ActionCommands {
#[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?;
let handler = ActionHandler::new(client);

match self {
ActionCommands::List {
status,
action_type,
v2,
} => {
let actions = if *v2 {
handler
.list_v2()
.await
.context("Failed to list actions (v2)")?
} else {
handler.list().await.context("Failed to list actions")?
};

// Convert to JSON Value for filtering and output
let mut response = serde_json::to_value(&actions)?;

// Apply filters if provided
if (status.is_some() || action_type.is_some())
&& let Some(actions) = response.as_array_mut()
{
actions.retain(|action| {
let mut keep = true;

if let Some(status_filter) = status {
if let Some(action_status) =
action.get("status").and_then(|s| s.as_str())
{
keep = keep && action_status.eq_ignore_ascii_case(status_filter);
} else {
keep = false;
}
}

if let Some(type_filter) = action_type {
if let Some(action_type) = action.get("type").and_then(|t| t.as_str()) {
keep = keep && action_type.eq_ignore_ascii_case(type_filter);
} else {
keep = false;
}
}

keep
});
}

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)?;
}

ActionCommands::Get { uid, v2 } => {
let action = if *v2 {
handler
.get_v2(uid)
.await
.context(format!("Failed to get action {} (v2)", uid))?
} else {
handler
.get(uid)
.await
.context(format!("Failed to get action {}", uid))?
};

// Convert to JSON Value
let response = serde_json::to_value(&action)?;

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)?;
}

ActionCommands::Status { uid, v2 } => {
let action = if *v2 {
handler
.get_v2(uid)
.await
.context(format!("Failed to get action status {} (v2)", uid))?
} else {
handler
.get(uid)
.await
.context(format!("Failed to get action status {}", uid))?
};

// Extract just the status information
let response = serde_json::to_value(&action)?;
let status_info = if let Some(obj) = response.as_object() {
let mut status = serde_json::Map::new();
if let Some(v) = obj.get("uid") {
status.insert("uid".to_string(), v.clone());
}
if let Some(v) = obj.get("status") {
status.insert("status".to_string(), v.clone());
}
if let Some(v) = obj.get("progress") {
status.insert("progress".to_string(), v.clone());
}
if let Some(v) = obj.get("error") {
status.insert("error".to_string(), v.clone());
}
if let Some(v) = obj.get("type") {
status.insert("type".to_string(), v.clone());
}
if let Some(v) = obj.get("description") {
status.insert("description".to_string(), v.clone());
}
serde_json::Value::Object(status)
} else {
response
};

let output_data = if let Some(q) = query {
super::utils::apply_jmespath(&status_info, q)?
} else {
status_info
};
super::utils::print_formatted_output(output_data, output_format)?;
}

ActionCommands::Cancel { uid } => {
handler
.cancel(uid)
.await
.context(format!("Failed to cancel action {}", uid))?;

// Create success response
let response = serde_json::json!({
"status": "success",
"message": format!("Action '{}' cancelled successfully", uid)
});

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)?;
}

ActionCommands::ListForBdb { bdb_uid } => {
let actions = handler
.list_for_bdb(*bdb_uid)
.await
.context(format!("Failed to list actions for database {}", bdb_uid))?;

// Convert to JSON Value
let response = serde_json::to_value(&actions)?;

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)?;
}
}

Ok(())
}
}

#[allow(dead_code)]
pub async fn handle_action_command(
conn_mgr: &ConnectionManager,
profile_name: Option<&str>,
action_cmd: ActionCommands,
output_format: OutputFormat,
query: Option<&str>,
) -> CliResult<()> {
action_cmd
.execute(conn_mgr, profile_name, output_format, query)
.await
}
1 change: 1 addition & 0 deletions crates/redisctl/src/commands/enterprise/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
//! Enterprise command implementations

pub mod actions;
pub mod cluster;
pub mod cluster_impl;
pub mod crdb;
Expand Down
10 changes: 10 additions & 0 deletions crates/redisctl/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,16 @@ async fn execute_enterprise_command(
use cli::EnterpriseCommands::*;

match enterprise_cmd {
Action(action_cmd) => {
commands::enterprise::actions::handle_action_command(
conn_mgr,
profile,
action_cmd.clone(),
output,
query,
)
.await
}
Cluster(cluster_cmd) => {
commands::enterprise::cluster::handle_cluster_command(
conn_mgr,
Expand Down
3 changes: 3 additions & 0 deletions docs/src/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@
- [Modules](./enterprise/modules.md)
- [Logs](./enterprise/logs.md)
- [Active-Active (CRDB)](./enterprise/crdb.md)
- [Actions (Tasks)](./enterprise/actions.md)
- [Diagnostics](./enterprise/diagnostics.md)
- [Job Scheduler](./enterprise/job-scheduler.md)
- [Workflows](./enterprise/workflows.md)
- [Raw API Access](./enterprise/api-access.md)

Expand Down
Loading
Loading