use redis_enterprise::{
AlertHandler, BdbHandler, ClusterHandler, CrdbHandler, CreateDatabaseRequest,
CreateRedisAclRequest, CreateRoleRequest, CreateUserRequest, DebugInfoHandler,
EnterpriseClient, LicenseHandler, LogsHandler, ModuleHandler, NodeHandler, RedisAclHandler,
RolesHandler, ShardHandler, StatsHandler, UserHandler,
};
use redisctl_config::Config;
use rmcp::{ErrorData as RmcpError, model::*};
use tracing::debug;
#[derive(Clone)]
pub struct EnterpriseTools {
client: EnterpriseClient,
}
impl EnterpriseTools {
pub fn new(profile: Option<&str>) -> anyhow::Result<Self> {
let config = Config::load()?;
let profile_name = match profile {
Some(name) => name.to_string(),
None => config.resolve_enterprise_profile(None)?,
};
debug!(profile = %profile_name, "Loading Enterprise client from profile");
let profile_config = config
.profiles
.get(&profile_name)
.ok_or_else(|| anyhow::anyhow!("Enterprise profile '{}' not found", profile_name))?;
let (url, username, password, insecure) =
profile_config.enterprise_credentials().ok_or_else(|| {
anyhow::anyhow!("Profile '{}' is not an Enterprise profile", profile_name)
})?;
let mut builder = EnterpriseClient::builder()
.base_url(url)
.username(username)
.insecure(insecure);
if let Some(pwd) = password {
builder = builder.password(pwd);
}
let client = builder.build()?;
Ok(Self { client })
}
fn to_result(&self, value: serde_json::Value) -> Result<CallToolResult, RmcpError> {
Ok(CallToolResult::success(vec![Content::text(
serde_json::to_string_pretty(&value).unwrap_or_else(|_| value.to_string()),
)]))
}
fn to_error(&self, err: impl std::fmt::Display) -> RmcpError {
RmcpError::internal_error(err.to_string(), None)
}
pub async fn get_cluster(&self) -> Result<CallToolResult, RmcpError> {
let handler = ClusterHandler::new(self.client.clone());
let cluster = handler.info().await.map_err(|e| self.to_error(e))?;
self.to_result(serde_json::to_value(cluster).map_err(|e| self.to_error(e))?)
}
pub async fn list_nodes(&self) -> Result<CallToolResult, RmcpError> {
let handler = NodeHandler::new(self.client.clone());
let nodes = handler.list().await.map_err(|e| self.to_error(e))?;
self.to_result(serde_json::to_value(nodes).map_err(|e| self.to_error(e))?)
}
pub async fn get_node(&self, node_id: i64) -> Result<CallToolResult, RmcpError> {
let handler = NodeHandler::new(self.client.clone());
let node = handler
.get(node_id as u32)
.await
.map_err(|e| self.to_error(e))?;
self.to_result(serde_json::to_value(node).map_err(|e| self.to_error(e))?)
}
pub async fn list_databases(&self) -> Result<CallToolResult, RmcpError> {
let handler = BdbHandler::new(self.client.clone());
let dbs = handler.list().await.map_err(|e| self.to_error(e))?;
self.to_result(serde_json::to_value(dbs).map_err(|e| self.to_error(e))?)
}
pub async fn get_database(&self, database_id: i64) -> Result<CallToolResult, RmcpError> {
let handler = BdbHandler::new(self.client.clone());
let db = handler
.get(database_id as u32)
.await
.map_err(|e| self.to_error(e))?;
self.to_result(serde_json::to_value(db).map_err(|e| self.to_error(e))?)
}
pub async fn get_database_stats(&self, database_id: i64) -> Result<CallToolResult, RmcpError> {
let handler = StatsHandler::new(self.client.clone());
let stats = handler
.database(database_id as u32, None)
.await
.map_err(|e| self.to_error(e))?;
self.to_result(serde_json::to_value(stats).map_err(|e| self.to_error(e))?)
}
pub async fn list_shards(&self) -> Result<CallToolResult, RmcpError> {
let handler = ShardHandler::new(self.client.clone());
let shards = handler.list().await.map_err(|e| self.to_error(e))?;
self.to_result(serde_json::to_value(shards).map_err(|e| self.to_error(e))?)
}
pub async fn list_alerts(&self) -> Result<CallToolResult, RmcpError> {
let handler = AlertHandler::new(self.client.clone());
let alerts = handler.list().await.map_err(|e| self.to_error(e))?;
self.to_result(serde_json::to_value(alerts).map_err(|e| self.to_error(e))?)
}
pub async fn get_logs(&self) -> Result<CallToolResult, RmcpError> {
let handler = LogsHandler::new(self.client.clone());
let logs = handler.list(None).await.map_err(|e| self.to_error(e))?;
self.to_result(serde_json::to_value(logs).map_err(|e| self.to_error(e))?)
}
pub async fn get_license(&self) -> Result<CallToolResult, RmcpError> {
let handler = LicenseHandler::new(self.client.clone());
let license = handler.get().await.map_err(|e| self.to_error(e))?;
self.to_result(serde_json::to_value(license).map_err(|e| self.to_error(e))?)
}
pub async fn create_database(
&self,
name: &str,
memory_size_mb: Option<u64>,
) -> Result<CallToolResult, RmcpError> {
let handler = BdbHandler::new(self.client.clone());
let memory_size = memory_size_mb.unwrap_or(100) * 1024 * 1024;
let request = CreateDatabaseRequest::builder()
.name(name)
.memory_size(memory_size)
.build();
let db = handler
.create(request)
.await
.map_err(|e| self.to_error(e))?;
self.to_result(serde_json::to_value(db).map_err(|e| self.to_error(e))?)
}
pub async fn delete_database(&self, database_id: i64) -> Result<CallToolResult, RmcpError> {
let handler = BdbHandler::new(self.client.clone());
handler
.delete(database_id as u32)
.await
.map_err(|e| self.to_error(e))?;
self.to_result(serde_json::json!({
"success": true,
"message": format!("Database {} deleted successfully", database_id)
}))
}
pub async fn update_database(
&self,
database_id: i64,
updates: serde_json::Value,
) -> Result<CallToolResult, RmcpError> {
let handler = BdbHandler::new(self.client.clone());
let db = handler
.update(database_id as u32, updates)
.await
.map_err(|e| self.to_error(e))?;
self.to_result(serde_json::to_value(db).map_err(|e| self.to_error(e))?)
}
pub async fn flush_database(&self, database_id: i64) -> Result<CallToolResult, RmcpError> {
let handler = BdbHandler::new(self.client.clone());
let result = handler
.flush(database_id as u32)
.await
.map_err(|e| self.to_error(e))?;
self.to_result(serde_json::to_value(result).map_err(|e| self.to_error(e))?)
}
pub async fn get_database_metrics(
&self,
database_id: i64,
) -> Result<CallToolResult, RmcpError> {
let handler = BdbHandler::new(self.client.clone());
let metrics = handler
.metrics(database_id as u32)
.await
.map_err(|e| self.to_error(e))?;
self.to_result(metrics)
}
pub async fn export_database(
&self,
database_id: i64,
export_location: &str,
) -> Result<CallToolResult, RmcpError> {
let handler = BdbHandler::new(self.client.clone());
let result = handler
.export(database_id as u32, export_location)
.await
.map_err(|e| self.to_error(e))?;
self.to_result(serde_json::to_value(result).map_err(|e| self.to_error(e))?)
}
pub async fn import_database(
&self,
database_id: i64,
import_location: &str,
flush_before_import: bool,
) -> Result<CallToolResult, RmcpError> {
let handler = BdbHandler::new(self.client.clone());
let result = handler
.import(database_id as u32, import_location, flush_before_import)
.await
.map_err(|e| self.to_error(e))?;
self.to_result(serde_json::to_value(result).map_err(|e| self.to_error(e))?)
}
pub async fn backup_database(&self, database_id: i64) -> Result<CallToolResult, RmcpError> {
let handler = BdbHandler::new(self.client.clone());
let result = handler
.backup(database_id as u32)
.await
.map_err(|e| self.to_error(e))?;
self.to_result(serde_json::to_value(result).map_err(|e| self.to_error(e))?)
}
pub async fn restore_database(
&self,
database_id: i64,
backup_uid: Option<&str>,
) -> Result<CallToolResult, RmcpError> {
let handler = BdbHandler::new(self.client.clone());
let result = handler
.restore(database_id as u32, backup_uid)
.await
.map_err(|e| self.to_error(e))?;
self.to_result(serde_json::to_value(result).map_err(|e| self.to_error(e))?)
}
pub async fn get_cluster_stats(&self) -> Result<CallToolResult, RmcpError> {
let handler = ClusterHandler::new(self.client.clone());
let stats = handler.stats().await.map_err(|e| self.to_error(e))?;
self.to_result(stats)
}
pub async fn get_cluster_settings(&self) -> Result<CallToolResult, RmcpError> {
let handler = ClusterHandler::new(self.client.clone());
let settings = handler.settings().await.map_err(|e| self.to_error(e))?;
self.to_result(settings)
}
pub async fn get_cluster_topology(&self) -> Result<CallToolResult, RmcpError> {
let handler = ClusterHandler::new(self.client.clone());
let topology = handler.topology().await.map_err(|e| self.to_error(e))?;
self.to_result(topology)
}
pub async fn update_cluster(
&self,
updates: serde_json::Value,
) -> Result<CallToolResult, RmcpError> {
let handler = ClusterHandler::new(self.client.clone());
let result = handler
.update(updates)
.await
.map_err(|e| self.to_error(e))?;
self.to_result(result)
}
pub async fn get_node_stats(&self, node_id: i64) -> Result<CallToolResult, RmcpError> {
let handler = NodeHandler::new(self.client.clone());
let stats = handler
.stats(node_id as u32)
.await
.map_err(|e| self.to_error(e))?;
self.to_result(serde_json::to_value(stats).map_err(|e| self.to_error(e))?)
}
pub async fn update_node(
&self,
node_id: i64,
updates: serde_json::Value,
) -> Result<CallToolResult, RmcpError> {
let handler = NodeHandler::new(self.client.clone());
let node = handler
.update(node_id as u32, updates)
.await
.map_err(|e| self.to_error(e))?;
self.to_result(serde_json::to_value(node).map_err(|e| self.to_error(e))?)
}
pub async fn remove_node(&self, node_id: i64) -> Result<CallToolResult, RmcpError> {
let handler = NodeHandler::new(self.client.clone());
handler
.remove(node_id as u32)
.await
.map_err(|e| self.to_error(e))?;
self.to_result(serde_json::json!({
"success": true,
"message": format!("Node {} removed from cluster", node_id)
}))
}
pub async fn get_shard(&self, shard_uid: &str) -> Result<CallToolResult, RmcpError> {
let handler = ShardHandler::new(self.client.clone());
let shard = handler.get(shard_uid).await.map_err(|e| self.to_error(e))?;
self.to_result(serde_json::to_value(shard).map_err(|e| self.to_error(e))?)
}
pub async fn get_alert(&self, alert_uid: &str) -> Result<CallToolResult, RmcpError> {
let handler = AlertHandler::new(self.client.clone());
let alert = handler.get(alert_uid).await.map_err(|e| self.to_error(e))?;
self.to_result(serde_json::to_value(alert).map_err(|e| self.to_error(e))?)
}
pub async fn list_users(&self) -> Result<CallToolResult, RmcpError> {
let handler = UserHandler::new(self.client.clone());
let users = handler.list().await.map_err(|e| self.to_error(e))?;
self.to_result(serde_json::to_value(users).map_err(|e| self.to_error(e))?)
}
pub async fn get_user(&self, user_id: i64) -> Result<CallToolResult, RmcpError> {
let handler = UserHandler::new(self.client.clone());
let user = handler
.get(user_id as u32)
.await
.map_err(|e| self.to_error(e))?;
self.to_result(serde_json::to_value(user).map_err(|e| self.to_error(e))?)
}
pub async fn create_user(
&self,
email: &str,
password: &str,
role: &str,
name: Option<&str>,
) -> Result<CallToolResult, RmcpError> {
let handler = UserHandler::new(self.client.clone());
let request = match name {
Some(n) => CreateUserRequest::builder()
.email(email)
.password(password)
.role(role)
.name(n)
.build(),
None => CreateUserRequest::builder()
.email(email)
.password(password)
.role(role)
.build(),
};
let user = handler
.create(request)
.await
.map_err(|e| self.to_error(e))?;
self.to_result(serde_json::to_value(user).map_err(|e| self.to_error(e))?)
}
pub async fn delete_user(&self, user_id: i64) -> Result<CallToolResult, RmcpError> {
let handler = UserHandler::new(self.client.clone());
handler
.delete(user_id as u32)
.await
.map_err(|e| self.to_error(e))?;
self.to_result(serde_json::json!({
"success": true,
"message": format!("User {} deleted successfully", user_id)
}))
}
pub async fn list_roles(&self) -> Result<CallToolResult, RmcpError> {
let handler = RolesHandler::new(self.client.clone());
let roles = handler.list().await.map_err(|e| self.to_error(e))?;
self.to_result(serde_json::to_value(roles).map_err(|e| self.to_error(e))?)
}
pub async fn get_role(&self, role_id: i64) -> Result<CallToolResult, RmcpError> {
let handler = RolesHandler::new(self.client.clone());
let role = handler
.get(role_id as u32)
.await
.map_err(|e| self.to_error(e))?;
self.to_result(serde_json::to_value(role).map_err(|e| self.to_error(e))?)
}
pub async fn create_role(
&self,
name: &str,
management: Option<&str>,
) -> Result<CallToolResult, RmcpError> {
let handler = RolesHandler::new(self.client.clone());
let request = match management {
Some(m) => CreateRoleRequest::builder()
.name(name)
.management(m)
.build(),
None => CreateRoleRequest::builder().name(name).build(),
};
let role = handler
.create(request)
.await
.map_err(|e| self.to_error(e))?;
self.to_result(serde_json::to_value(role).map_err(|e| self.to_error(e))?)
}
pub async fn delete_role(&self, role_id: i64) -> Result<CallToolResult, RmcpError> {
let handler = RolesHandler::new(self.client.clone());
handler
.delete(role_id as u32)
.await
.map_err(|e| self.to_error(e))?;
self.to_result(serde_json::json!({
"success": true,
"message": format!("Role {} deleted successfully", role_id)
}))
}
pub async fn list_acls(&self) -> Result<CallToolResult, RmcpError> {
let handler = RedisAclHandler::new(self.client.clone());
let acls = handler.list().await.map_err(|e| self.to_error(e))?;
self.to_result(serde_json::to_value(acls).map_err(|e| self.to_error(e))?)
}
pub async fn get_acl(&self, acl_id: i64) -> Result<CallToolResult, RmcpError> {
let handler = RedisAclHandler::new(self.client.clone());
let acl = handler
.get(acl_id as u32)
.await
.map_err(|e| self.to_error(e))?;
self.to_result(serde_json::to_value(acl).map_err(|e| self.to_error(e))?)
}
pub async fn create_acl(
&self,
name: &str,
acl: &str,
description: Option<&str>,
) -> Result<CallToolResult, RmcpError> {
let handler = RedisAclHandler::new(self.client.clone());
let request = match description {
Some(d) => CreateRedisAclRequest::builder()
.name(name)
.acl(acl)
.description(d)
.build(),
None => CreateRedisAclRequest::builder().name(name).acl(acl).build(),
};
let result = handler
.create(request)
.await
.map_err(|e| self.to_error(e))?;
self.to_result(serde_json::to_value(result).map_err(|e| self.to_error(e))?)
}
pub async fn delete_acl(&self, acl_id: i64) -> Result<CallToolResult, RmcpError> {
let handler = RedisAclHandler::new(self.client.clone());
handler
.delete(acl_id as u32)
.await
.map_err(|e| self.to_error(e))?;
self.to_result(serde_json::json!({
"success": true,
"message": format!("Redis ACL {} deleted successfully", acl_id)
}))
}
pub async fn list_modules(&self) -> Result<CallToolResult, RmcpError> {
let handler = ModuleHandler::new(self.client.clone());
let modules = handler.list().await.map_err(|e| self.to_error(e))?;
self.to_result(serde_json::to_value(modules).map_err(|e| self.to_error(e))?)
}
pub async fn get_module(&self, module_uid: &str) -> Result<CallToolResult, RmcpError> {
let handler = ModuleHandler::new(self.client.clone());
let module = handler
.get(module_uid)
.await
.map_err(|e| self.to_error(e))?;
self.to_result(serde_json::to_value(module).map_err(|e| self.to_error(e))?)
}
pub async fn list_crdbs(&self) -> Result<CallToolResult, RmcpError> {
let handler = CrdbHandler::new(self.client.clone());
let crdbs = handler.list().await.map_err(|e| self.to_error(e))?;
self.to_result(serde_json::to_value(crdbs).map_err(|e| self.to_error(e))?)
}
pub async fn get_crdb(&self, crdb_guid: &str) -> Result<CallToolResult, RmcpError> {
let handler = CrdbHandler::new(self.client.clone());
let crdb = handler.get(crdb_guid).await.map_err(|e| self.to_error(e))?;
self.to_result(serde_json::to_value(crdb).map_err(|e| self.to_error(e))?)
}
pub async fn update_crdb(
&self,
crdb_guid: &str,
updates: serde_json::Value,
) -> Result<CallToolResult, RmcpError> {
let handler = CrdbHandler::new(self.client.clone());
let crdb = handler
.update(crdb_guid, updates)
.await
.map_err(|e| self.to_error(e))?;
self.to_result(serde_json::to_value(crdb).map_err(|e| self.to_error(e))?)
}
pub async fn delete_crdb(&self, crdb_guid: &str) -> Result<CallToolResult, RmcpError> {
let handler = CrdbHandler::new(self.client.clone());
handler
.delete(crdb_guid)
.await
.map_err(|e| self.to_error(e))?;
self.to_result(serde_json::json!({
"success": true,
"message": format!("CRDB {} deleted successfully", crdb_guid)
}))
}
pub async fn list_debuginfo(&self) -> Result<CallToolResult, RmcpError> {
let handler = DebugInfoHandler::new(self.client.clone());
let tasks = handler.list().await.map_err(|e| self.to_error(e))?;
self.to_result(serde_json::to_value(tasks).map_err(|e| self.to_error(e))?)
}
pub async fn get_debuginfo_status(&self, task_id: &str) -> Result<CallToolResult, RmcpError> {
let handler = DebugInfoHandler::new(self.client.clone());
let status = handler
.status(task_id)
.await
.map_err(|e| self.to_error(e))?;
self.to_result(serde_json::to_value(status).map_err(|e| self.to_error(e))?)
}
}