From 6be84d18703f8ce151eac44469d6af6ab1d20747 Mon Sep 17 00:00:00 2001 From: "Meir Shpilraien (Spielrein)" Date: Mon, 16 Oct 2023 16:04:31 +0300 Subject: [PATCH] Added support for _proxy-filtered flag. (#366) The new flag is an enterprise only flag. The new flag will make sure the command will not appeared on slow log, command command, and other location where we do not want to expose it. --- .circleci/config.yml | 2 +- redismodule-rs-macros/src/command.rs | 35 ++++++++++++++++++++++++---- redismodule-rs-macros/src/lib.rs | 3 ++- src/context/commands.rs | 16 +++++++------ src/context/mod.rs | 18 ++++++++++++++ 5 files changed, 61 insertions(+), 13 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 8a98b8a5..f4e1941d 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -114,7 +114,7 @@ commands: - run: name: patch macos tests # Avoid AVX with Regex with CircleCI since virtualization layer doesn't support it. Use sed to replace the relevant entry in cargo.toml command: | - if [[ $(uname -s) == Darwin ]]; then sed -i 's/regex = "1"/regex = { version = "1", features = ["perf", "unicode"] }/g' Cargo.toml; fi + if [[ $(uname -s) == Darwin ]]; then sed -i 's/regex = "1"/regex = { version = "1", features = ["perf", "unicode"], default-features = false }/g' Cargo.toml; fi cat Cargo.toml - restore_cache: keys: diff --git a/redismodule-rs-macros/src/command.rs b/redismodule-rs-macros/src/command.rs index 512ee080..973a0e7c 100644 --- a/redismodule-rs-macros/src/command.rs +++ b/redismodule-rs-macros/src/command.rs @@ -109,6 +109,21 @@ impl From<&RedisCommandFlags> for &'static str { } } +#[derive(Debug, Deserialize)] +pub enum RedisEnterpriseCommandFlags { + /// A special enterprise only flag, make sure the commands marked with this flag will not be expose to + /// user via `command` command or on slow log. + ProxyFiltered, +} + +impl From<&RedisEnterpriseCommandFlags> for &'static str { + fn from(value: &RedisEnterpriseCommandFlags) -> Self { + match value { + RedisEnterpriseCommandFlags::ProxyFiltered => "_proxy-filtered", + } + } +} + #[derive(Debug, Deserialize)] pub enum RedisCommandKeySpecFlags { /// Read-Only. Reads the value of the key, but doesn't necessarily return it. @@ -212,6 +227,7 @@ pub struct KeySpecArg { struct Args { name: Option, flags: Vec, + enterprise_flags: Option>, summary: Option, complexity: Option, since: Option, @@ -240,10 +256,7 @@ pub(crate) fn redis_command(attr: TokenStream, item: TokenStream) -> TokenStream let original_function_name = func.sig.ident.clone(); - let c_function_name = Ident::new( - &format!("_inner_{}", func.sig.ident), - func.sig.ident.span(), - ); + let c_function_name = Ident::new(&format!("_inner_{}", func.sig.ident), func.sig.ident.span()); let get_command_info_function_name = Ident::new( &format!("_inner_get_command_info_{}", func.sig.ident), @@ -262,6 +275,19 @@ pub(crate) fn redis_command(attr: TokenStream, item: TokenStream) -> TokenStream .trim() .to_owned(); let flags_literal = quote!(#flags_str); + let enterprise_flags_str = args + .enterprise_flags + .map(|v| { + v.into_iter() + .fold(String::new(), |s, v| { + format!("{} {}", s, Into::<&'static str>::into(&v)) + }) + .trim() + .to_owned() + }) + .unwrap_or_default(); + + let enterprise_flags_literal = quote!(#enterprise_flags_str); let summary_literal = to_token_stream(args.summary); let complexity_literal = to_token_stream(args.complexity); let since_literal = to_token_stream(args.since); @@ -362,6 +388,7 @@ pub(crate) fn redis_command(attr: TokenStream, item: TokenStream) -> TokenStream Ok(redis_module::commands::CommandInfo::new( #name_literal.to_owned(), Some(#flags_literal.to_owned()), + Some(#enterprise_flags_literal.to_owned()), #summary_literal, #complexity_literal, #since_literal, diff --git a/redismodule-rs-macros/src/lib.rs b/redismodule-rs-macros/src/lib.rs index b07f2ba2..99d08875 100644 --- a/redismodule-rs-macros/src/lib.rs +++ b/redismodule-rs-macros/src/lib.rs @@ -9,7 +9,8 @@ mod redis_value; /// This proc macro allow to specify that the follow function is a Redis command. /// The macro accept the following arguments that discribe the command properties: /// * name (optional) - The command name. in case not given, the function name will be taken. -/// * flags - An array of `RedisCommandFlags`. +/// * flags - An array of [`command::RedisCommandFlags`]. +/// * enterprise_flags - An array of [`command::RedisEnterpriseCommandFlags`]. /// * summary (optional) - Command summary /// * complexity (optional) - Command compexity /// * since (optional) - At which module version the command was first introduce diff --git a/src/context/commands.rs b/src/context/commands.rs index 534406b9..89832cea 100644 --- a/src/context/commands.rs +++ b/src/context/commands.rs @@ -315,6 +315,7 @@ type CommandCallback = pub struct CommandInfo { name: String, flags: Option, + enterprise_flags: Option, summary: Option, complexity: Option, since: Option, @@ -328,6 +329,7 @@ impl CommandInfo { pub fn new( name: String, flags: Option, + enterprise_flags: Option, summary: Option, complexity: Option, since: Option, @@ -339,6 +341,7 @@ impl CommandInfo { CommandInfo { name, flags, + enterprise_flags, summary, complexity, since, @@ -368,16 +371,15 @@ api! {[ ], /// Register all the commands located on `COMMNADS_LIST`. fn register_commands_internal(ctx: &Context) -> Result<(), RedisError> { + let is_enterprise = ctx.is_enterprise(); COMMANDS_LIST.iter().try_for_each(|command| { let command_info = command()?; let name: CString = CString::new(command_info.name.as_str()).unwrap(); - let flags = CString::new( - command_info - .flags - .as_deref() - .unwrap_or(""), - ) - .unwrap(); + let mut flags = command_info.flags.as_deref().unwrap_or("").to_owned(); + if is_enterprise { + flags = format!("{flags} {}", command_info.enterprise_flags.as_deref().unwrap_or("")).trim().to_owned(); + } + let flags = CString::new(flags).map_err(|e| RedisError::String(e.to_string()))?; if unsafe { RedisModule_CreateCommand( diff --git a/src/context/mod.rs b/src/context/mod.rs index 3c0cb66a..9c9f3d50 100644 --- a/src/context/mod.rs +++ b/src/context/mod.rs @@ -850,6 +850,24 @@ impl Context { unsafe { RedisModule_AvoidReplicaTraffic() == 1 } } ); + + /// Return [Ok(true)] is the current Redis deployment is enterprise, otherwise [Ok(false)]. + /// Return error in case it was not possible to determind the deployment. + fn is_enterprise_internal(&self) -> Result { + let info_res = self.call("info", &["server"])?; + match info_res { + RedisValue::BulkRedisString(res) => Ok(res.try_as_str()?.contains("rlec_version:")), + _ => Err(RedisError::Str("Mismatch call reply type")), + } + } + + /// Return `true` is the current Redis deployment is enterprise, otherwise `false`. + pub fn is_enterprise(&self) -> bool { + self.is_enterprise_internal().unwrap_or_else(|e| { + log::error!("Failed getting deployment type, assuming oss. Error: {e}."); + false + }) + } } extern "C" fn post_notification_job_free_callback(pd: *mut c_void) {