This is an automated email from the ASF dual-hosted git repository.
Mmuzaf pushed a commit to branch cassandra-6.0
in repository https://gitbox.apache.org/repos/asf/cassandra.git
The following commit(s) were added to refs/heads/cassandra-6.0 by this push:
new 7874fdae57 Forbid ambiguous option/parameter keys in nodetool command
hierarchy
7874fdae57 is described below
commit 7874fdae578d61a770792911aa833f6fa91856e4
Author: Maxim Muzafarov <[email protected]>
AuthorDate: Sat Jul 11 18:51:51 2026 +0200
Forbid ambiguous option/parameter keys in nodetool command hierarchy
patch by Maxim Muzafarov; reviewed by Dmitry Konstantinov for
CASSANDRA-21509
---
.../tools/nodetool/InvalidatePermissionsCache.java | 2 +-
.../nodetool/help/invalidatepermissionscache | 4 +-
.../tools/nodetool/NodetoolClassHierarchyTest.java | 79 ++++++++++++++++++++++
3 files changed, 82 insertions(+), 3 deletions(-)
diff --git
a/src/java/org/apache/cassandra/tools/nodetool/InvalidatePermissionsCache.java
b/src/java/org/apache/cassandra/tools/nodetool/InvalidatePermissionsCache.java
index e1fcc6b26a..7f1372b33d 100644
---
a/src/java/org/apache/cassandra/tools/nodetool/InvalidatePermissionsCache.java
+++
b/src/java/org/apache/cassandra/tools/nodetool/InvalidatePermissionsCache.java
@@ -38,7 +38,7 @@ import static
com.google.common.base.Preconditions.checkArgument;
@Command(name = "invalidatepermissionscache", description = "Invalidate the
permissions cache")
public class InvalidatePermissionsCache extends AbstractCommand
{
- @Parameters(paramLabel = "role", description = "A role for which
permissions to specified resources need to be invalidated", arity = "0..1",
index = "0")
+ @Parameters(paramLabel = "role_name", description = "A role for which
permissions to specified resources need to be invalidated", arity = "0..1",
index = "0")
private String roleName;
// Data Resources
diff --git a/test/resources/nodetool/help/invalidatepermissionscache
b/test/resources/nodetool/help/invalidatepermissionscache
index 67566462d1..8d956bdca0 100644
--- a/test/resources/nodetool/help/invalidatepermissionscache
+++ b/test/resources/nodetool/help/invalidatepermissionscache
@@ -10,7 +10,7 @@ SYNOPSIS
[--all-tables] [--function <function>]
[--functions-in-keyspace <functions-in-keyspace>]
[--keyspace <keyspace>] [--mbean <mbean>] [--role <role>]
- [--table <table>] [--] <role>
+ [--table <table>] [--] <role_name>
OPTIONS
--all-functions
@@ -69,6 +69,6 @@ OPTIONS
list of argument, (useful when arguments might be mistaken for
command-line options
- <role>
+ <role_name>
A role for which permissions to specified resources need to be
invalidated
diff --git
a/test/unit/org/apache/cassandra/tools/nodetool/NodetoolClassHierarchyTest.java
b/test/unit/org/apache/cassandra/tools/nodetool/NodetoolClassHierarchyTest.java
index 4734a45c70..66e764aecf 100644
---
a/test/unit/org/apache/cassandra/tools/nodetool/NodetoolClassHierarchyTest.java
+++
b/test/unit/org/apache/cassandra/tools/nodetool/NodetoolClassHierarchyTest.java
@@ -20,8 +20,11 @@ package org.apache.cassandra.tools.nodetool;
import java.lang.reflect.Field;
import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
+import java.util.Set;
import java.util.TreeMap;
import java.util.function.Consumer;
@@ -143,6 +146,82 @@ public class NodetoolClassHierarchyTest extends CQLTester
failedCommands.isEmpty());
}
+ /**
+ * When command arguments are addressed by name rather than by CLI
position, all arguments
+ * of a command share a single flat, case-insensitive key namespace:
options are keyed by
+ * their normalized {@code paramLabel()} and names, positional parameters
by their paramLabel.
+ * No two arguments of the same command may claim the same normalized key,
otherwise one value
+ * is silently overwritten on write or misrouted on read (e.g. a command
field and a
+ * {@code @Mixin} field that produce the same paramLabel).
+ */
+ @Test
+ public void testCommandArgumentKeysAreUnambiguous()
+ {
+ CommandLine root = new CommandLine(NodetoolCommand.class);
+ Map<String, List<String>> affected = new TreeMap<>();
+
+ commandTreeWalker(root, cmd -> {
+ List<String> violations = collectAmbiguousArgumentKeys(cmd);
+ if (!violations.isEmpty())
+ affected.put(fullCommandName(cmd), violations);
+ });
+
+ assertTrue("The following commands have ambiguous argument keys
(options are keyed by " +
+ "normalized paramLabel and names, positional parameters by
paramLabel, all in " +
+ "one case-insensitive namespace). Rename the field or set
an explicit, unique " +
+ "paramLabel:\n" +
+ buildAffectedCommandMessage(affected),
+ affected.isEmpty());
+ }
+
+ private static List<String> collectAmbiguousArgumentKeys(CommandLine cmd)
+ {
+ List<String> violations = new ArrayList<>();
+ Map<String, String> keyOwners = new LinkedHashMap<>();
+
+ for (CommandLine.Model.OptionSpec option :
cmd.getCommandSpec().options())
+ {
+ if (option.usageHelp() || option.versionHelp())
+ continue;
+
+ String owner = "option " + String.join("/", option.names());
+ // An option is addressable by its paramLabel and by every
name/alias, so all of them
+ // must stay unambiguous, a set tolerates a paramLabel equal to
one of its own names.
+ Set<String> keys = new LinkedHashSet<>();
+ keys.add(normalizeOptionName(option.paramLabel()).toLowerCase());
+ for (String name : option.names())
+ keys.add(normalizeOptionName(name).toLowerCase());
+
+ for (String key : keys)
+ claimArgumentKey(key, owner, keyOwners, violations);
+ }
+
+ for (CommandLine.Model.PositionalParamSpec param :
cmd.getCommandSpec().positionalParameters())
+ {
+ String owner = String.format("parameter index=%s (%s)",
param.index(), param.paramLabel());
+
claimArgumentKey(normalizeOptionName(param.paramLabel()).toLowerCase(), owner,
keyOwners, violations);
+ }
+
+ return violations;
+ }
+
+ private static void claimArgumentKey(String key, String owner, Map<String,
String> keyOwners, List<String> violations)
+ {
+ String previousOwner = keyOwners.putIfAbsent(key, owner);
+ if (previousOwner != null)
+ violations.add(String.format("key '%s' is claimed by both %s and
%s", key, previousOwner, owner));
+ }
+
+ /** Strips the leading dashes from an option name. */
+ private static String normalizeOptionName(String name)
+ {
+ if (name.startsWith("--"))
+ return name.substring(2);
+ else if (name.startsWith("-"))
+ return name.substring(1);
+ return name;
+ }
+
/**
* For a given command, follows the {@code @ParentCommand} chain and
collects all
* options and parameters declared on each parent.
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]