diff --git a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/cli/Picocli.java b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/cli/Picocli.java index 0e2fdd4de15..3877bfee03c 100644 --- a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/cli/Picocli.java +++ b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/cli/Picocli.java @@ -225,6 +225,7 @@ public class Picocli { if (cliArgs.contains(OPTIMIZED_BUILD_OPTION_LONG) && !wasBuildEverRun()) { throw new PropertyException(Messages.optimizedUsedForFirstStartup()); } + warnOnDuplicatedOptionsInCli(); IncludeOptions options = getIncludeOptions(cliArgs, abstractCommand, abstractCommand.getName()); @@ -953,4 +954,13 @@ public class Picocli { } } + // Show warning about duplicated options in CLI + public void warnOnDuplicatedOptionsInCli() { + var duplicatedOptionsNames = ConfigArgsConfigSource.getDuplicatedArgNames(); + if (!duplicatedOptionsNames.isEmpty()) { + warn("Duplicated options present in CLI: %s".formatted(String.join(", ", duplicatedOptionsNames))); + ConfigArgsConfigSource.clearDuplicatedArgNames(); + } + } + } diff --git a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/ConfigArgsConfigSource.java b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/ConfigArgsConfigSource.java index 7bf1319abc3..13006ff25d8 100644 --- a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/ConfigArgsConfigSource.java +++ b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/ConfigArgsConfigSource.java @@ -22,6 +22,7 @@ import static org.keycloak.quarkus.runtime.cli.Picocli.ARG_SHORT_PREFIX; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; @@ -52,6 +53,7 @@ public class ConfigArgsConfigSource extends PropertiesConfigSource { private static final String CLI_ARGS = "kc.config.args"; public static final String NAME = "CliConfigSource"; private static final Pattern ARG_KEY_VALUE_SPLIT = Pattern.compile("="); + private static Set duplicatedArgNames; protected ConfigArgsConfigSource() { super(parseArguments(), NAME, 600); @@ -103,20 +105,29 @@ public class ConfigArgsConfigSource extends PropertiesConfigSource { } private static Map parseArguments() { - Map properties = new HashMap<>(); + final Map properties = new HashMap<>(); + final Set allPropertiesNames = new HashSet<>(); + duplicatedArgNames = new HashSet<>(); - parseConfigArgs(getAllCliArgs(), new BiConsumer() { - @Override - public void accept(String key, String value) { - PropertyMappers.getKcKeyFromCliKey(key).ifPresent(s -> { - properties.put(s, value); - }); + parseConfigArgs(getAllCliArgs(), (key, value) -> { + if (!allPropertiesNames.add(key)) { + duplicatedArgNames.add(key); } + PropertyMappers.getKcKeyFromCliKey(key).ifPresent(s -> properties.put(s, value)); }, ignored -> {}); return properties; } + public static Set getDuplicatedArgNames() { + return Collections.unmodifiableSet(duplicatedArgNames); + } + + public static void clearDuplicatedArgNames() { + // after handling these duplicates, clear the memory + duplicatedArgNames = Collections.emptySet(); + } + public static void parseConfigArgs(List args, BiConsumer valueArgConsumer, Consumer unaryConsumer) { for (int i = 0; i < args.size(); i++) { String arg = args.get(i); diff --git a/quarkus/runtime/src/test/java/org/keycloak/quarkus/runtime/cli/PicocliTest.java b/quarkus/runtime/src/test/java/org/keycloak/quarkus/runtime/cli/PicocliTest.java index 094f7ecb2f2..f3bf23813e3 100644 --- a/quarkus/runtime/src/test/java/org/keycloak/quarkus/runtime/cli/PicocliTest.java +++ b/quarkus/runtime/src/test/java/org/keycloak/quarkus/runtime/cli/PicocliTest.java @@ -984,6 +984,43 @@ public class PicocliTest extends AbstractConfigurationTest { assertThat(nonRunningPicocli.getErrString(), containsString("Available only when health is enabled")); } + @Test + public void duplicatedCliOptions() { + var nonRunningPicocli = pseudoLaunch("start-dev", "--http-access-log-enabled=true", "--http-access-log-enabled=false"); + assertEquals(CommandLine.ExitCode.OK, nonRunningPicocli.exitCode); + assertThat(nonRunningPicocli.getOutString(), containsString("WARNING: Duplicated options present in CLI: --http-access-log-enabled")); + onAfter(); + + nonRunningPicocli = pseudoLaunch("start-dev", "--http-access-log-enabled=true", "--http-access-log-enabled=false", "--db=postgres", "--db=dev-mem"); + assertEquals(CommandLine.ExitCode.OK, nonRunningPicocli.exitCode); + assertThat(nonRunningPicocli.getOutString(), containsString("WARNING: Duplicated options present in CLI: --http-access-log-enabled, --db")); + onAfter(); + + nonRunningPicocli = pseudoLaunch("start-dev", + "--http-access-log-enabled=true", "--http-access-log-enabled=false", + "--db=postgres", "--db=dev-mem", + "--db-kind-my-store=mariadb", "--db-kind-my-store=dev-mem"); + assertEquals(CommandLine.ExitCode.OK, nonRunningPicocli.exitCode); + assertThat(nonRunningPicocli.getOutString(), containsString("Duplicated options present in CLI: --db-kind-my-store, --http-access-log-enabled, --db")); + onAfter(); + + nonRunningPicocli = pseudoLaunch("start-dev", "--non-existing=yes", "--non-existing=no"); + assertEquals(CommandLine.ExitCode.USAGE, nonRunningPicocli.exitCode); + assertThat(nonRunningPicocli.getOutString(), not(containsString("WARNING: Duplicated options present in CLI: --non-existing"))); + assertThat(nonRunningPicocli.getErrString(), containsString("Unknown option: '--non-existing'")); + onAfter(); + + nonRunningPicocli = pseudoLaunch("start-dev", "-Dsome.property=123", "-Dsome.property=456"); + assertEquals(CommandLine.ExitCode.OK, nonRunningPicocli.exitCode); + assertThat(nonRunningPicocli.getOutString(), containsString("WARNING: Duplicated options present in CLI: -Dsome.property")); + onAfter(); + + nonRunningPicocli = pseudoLaunch("start-dev", "something-wrong=asdf", "something-wrong=not-here"); + assertEquals(CommandLine.ExitCode.USAGE, nonRunningPicocli.exitCode); + assertThat(nonRunningPicocli.getOutString(), not(containsString("WARNING: Duplicated options present in CLI: something-wrong"))); + assertThat(nonRunningPicocli.getErrString(), containsString("Unknown option: 'something-wrong'")); + } + @Test public void httpOptimizedSerializers() { var nonRunningPicocli = pseudoLaunch("start-dev");