Skip to content

Commit

Permalink
Polishing #1992
Browse files Browse the repository at this point in the history
Allow dry-run using command objects.
  • Loading branch information
mp911de committed Jul 1, 2022
1 parent 6e5018b commit da8e356
Show file tree
Hide file tree
Showing 13 changed files with 169 additions and 19 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -96,10 +96,15 @@ public RedisFuture<Long> aclDeluser(String... usernames) {
}

@Override
public RedisFuture<String> aclDryRun(String username, String command, V... args) {
public RedisFuture<String> aclDryRun(String username, String command, String... args) {
return dispatch(commandBuilder.aclDryRun(username, command, args));
}

@Override
public RedisFuture<String> aclDryRun(String username, RedisCommand<K, V, ?> command) {
return dispatch(commandBuilder.aclDryRun(username, command));
}

@Override
public RedisFuture<String> aclGenpass() {
return dispatch(commandBuilder.aclGenpass());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -128,10 +128,15 @@ public Mono<Long> aclDeluser(String... usernames) {
}

@Override
public Mono<String> aclDryRun(String username, String command, V... args) {
public Mono<String> aclDryRun(String username, String command, String... args) {
return createMono(() -> commandBuilder.aclDryRun(username, command, args));
}

@Override
public Mono<String> aclDryRun(String username, RedisCommand<K, V, ?> command) {
return createMono(() -> commandBuilder.aclDryRun(username, command));
}

@Override
public Mono<String> aclGenpass() {
return createMono(commandBuilder::aclGenpass);
Expand Down
18 changes: 16 additions & 2 deletions src/main/java/io/lettuce/core/RedisCommandBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -93,12 +93,26 @@ Command<K, V, Long> aclDeluser(String... usernames) {
return createCommand(ACL, new IntegerOutput<>(codec), args);
}

Command<K, V, String> aclDryRun(String username, String command, V... commandArgs) {
Command<K, V, String> aclDryRun(String username, String command, String... commandArgs) {
LettuceAssert.notNull(username, "username " + MUST_NOT_BE_NULL);
LettuceAssert.notNull(command, "command " + MUST_NOT_BE_NULL);

CommandArgs<K, V> args = new CommandArgs<>(codec);
args.add(DRYRUN).add(username).add(command).addValues(commandArgs);
args.add(DRYRUN).add(username).add(command);

for (String commandArg : commandArgs) {
args.add(commandArg);
}
return createCommand(ACL, new StatusOutput<>(codec), args);
}

Command<K, V, String> aclDryRun(String username, RedisCommand<K, V, ?> command) {
LettuceAssert.notNull(username, "username " + MUST_NOT_BE_NULL);
LettuceAssert.notNull(command, "command " + MUST_NOT_BE_NULL);

CommandArgs<K, V> args = new CommandArgs<>(codec);
args.add(DRYRUN).add(username).add(command.getType()).addAll(command.getArgs());

return createCommand(ACL, new StatusOutput<>(codec), args);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import io.lettuce.core.AclSetuserArgs;
import io.lettuce.core.RedisFuture;
import io.lettuce.core.protocol.CommandType;
import io.lettuce.core.protocol.RedisCommand;

/**
* Asynchronous executed commands for the ACL-API.
Expand Down Expand Up @@ -65,7 +66,17 @@ public interface RedisAclAsyncCommands<K, V> {
* @return String reply: OK on success.
* @since 6.2
*/
RedisFuture<String> aclDryRun(String username, String command, V... args);
RedisFuture<String> aclDryRun(String username, String command, String... args);

/**
* Simulate the execution of a given command by a given user.
*
* @param username the specified username
* @param command the specified command to inspect
* @return String reply: OK on success.
* @since 6.2
*/
RedisFuture<String> aclDryRun(String username, RedisCommand<K, V, ?> command);

/**
* The command generates a password.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import io.lettuce.core.AclCategory;
import io.lettuce.core.AclSetuserArgs;
import io.lettuce.core.protocol.CommandType;
import io.lettuce.core.protocol.RedisCommand;

/**
* Reactive executed commands for the ACL-API.
Expand Down Expand Up @@ -66,7 +67,17 @@ public interface RedisAclReactiveCommands<K, V> {
* @return String reply: OK on success.
* @since 6.2
*/
Mono<String> aclDryRun(String username, String command, V... args);
Mono<String> aclDryRun(String username, String command, String... args);

/**
* Simulate the execution of a given command by a given user.
*
* @param username the specified username
* @param command the specified command to inspect
* @return String reply: OK on success.
* @since 6.2
*/
Mono<String> aclDryRun(String username, RedisCommand<K, V, ?> command);

/**
* The command generates a password.
Expand Down
13 changes: 12 additions & 1 deletion src/main/java/io/lettuce/core/api/sync/RedisAclCommands.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import io.lettuce.core.AclCategory;
import io.lettuce.core.AclSetuserArgs;
import io.lettuce.core.protocol.CommandType;
import io.lettuce.core.protocol.RedisCommand;

/**
* Synchronous executed commands for the ACL-API.
Expand Down Expand Up @@ -64,7 +65,17 @@ public interface RedisAclCommands<K, V> {
* @return String reply: OK on success.
* @since 6.2
*/
String aclDryRun(String username, String command, V... args);
String aclDryRun(String username, String command, String... args);

/**
* Simulate the execution of a given command by a given user.
*
* @param username the specified username
* @param command the specified command to inspect
* @return String reply: OK on success.
* @since 6.2
*/
String aclDryRun(String username, RedisCommand<K, V, ?> command);

/**
* The command generates a password.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import io.lettuce.core.AclCategory;
import io.lettuce.core.AclSetuserArgs;
import io.lettuce.core.protocol.CommandType;
import io.lettuce.core.protocol.RedisCommand;

/**
* Asynchronous executed commands on a node selection for the ACL-API.
Expand Down Expand Up @@ -64,7 +65,17 @@ public interface NodeSelectionAclAsyncCommands<K, V> {
* @return String reply: OK on success.
* @since 6.2
*/
AsyncExecutions<String> aclDryRun(String username, String command, V... args);
AsyncExecutions<String> aclDryRun(String username, String command, String... args);

/**
* Simulate the execution of a given command by a given user.
*
* @param username the specified username
* @param command the specified command to inspect
* @return String reply: OK on success.
* @since 6.2
*/
AsyncExecutions<String> aclDryRun(String username, RedisCommand<K, V, ?> command);

/**
* The command generates a password.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import io.lettuce.core.AclCategory;
import io.lettuce.core.AclSetuserArgs;
import io.lettuce.core.protocol.CommandType;
import io.lettuce.core.protocol.RedisCommand;

/**
* Synchronous executed commands on a node selection for the ACL-API.
Expand Down Expand Up @@ -64,7 +65,17 @@ public interface NodeSelectionAclCommands<K, V> {
* @return String reply: OK on success.
* @since 6.2
*/
Executions<String> aclDryRun(String username, String command, V... args);
Executions<String> aclDryRun(String username, String command, String... args);

/**
* Simulate the execution of a given command by a given user.
*
* @param username the specified username
* @param command the specified command to inspect
* @return String reply: OK on success.
* @since 6.2
*/
Executions<String> aclDryRun(String username, RedisCommand<K, V, ?> command);

/**
* The command generates a password.
Expand Down
14 changes: 14 additions & 0 deletions src/main/java/io/lettuce/core/protocol/CommandArgs.java
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,20 @@ public CommandArgs<K, V> add(ProtocolKeyword keyword) {
return this;
}

/**
* Add all arguments from {@link CommandArgs}
*
* @param args the args, must not be {@code null}
* @return the command args.
* @since 6.2
*/
public CommandArgs<K, V> addAll(CommandArgs<?, ?> args) {

LettuceAssert.notNull(args, "CommandArgs must not be null");
this.singularArguments.addAll(args.singularArguments);
return this;
}

@Override
public String toString() {

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2021-2022 the original author or authors.
* Copyright 2020-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -20,6 +20,7 @@ import io.lettuce.core.AclCategory
import io.lettuce.core.AclSetuserArgs
import io.lettuce.core.ExperimentalLettuceCoroutinesApi
import io.lettuce.core.protocol.CommandType
import io.lettuce.core.protocol.RedisCommand
import kotlinx.coroutines.flow.Flow

/**
Expand Down Expand Up @@ -64,7 +65,17 @@ interface RedisAclCoroutinesCommands<K : Any, V : Any> {
* @return String reply: OK on success.
* @since 6.2
*/
suspend fun aclDryRun(username: String, command: String, vararg args: V): String?
suspend fun aclDryRun(username: String, command: String, vararg args: String): String?

/**
* Simulate the execution of a given command by a given user.
*
* @param username the specified username
* @param command the specified command to inspect
* @return String reply: OK on success.
* @since 6.2
*/
suspend fun aclDryRun(username: String, command: RedisCommand<K, V, *>): String?

/**
* The command generates a password.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import io.lettuce.core.AclSetuserArgs
import io.lettuce.core.ExperimentalLettuceCoroutinesApi
import io.lettuce.core.api.reactive.RedisAclReactiveCommands
import io.lettuce.core.protocol.CommandType
import io.lettuce.core.protocol.RedisCommand
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.toList
import kotlinx.coroutines.reactive.asFlow
Expand All @@ -45,13 +46,24 @@ internal class RedisAclCoroutinesCommandsImpl<K : Any, V : Any>(internal val ops
override suspend fun aclCat(category: AclCategory): Set<CommandType> =
ops.aclCat(category).awaitFirstOrElse { emptySet<CommandType>() }

override suspend fun aclDeluser(vararg usernames: String): Long? = ops.aclDeluser(*usernames).awaitFirstOrNull()
override suspend fun aclDeluser(vararg usernames: String): Long? =
ops.aclDeluser(*usernames).awaitFirstOrNull()

override suspend fun aclDryRun(username: String, command: String, vararg args: V): String? = ops.aclDryRun(username, command, *args).awaitFirstOrNull()
override suspend fun aclDryRun(
username: String,
command: String,
vararg args: String
): String? = ops.aclDryRun(username, command, *args).awaitFirstOrNull()

override suspend fun aclDryRun(
username: String,
command: RedisCommand<K, V, *>
): String? = ops.aclDryRun(username, command).awaitFirstOrNull()

override suspend fun aclGenpass(): String? = ops.aclGenpass().awaitFirstOrNull()

override suspend fun aclGenpass(bits: Int): String? = ops.aclGenpass(bits).awaitFirstOrNull()
override suspend fun aclGenpass(bits: Int): String? =
ops.aclGenpass(bits).awaitFirstOrNull()

override suspend fun aclGetuser(username: String): List<Any> =
ops.aclGetuser(username).awaitFirst()
Expand Down
13 changes: 12 additions & 1 deletion src/main/templates/io/lettuce/core/api/RedisAclCommands.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

import io.lettuce.core.*;
import io.lettuce.core.protocol.CommandType;
import io.lettuce.core.protocol.RedisCommand;

import java.util.List;
import java.util.Map;
Expand Down Expand Up @@ -62,7 +63,17 @@ public interface RedisAclCommands<K, V> {
* @return String reply: OK on success.
* @since 6.2
*/
String aclDryRun(String username, String command, V... args);
String aclDryRun(String username, String command, String... args);

/**
* Simulate the execution of a given command by a given user.
*
* @param username the specified username
* @param command the specified command to inspect
* @return String reply: OK on success.
* @since 6.2
*/
String aclDryRun(String username, RedisCommand<K, V, ?> command);

/**
* The command generates a password.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@
import io.lettuce.core.RedisCommandExecutionException;
import io.lettuce.core.TestSupport;
import io.lettuce.core.api.sync.RedisCommands;
import io.lettuce.core.codec.StringCodec;
import io.lettuce.core.output.StatusOutput;
import io.lettuce.core.protocol.Command;
import io.lettuce.core.protocol.CommandArgs;
import io.lettuce.core.protocol.CommandType;
import io.lettuce.test.LettuceExtension;
import io.lettuce.test.condition.EnabledOnCommand;
Expand Down Expand Up @@ -70,11 +74,26 @@ void aclDeluser() {
}

@Test
@EnabledOnCommand("EVAL_RO") // Redis 7.0
@EnabledOnCommand("EVAL_RO") // Redis 7.0
void aclDryRun() {
assertThatThrownBy(() -> redis.aclDryRun("non-existing", "GET", "foo", "bar"))
.isInstanceOf(RedisCommandExecutionException.class).hasMessageContaining("ERR User 'non-existing' not found");
assertThat(redis.aclDryRun("default", "GET", "foo", "bar")).isEqualTo("OK");

AclSetuserArgs args = AclSetuserArgs.Builder.on().addCommand(CommandType.GET).keyPattern("objects:*")
.addPassword("foobared");
assertThat(redis.aclSetuser("foo", args)).isEqualTo("OK");

assertThat(redis.aclDryRun("foo", "GET", "objects:foo")).isEqualTo("OK");
assertThat(redis.aclDryRun("foo", "GET", "baz")).contains("no permissions");

Command<String, String, String> getFoo = new Command<>(CommandType.GET, new StatusOutput<>(StringCodec.UTF8),
new CommandArgs<>(StringCodec.UTF8).add("objects:foo"));
Command<String, String, String> getBaz = new Command<>(CommandType.GET, new StatusOutput<>(StringCodec.UTF8),
new CommandArgs<>(StringCodec.UTF8).add("baz"));

assertThat(redis.aclDryRun("foo", getFoo)).isEqualTo("OK");
assertThat(redis.aclDryRun("foo", getBaz)).contains("no permissions");
}

@Test
Expand All @@ -90,7 +109,8 @@ void aclGetuser() {

@Test
void aclLoad() {
assertThatThrownBy(redis::aclLoad).isInstanceOf(RedisCommandExecutionException.class).hasMessageContaining("ERR This Redis instance is not configured to use an ACL file.");
assertThatThrownBy(redis::aclLoad).isInstanceOf(RedisCommandExecutionException.class)
.hasMessageContaining("ERR This Redis instance is not configured to use an ACL file.");
}

@Test
Expand All @@ -111,13 +131,15 @@ void aclList() {

@Test
void aclSave() {
assertThatThrownBy(redis::aclSave).isInstanceOf(RedisCommandExecutionException.class).hasMessageContaining("ERR This Redis instance is not configured to use an ACL file.");
assertThatThrownBy(redis::aclSave).isInstanceOf(RedisCommandExecutionException.class)
.hasMessageContaining("ERR This Redis instance is not configured to use an ACL file.");
}

@Test
void aclSetuser() {
assertThat(redis.aclDeluser("foo")).isNotNull();
AclSetuserArgs args = AclSetuserArgs.Builder.on().addCommand(CommandType.GET).keyPattern("objects:*").addPassword("foobared");
AclSetuserArgs args = AclSetuserArgs.Builder.on().addCommand(CommandType.GET).keyPattern("objects:*")
.addPassword("foobared");
assertThat(redis.aclSetuser("foo", args)).isEqualTo("OK");
assertThat(redis.aclGetuser("foo")).contains("commands").contains("passwords").contains("keys");
assertThat(redis.aclDeluser("foo")).isNotNull();
Expand All @@ -141,4 +163,5 @@ void aclUsers() {
void aclWhoami() {
assertThat(redis.aclWhoami()).isEqualTo("default");
}

}

0 comments on commit da8e356

Please sign in to comment.