diff --git a/tools/src/main/java/org/apache/kafka/tools/FeatureCommand.java b/tools/src/main/java/org/apache/kafka/tools/FeatureCommand.java index 87e4c228baf2e..ff29936481fe0 100644 --- a/tools/src/main/java/org/apache/kafka/tools/FeatureCommand.java +++ b/tools/src/main/java/org/apache/kafka/tools/FeatureCommand.java @@ -76,7 +76,7 @@ static void execute(String... args) throws Exception { .newArgumentParser("kafka-features") .defaultHelp(true) .description("This tool manages feature flags in Kafka."); - MutuallyExclusiveGroup bootstrapGroup = parser.addMutuallyExclusiveGroup().required(true); + MutuallyExclusiveGroup bootstrapGroup = parser.addMutuallyExclusiveGroup(); bootstrapGroup.addArgument("--bootstrap-server") .help("A comma-separated list of host:port pairs to use for establishing the connection to the Kafka cluster."); bootstrapGroup.addArgument("--bootstrap-controller") @@ -92,14 +92,27 @@ static void execute(String... args) throws Exception { addVersionMappingParser(subparsers); addFeatureDependenciesParser(subparsers); - Namespace namespace = parser.parseArgsOrFail(args); + Namespace namespace = parser.parseArgs(args); String command = namespace.getString("command"); + if (command.equals("version-mapping")) { + handleVersionMapping(namespace, Feature.PRODUCTION_FEATURES); + return; + } else if (command.equals("feature-dependencies")) { + handleFeatureDependencies(namespace, Feature.PRODUCTION_FEATURES); + return; + } String configPath = namespace.getString("command_config"); Properties properties = (configPath == null) ? new Properties() : Utils.loadProps(configPath); - CommandLineUtils.initializeBootstrapProperties(properties, - Optional.ofNullable(namespace.getString("bootstrap_server")), - Optional.ofNullable(namespace.getString("bootstrap_controller"))); + try { + CommandLineUtils.initializeBootstrapProperties(properties, + Optional.ofNullable(namespace.getString("bootstrap_server")), + Optional.ofNullable(namespace.getString("bootstrap_controller"))); + } catch (Exception e) { + // bootstrap_server and bootstrap_controller are in a mutually exclusive group, + // so the exception happens only when both of them are missing + throw new ArgumentParserException(e.getMessage(), parser); + } try (Admin adminClient = Admin.create(properties)) { switch (command) { @@ -115,12 +128,6 @@ static void execute(String... args) throws Exception { case "disable": handleDisable(namespace, adminClient); break; - case "version-mapping": - handleVersionMapping(namespace, Feature.PRODUCTION_FEATURES); - break; - case "feature-dependencies": - handleFeatureDependencies(namespace, Feature.PRODUCTION_FEATURES); - break; default: throw new TerseException("Unknown command " + command); } diff --git a/tools/src/test/java/org/apache/kafka/tools/FeatureCommandTest.java b/tools/src/test/java/org/apache/kafka/tools/FeatureCommandTest.java index 2caaf8a2918e8..01433dc674550 100644 --- a/tools/src/test/java/org/apache/kafka/tools/FeatureCommandTest.java +++ b/tools/src/test/java/org/apache/kafka/tools/FeatureCommandTest.java @@ -459,6 +459,22 @@ public void testHandleVersionMappingWithInvalidReleaseVersion() { MetadataVersion.MINIMUM_VERSION, MetadataVersion.latestTesting()), exception2.getMessage()); } + @Test + public void testHandleVersionMappingWithoutBootstrap() { + Map.Entry output = ToolsTestUtils.grabConsoleOutputAndError(() -> FeatureCommand.mainNoExit("version-mapping")); + assertEquals("", output.getValue()); + MetadataVersion metadataVersion = MetadataVersion.latestProduction(); + assertTrue(output.getKey().contains("metadata.version=" + metadataVersion.featureLevel() + " (" + metadataVersion.version() + ")"), + "Output did not contain expected Metadata Version: " + output.getKey()); + } + + @Test + public void testHandleRemoteCommandWithoutBootstrap() { + String errorMessage = ToolsTestUtils.grabConsoleError(() -> FeatureCommand.mainNoExit("upgrade")); + assertTrue(errorMessage.contains("You must specify either --bootstrap-controller " + + "or --bootstrap-server.")); + } + @Test public void testHandleFeatureDependenciesForFeatureWithDependencies() { Map namespace = new HashMap<>();