diff --git a/quarkus/runtime/pom.xml b/quarkus/runtime/pom.xml
index ab56c8dfb11..0155a9030d5 100644
--- a/quarkus/runtime/pom.xml
+++ b/quarkus/runtime/pom.xml
@@ -615,6 +615,12 @@
junit
test
+
+
+ org.hamcrest
+ hamcrest
+ test
+
diff --git a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/KeycloakMain.java b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/KeycloakMain.java
index d832958ba48..f507ba80cec 100644
--- a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/KeycloakMain.java
+++ b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/KeycloakMain.java
@@ -22,7 +22,6 @@ import static org.keycloak.quarkus.runtime.Environment.isDevProfile;
import static org.keycloak.quarkus.runtime.Environment.getProfileOrDefault;
import static org.keycloak.quarkus.runtime.Environment.isNonServerMode;
import static org.keycloak.quarkus.runtime.Environment.isTestLaunchMode;
-import static org.keycloak.quarkus.runtime.cli.Picocli.parseAndRun;
import static org.keycloak.quarkus.runtime.cli.command.AbstractStartCommand.OPTIMIZED_BUILD_OPTION_LONG;
import static org.keycloak.quarkus.runtime.cli.command.AbstractStartCommand.wasBuildEverRun;
import static org.keycloak.quarkus.runtime.cli.command.Start.isDevProfileNotAllowed;
@@ -104,7 +103,7 @@ public class KeycloakMain implements QuarkusApplication {
}
// parse arguments and execute any of the configured commands
- parseAndRun(cliArgs);
+ new Picocli().parseAndRun(cliArgs);
}
/**
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 53b87db7b4f..bdd5f995d4a 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
@@ -94,7 +94,7 @@ import picocli.CommandLine.Model.ISetter;
import picocli.CommandLine.Model.OptionSpec;
import picocli.CommandLine.Model.ArgGroupSpec;
-public final class Picocli {
+public class Picocli {
public static final String ARG_PREFIX = "--";
public static final String ARG_SHORT_PREFIX = "-";
@@ -105,10 +105,7 @@ public final class Picocli {
boolean includeBuildTime;
}
- private Picocli() {
- }
-
- public static void parseAndRun(List cliArgs) {
+ public void parseAndRun(List cliArgs) {
// perform two passes over the cli args. First without option validation to determine the current command, then with option validation enabled
CommandLine cmd = createCommandLine(spec -> spec
.addUnmatchedArgsBinding(CommandLine.Model.UnmatchedArgsBinding.forStringArrayConsumer(new ISetter() {
@@ -135,7 +132,7 @@ public final class Picocli {
exitCode = runReAugmentationIfNeeded(cliArgs, cmd, currentCommand);
} else {
PropertyMappers.sanitizeDisabledMappers();
- exitCode = cmd.execute(argArray);
+ exitCode = run(cmd, argArray);
}
exitOnFailure(exitCode, cmd);
@@ -146,7 +143,11 @@ public final class Picocli {
}
}
- private static CommandLine createCommandLineForCommand(List cliArgs, List commandLineList) {
+ protected int run(CommandLine cmd, String[] argArray) {
+ return cmd.execute(argArray);
+ }
+
+ private CommandLine createCommandLineForCommand(List cliArgs, List commandLineList) {
return createCommandLine(spec -> {
// use the incoming commandLineList from the initial parsing to determine the current command
CommandSpec currentSpec = spec;
@@ -177,7 +178,7 @@ public final class Picocli {
});
}
- private static void catchParameterException(ParameterException parEx, CommandLine cmd, String[] args) {
+ private void catchParameterException(ParameterException parEx, CommandLine cmd, String[] args) {
int exitCode;
try {
exitCode = cmd.getParameterExceptionHandler().handleParseException(parEx, args);
@@ -189,20 +190,20 @@ public final class Picocli {
exitOnFailure(exitCode, cmd);
}
- private static void catchProfileException(String message, Throwable cause, CommandLine cmd) {
+ private void catchProfileException(String message, Throwable cause, CommandLine cmd) {
ExecutionExceptionHandler errorHandler = new ExecutionExceptionHandler();
errorHandler.error(cmd.getErr(), message, cause);
exitOnFailure(CommandLine.ExitCode.USAGE, cmd);
}
- private static void exitOnFailure(int exitCode, CommandLine cmd) {
+ protected void exitOnFailure(int exitCode, CommandLine cmd) {
if (exitCode != cmd.getCommandSpec().exitCodeOnSuccess() && !Environment.isTestLaunchMode() || isRebuildCheck()) {
// hard exit wanted, as build failed and no subsequent command should be executed. no quarkus involved.
System.exit(exitCode);
}
}
- private static int runReAugmentationIfNeeded(List cliArgs, CommandLine cmd, CommandLine currentCommand) {
+ protected int runReAugmentationIfNeeded(List cliArgs, CommandLine cmd, CommandLine currentCommand) {
int exitCode = 0;
if (currentCommand == null) {
@@ -669,7 +670,7 @@ public final class Picocli {
return key.startsWith("kc.provider.file");
}
- public static CommandLine createCommandLine(Consumer consumer) {
+ public CommandLine createCommandLine(Consumer consumer) {
CommandSpec spec = CommandSpec.forAnnotatedObject(new Main()).name(Environment.getCommand());
consumer.accept(spec);
@@ -679,11 +680,15 @@ public final class Picocli {
cmd.setParameterExceptionHandler(new ShortErrorMessageHandler());
cmd.setHelpFactory(new HelpFactory());
cmd.getHelpSectionMap().put(SECTION_KEY_COMMAND_LIST, new SubCommandListRenderer());
- cmd.setErr(new PrintWriter(System.err, true));
+ cmd.setErr(getErrWriter());
return cmd;
}
+ protected PrintWriter getErrWriter() {
+ return new PrintWriter(System.err, true);
+ }
+
private static void addHelp(CommandSpec currentSpec) {
try {
currentSpec.addOption(OptionSpec.builder(Help.OPTION_NAMES)
@@ -795,7 +800,6 @@ public final class Picocli {
return mapper.getExpectedValues().iterator();
}
})
- .parameterConsumer(PropertyMapperParameterConsumer.INSTANCE)
.hidden(mapper.isHidden());
if (mapper.getDefaultValue().isPresent()) {
@@ -804,6 +808,14 @@ public final class Picocli {
if (mapper.getType() != null) {
optBuilder.type(mapper.getType());
+ if (mapper.isList()) {
+ // make picocli aware of the only list convention we allow
+ optBuilder.splitRegex(",");
+ } else if (mapper.getType().isEnum()) {
+ // prevent the auto-conversion that picocli does
+ // we validate the expected values later
+ optBuilder.type(String.class);
+ }
} else {
optBuilder.type(String.class);
}
diff --git a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/cli/PropertyMapperParameterConsumer.java b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/cli/PropertyMapperParameterConsumer.java
deleted file mode 100644
index 49c6980ed68..00000000000
--- a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/cli/PropertyMapperParameterConsumer.java
+++ /dev/null
@@ -1,78 +0,0 @@
-/*
- * Copyright 2021 Red Hat, Inc. and/or its affiliates
- * and other contributors as indicated by the @author tags.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.keycloak.quarkus.runtime.cli;
-
-import static org.keycloak.quarkus.runtime.cli.Picocli.ARG_PREFIX;
-
-import java.util.Stack;
-
-import picocli.CommandLine;
-import picocli.CommandLine.Model.ArgSpec;
-import picocli.CommandLine.Model.CommandSpec;
-import picocli.CommandLine.Model.OptionSpec;
-import picocli.CommandLine.ParameterException;
-
-public final class PropertyMapperParameterConsumer implements CommandLine.IParameterConsumer {
-
- static final CommandLine.IParameterConsumer INSTANCE = new PropertyMapperParameterConsumer();
-
- private PropertyMapperParameterConsumer() {
- // singleton
- }
-
- @Override
- public void consumeParameters(Stack args, ArgSpec argSpec,
- CommandSpec commandSpec) {
- if (argSpec instanceof OptionSpec) {
- validateOption(args, argSpec, commandSpec);
- }
- }
-
- private void validateOption(Stack args, ArgSpec argSpec, CommandSpec commandSpec) {
- OptionSpec option = (OptionSpec) argSpec;
- String name = String.join(", ", option.names());
- CommandLine commandLine = commandSpec.commandLine();
-
- if (args.isEmpty() || !isOptionValue(args.peek())) {
- throw new ParameterException(commandLine,
- "Missing required value. " + getExpectedMessage(argSpec, option, name));
- }
-
- // consumes the value, actual value validation will be performed later
- args.pop();
-
- if (!args.isEmpty() && isOptionValue(args.peek())) {
- throw new ParameterException(commandLine, getExpectedMessage(argSpec, option, name));
- }
- }
-
- private String getExpectedMessage(ArgSpec argSpec, OptionSpec option, String name) {
- return String.format("Option '%s' (%s) expects %s.%s", name, argSpec.paramLabel(),
- option.typeInfo().isMultiValue() ? "one or more comma separated values without whitespace": "a single value",
- getExpectedValuesMessage(argSpec.completionCandidates(), option.completionCandidates()));
- }
-
- private boolean isOptionValue(String arg) {
- return !(arg.startsWith(ARG_PREFIX) || arg.startsWith(Picocli.ARG_SHORT_PREFIX));
- }
-
- public static String getExpectedValuesMessage(Iterable specCandidates, Iterable optionCandidates) {
- return optionCandidates.iterator().hasNext() ? " Expected values are: " + String.join(", ", specCandidates) : "";
- }
-
-}
diff --git a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/cli/ShortErrorMessageHandler.java b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/cli/ShortErrorMessageHandler.java
index 78ec05a4016..251826336d9 100644
--- a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/cli/ShortErrorMessageHandler.java
+++ b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/cli/ShortErrorMessageHandler.java
@@ -1,26 +1,28 @@
package org.keycloak.quarkus.runtime.cli;
+import static java.lang.String.format;
+import static org.keycloak.quarkus.runtime.cli.command.AbstractStartCommand.OPTIMIZED_BUILD_OPTION_LONG;
+
+import java.io.PrintWriter;
+import java.util.Optional;
+import java.util.function.BooleanSupplier;
+import java.util.stream.Stream;
+
import org.keycloak.quarkus.runtime.cli.command.AbstractCommand;
import org.keycloak.quarkus.runtime.cli.command.Start;
import org.keycloak.quarkus.runtime.configuration.KcUnmatchedArgumentException;
import org.keycloak.quarkus.runtime.configuration.mappers.PropertyMapper;
import org.keycloak.quarkus.runtime.configuration.mappers.PropertyMappers;
-import java.io.PrintWriter;
-import java.util.stream.Stream;
-
import picocli.CommandLine;
import picocli.CommandLine.IParameterExceptionHandler;
+import picocli.CommandLine.MissingParameterException;
+import picocli.CommandLine.Model.ArgSpec;
import picocli.CommandLine.Model.CommandSpec;
+import picocli.CommandLine.Model.OptionSpec;
import picocli.CommandLine.ParameterException;
import picocli.CommandLine.UnmatchedArgumentException;
-import java.util.Optional;
-import java.util.function.BooleanSupplier;
-
-import static java.lang.String.format;
-import static org.keycloak.quarkus.runtime.cli.command.AbstractStartCommand.OPTIMIZED_BUILD_OPTION_LONG;
-
public class ShortErrorMessageHandler implements IParameterExceptionHandler {
@Override
@@ -70,6 +72,15 @@ public class ShortErrorMessageHandler implements IParameterExceptionHandler {
}
}
}
+ } else if (ex instanceof MissingParameterException) {
+ MissingParameterException mpe = (MissingParameterException)ex;
+ if (mpe.getMissing().size() == 1) {
+ ArgSpec spec = mpe.getMissing().get(0);
+ if (spec instanceof OptionSpec) {
+ OptionSpec option = (OptionSpec)spec;
+ errorMessage = getExpectedMessage(option);
+ }
+ }
}
writer.println(cmd.getColorScheme().errorText(errorMessage));
@@ -97,4 +108,15 @@ public class ShortErrorMessageHandler implements IParameterExceptionHandler {
private String[] getUnmatchedPartsByOptionSeparator(UnmatchedArgumentException uae, String separator) {
return uae.getUnmatched().get(0).split(separator);
}
+
+ private String getExpectedMessage(OptionSpec option) {
+ return String.format("Option '%s' (%s) expects %s.%s", String.join(", ", option.names()), option.paramLabel(),
+ option.typeInfo().isMultiValue() ? "one or more comma separated values without whitespace": "a single value",
+ getExpectedValuesMessage(option.completionCandidates()));
+ }
+
+ public static String getExpectedValuesMessage(Iterable specCandidates) {
+ return specCandidates.iterator().hasNext() ? " Expected values are: " + String.join(", ", specCandidates) : "";
+ }
+
}
diff --git a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/cli/command/AbstractStartCommand.java b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/cli/command/AbstractStartCommand.java
index 91bf436eeea..43b942cab5f 100644
--- a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/cli/command/AbstractStartCommand.java
+++ b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/cli/command/AbstractStartCommand.java
@@ -35,6 +35,8 @@ import static org.keycloak.quarkus.runtime.configuration.Configuration.getRawPer
public abstract class AbstractStartCommand extends AbstractCommand implements Runnable {
public static final String OPTIMIZED_BUILD_OPTION_LONG = "--optimized";
+
+ private boolean skipStart;
@Override
public void run() {
@@ -48,7 +50,9 @@ public abstract class AbstractStartCommand extends AbstractCommand implements Ru
executionError(spec.commandLine(), Messages.optimizedUsedForFirstStartup());
}
- KeycloakMain.start((ExecutionExceptionHandler) cmd.getExecutionExceptionHandler(), cmd.getErr(), cmd.getParseResult().originalArgs().toArray(new String[0]));
+ if (!skipStart) {
+ KeycloakMain.start((ExecutionExceptionHandler) cmd.getExecutionExceptionHandler(), cmd.getErr(), cmd.getParseResult().originalArgs().toArray(new String[0]));
+ }
}
protected void doBeforeRun() {
@@ -68,5 +72,9 @@ public abstract class AbstractStartCommand extends AbstractCommand implements Ru
protected EnumSet excludedCategories() {
return EnumSet.of(OptionCategory.IMPORT, OptionCategory.EXPORT);
}
+
+ public void setSkipStart(boolean skipStart) {
+ this.skipStart = skipStart;
+ }
}
diff --git a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/mappers/PropertyMapper.java b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/mappers/PropertyMapper.java
index a21152242b1..4bab8ba690d 100644
--- a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/mappers/PropertyMapper.java
+++ b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/mappers/PropertyMapper.java
@@ -40,7 +40,7 @@ import org.keycloak.config.Option;
import org.keycloak.config.OptionBuilder;
import org.keycloak.config.OptionCategory;
import org.keycloak.quarkus.runtime.cli.PropertyException;
-import org.keycloak.quarkus.runtime.cli.PropertyMapperParameterConsumer;
+import org.keycloak.quarkus.runtime.cli.ShortErrorMessageHandler;
import org.keycloak.quarkus.runtime.configuration.ConfigArgsConfigSource;
import org.keycloak.quarkus.runtime.configuration.KcEnvConfigSource;
import org.keycloak.quarkus.runtime.configuration.KeycloakConfigSourceProvider;
@@ -417,11 +417,15 @@ public class PropertyMapper {
validator.accept(this, value);
}
}
+
+ public boolean isList() {
+ return getOption().getType() == java.util.List.class;
+ }
public void validateValues(ConfigValue configValue, BiConsumer singleValidator) {
String value = configValue.getValue();
- boolean multiValued = getOption().getType() == java.util.List.class;
+ boolean multiValued = isList();
StringBuilder result = new StringBuilder();
String[] values = multiValued ? value.split(",") : new String[] { value };
@@ -462,10 +466,10 @@ public class PropertyMapper {
if (!expectedValues.isEmpty() && !expectedValues.contains(v) && getOption().isStrictExpectedValues()) {
throw new PropertyException(
String.format("Invalid value for option %s: %s.%s", getOptionAndSourceMessage(configValue), v,
- PropertyMapperParameterConsumer.getExpectedValuesMessage(expectedValues, expectedValues)));
+ ShortErrorMessageHandler.getExpectedValuesMessage(expectedValues)));
}
}
-
+
String getOptionAndSourceMessage(ConfigValue configValue) {
if (isCliOption(configValue)) {
return String.format("'%s'", this.getCliFormat());
diff --git a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/mappers/PropertyMappers.java b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/mappers/PropertyMappers.java
index bbeb0dc6084..9f7b3fb5922 100644
--- a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/mappers/PropertyMappers.java
+++ b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/mappers/PropertyMappers.java
@@ -39,12 +39,17 @@ import static org.keycloak.quarkus.runtime.configuration.KeycloakConfigSourcePro
public final class PropertyMappers {
public static String VALUE_MASK = "*******";
- private static final MappersConfig MAPPERS = new MappersConfig();
+ private static MappersConfig MAPPERS;
private static final Logger log = Logger.getLogger(PropertyMappers.class);
private PropertyMappers(){}
-
+
static {
+ reset();
+ }
+
+ public static void reset() {
+ MAPPERS = new MappersConfig();
MAPPERS.addAll(CachingPropertyMappers.getClusteringPropertyMappers());
MAPPERS.addAll(DatabasePropertyMappers.getDatabasePropertyMappers());
MAPPERS.addAll(HostnameV2PropertyMappers.getHostnamePropertyMappers());
diff --git a/quarkus/runtime/src/test/java/or/keycloak/quarkus/runtime/cli/PicocliTest.java b/quarkus/runtime/src/test/java/or/keycloak/quarkus/runtime/cli/PicocliTest.java
new file mode 100644
index 00000000000..6a6546be7d6
--- /dev/null
+++ b/quarkus/runtime/src/test/java/or/keycloak/quarkus/runtime/cli/PicocliTest.java
@@ -0,0 +1,209 @@
+/*
+ * Copyright 2024 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package or.keycloak.quarkus.runtime.cli;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.containsString;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.Arrays;
+import java.util.List;
+
+import org.junit.Test;
+import org.keycloak.quarkus.runtime.cli.Picocli;
+import org.keycloak.quarkus.runtime.cli.command.AbstractStartCommand;
+import org.keycloak.quarkus.runtime.configuration.ConfigArgsConfigSource;
+import org.keycloak.quarkus.runtime.configuration.test.AbstractConfigurationTest;
+
+import io.smallrye.config.SmallRyeConfig;
+import picocli.CommandLine;
+import picocli.CommandLine.Help;
+
+public class PicocliTest extends AbstractConfigurationTest {
+
+ // TODO: could utilize CLIResult
+ private class NonRunningPicocli extends Picocli {
+
+ final StringWriter err = new StringWriter();
+ SmallRyeConfig config;
+ int exitCode = Integer.MAX_VALUE;
+
+ String getErrString() {
+ // normalize line endings - TODO: could also normalize non-printable chars
+ // but for now those are part of the expected output
+ return System.lineSeparator().equals("\n") ? err.toString()
+ : err.toString().replace(System.lineSeparator(), "\n");
+ }
+
+ @Override
+ protected PrintWriter getErrWriter() {
+ return new PrintWriter(err, true);
+ }
+
+ @Override
+ protected void exitOnFailure(int exitCode, CommandLine cmd) {
+ this.exitCode = exitCode;
+ }
+
+ protected int runReAugmentationIfNeeded(List cliArgs, CommandLine cmd, CommandLine currentCommand) {
+ throw new AssertionError("Should not reaugment");
+ };
+
+ @Override
+ protected int run(CommandLine cmd, String[] argArray) {
+ skipStart(cmd);
+ return super.run(cmd, argArray);
+ }
+
+ private void skipStart(CommandLine cmd) {
+ for (CommandLine sub : cmd.getSubcommands().values()) {
+ if (sub.getCommand() instanceof AbstractStartCommand) {
+ ((AbstractStartCommand) (sub.getCommand())).setSkipStart(true);
+ }
+ skipStart(sub);
+ }
+ }
+
+ @Override
+ public void parseAndRun(List cliArgs) {
+ ConfigArgsConfigSource.setCliArgs(cliArgs.toArray(String[]::new));
+ config = createConfig();
+ super.parseAndRun(cliArgs);
+ }
+
+ };
+
+ NonRunningPicocli pseudoLaunch(String... args) {
+ NonRunningPicocli nonRunningPicocli = new NonRunningPicocli();
+ nonRunningPicocli.parseAndRun(Arrays.asList(args));
+ return nonRunningPicocli;
+ }
+
+ @Test
+ public void testNegativeArgument() {
+ NonRunningPicocli nonRunningPicocli = pseudoLaunch("start-dev");
+ assertEquals(CommandLine.ExitCode.OK, nonRunningPicocli.exitCode);
+ assertEquals("1h",
+ nonRunningPicocli.config.getConfigValue("quarkus.http.ssl.certificate.reload-period").getValue());
+
+ nonRunningPicocli = pseudoLaunch("start-dev", "--https-certificates-reload-period=-1");
+ assertEquals(CommandLine.ExitCode.OK, nonRunningPicocli.exitCode);
+ assertNull(nonRunningPicocli.config.getConfigValue("quarkus.http.ssl.certificate.reload-period").getValue());
+ }
+
+ @Test
+ public void testInvalidArgumentType() {
+ NonRunningPicocli nonRunningPicocli = pseudoLaunch("start-dev", "--http-port=a");
+ assertEquals(CommandLine.ExitCode.USAGE, nonRunningPicocli.exitCode);
+ assertThat(nonRunningPicocli.getErrString(),
+ containsString("Invalid value for option '--http-port': 'a' is not an int"));
+ }
+
+ @Test
+ public void failWrongEnumValue() {
+ NonRunningPicocli nonRunningPicocli = pseudoLaunch("start-dev", "--log-console-level=wrong");
+ assertEquals(CommandLine.ExitCode.USAGE, nonRunningPicocli.exitCode);
+ assertThat(nonRunningPicocli.getErrString(), containsString(
+ "Invalid value for option '--log-console-level': wrong. Expected values are: off, fatal, error, warn, info, debug, trace, all"));
+ }
+
+ @Test
+ public void failMissingOptionValue() {
+ NonRunningPicocli nonRunningPicocli = pseudoLaunch("start-dev", "--db");
+ assertEquals(CommandLine.ExitCode.USAGE, nonRunningPicocli.exitCode);
+ assertThat(nonRunningPicocli.getErrString(), containsString(
+ "Option '--db' (vendor) expects a single value. Expected values are: dev-file, dev-mem, mariadb, mssql, mysql, oracle, postgres"));
+ }
+
+ @Test
+ public void failMultipleOptionValue() {
+ NonRunningPicocli nonRunningPicocli = pseudoLaunch("build", "--db", "mysql", "postgres");
+ assertEquals(CommandLine.ExitCode.USAGE, nonRunningPicocli.exitCode);
+ assertThat(nonRunningPicocli.getErrString(), containsString("Unknown option: 'postgres'"));
+ }
+
+ @Test
+ public void failMultipleMultiOptionValue() {
+ NonRunningPicocli nonRunningPicocli = pseudoLaunch("build", "--features", "linkedin-oauth", "account3");
+ assertEquals(CommandLine.ExitCode.USAGE, nonRunningPicocli.exitCode);
+ assertThat(nonRunningPicocli.getErrString(), containsString("Unknown option: 'account3'"));
+ }
+
+ @Test
+ public void failMissingMultiOptionValue() {
+ NonRunningPicocli nonRunningPicocli = pseudoLaunch("build", "--features");
+ assertEquals(CommandLine.ExitCode.USAGE, nonRunningPicocli.exitCode);
+ assertThat(nonRunningPicocli.getErrString(), containsString(
+ "Option '--features' (feature) expects one or more comma separated values without whitespace. Expected values are:"));
+ }
+
+ @Test
+ public void failInvalidMultiOptionValue() {
+ NonRunningPicocli nonRunningPicocli = pseudoLaunch("build", "--features", "xyz,account3");
+ assertEquals(CommandLine.ExitCode.USAGE, nonRunningPicocli.exitCode);
+ assertThat(nonRunningPicocli.getErrString(),
+ containsString("xyz is an unrecognized feature, it should be one of"));
+ }
+
+ @Test
+ public void failUnknownOption() {
+ NonRunningPicocli nonRunningPicocli = pseudoLaunch("build", "--nosuch");
+ assertEquals(CommandLine.ExitCode.USAGE, nonRunningPicocli.exitCode);
+ assertThat(nonRunningPicocli.getErrString(), containsString("Unknown option: '--nosuch'"));
+ }
+
+ @Test
+ public void failUnknownOptionWhitespaceSeparatorNotShowingValue() {
+ NonRunningPicocli nonRunningPicocli = pseudoLaunch("start", "--db-pasword", "mytestpw");
+ assertEquals(CommandLine.ExitCode.USAGE, nonRunningPicocli.exitCode);
+ assertThat(nonRunningPicocli.getErrString(), containsString(Help.defaultColorScheme(Help.Ansi.AUTO)
+ .errorText("Unknown option: '--db-pasword'")
+ + "\nPossible solutions: --db-url, --db-url-host, --db-url-database, --db-url-port, --db-url-properties, --db-username, --db-password, --db-schema, --db-pool-initial-size, --db-pool-min-size, --db-pool-max-size, --db-driver, --db"));
+ }
+
+ @Test
+ public void failUnknownOptionEqualsSeparatorNotShowingValue() {
+ NonRunningPicocli nonRunningPicocli = pseudoLaunch("start", "--db-pasword=mytestpw");
+ assertEquals(CommandLine.ExitCode.USAGE, nonRunningPicocli.exitCode);
+ assertThat(nonRunningPicocli.getErrString(), containsString(Help.defaultColorScheme(Help.Ansi.AUTO)
+ .errorText("Unknown option: '--db-pasword'")
+ + "\nPossible solutions: --db-url, --db-url-host, --db-url-database, --db-url-port, --db-url-properties, --db-username, --db-password, --db-schema, --db-pool-initial-size, --db-pool-min-size, --db-pool-max-size, --db-driver, --db"));
+ }
+
+ @Test
+ public void failWithFirstOptionOnMultipleUnknownOptions() {
+ NonRunningPicocli nonRunningPicocli = pseudoLaunch("start", "--db-username=foobar", "--db-pasword=mytestpw",
+ "--foobar=barfoo");
+ assertEquals(CommandLine.ExitCode.USAGE, nonRunningPicocli.exitCode);
+ assertThat(nonRunningPicocli.getErrString(), containsString(Help.defaultColorScheme(Help.Ansi.AUTO)
+ .errorText("Unknown option: '--db-pasword'")
+ + "\nPossible solutions: --db-url, --db-url-host, --db-url-database, --db-url-port, --db-url-properties, --db-username, --db-password, --db-schema, --db-pool-initial-size, --db-pool-min-size, --db-pool-max-size, --db-driver, --db"));
+ }
+
+ @Test
+ public void failSingleParamWithSpace() {
+ NonRunningPicocli nonRunningPicocli = pseudoLaunch("start", "--db postgres");
+ assertEquals(CommandLine.ExitCode.USAGE, nonRunningPicocli.exitCode);
+ assertThat(nonRunningPicocli.getErrString(), containsString(
+ "Option: '--db postgres' is not expected to contain whitespace, please remove any unnecessary quoting/escaping"));
+ }
+
+}
diff --git a/quarkus/runtime/src/test/java/org/keycloak/quarkus/runtime/configuration/test/AbstractConfigurationTest.java b/quarkus/runtime/src/test/java/org/keycloak/quarkus/runtime/configuration/test/AbstractConfigurationTest.java
index 47cf4f527d9..8fcc28aefd8 100644
--- a/quarkus/runtime/src/test/java/org/keycloak/quarkus/runtime/configuration/test/AbstractConfigurationTest.java
+++ b/quarkus/runtime/src/test/java/org/keycloak/quarkus/runtime/configuration/test/AbstractConfigurationTest.java
@@ -29,6 +29,7 @@ import org.keycloak.Config;
import org.keycloak.quarkus.runtime.configuration.Configuration;
import org.keycloak.quarkus.runtime.configuration.KeycloakConfigSourceProvider;
import org.keycloak.quarkus.runtime.configuration.MicroProfileConfigProvider;
+import org.keycloak.quarkus.runtime.configuration.mappers.PropertyMappers;
import java.lang.reflect.Field;
import java.util.HashMap;
@@ -109,6 +110,7 @@ public abstract class AbstractConfigurationTest {
}
SmallRyeConfigProviderResolver.class.cast(ConfigProviderResolver.instance()).releaseConfig(ConfigProvider.getConfig());
+ PropertyMappers.reset();
}
protected Config.Scope initConfig(String... scope) {
diff --git a/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/OptionValidationTest.java b/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/OptionValidationTest.java
deleted file mode 100644
index 5139014e9db..00000000000
--- a/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/OptionValidationTest.java
+++ /dev/null
@@ -1,107 +0,0 @@
-/*
- * Copyright 2021 Red Hat, Inc. and/or its affiliates
- * and other contributors as indicated by the @author tags.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.keycloak.it.cli;
-
-import org.junit.jupiter.api.Test;
-import org.keycloak.it.junit5.extension.CLIResult;
-import org.keycloak.it.junit5.extension.CLITest;
-import org.keycloak.it.junit5.extension.ConfigurationTestResource;
-
-import io.quarkus.test.common.QuarkusTestResource;
-import io.quarkus.test.junit.main.Launch;
-import io.quarkus.test.junit.main.LaunchResult;
-import org.keycloak.it.utils.KeycloakDistribution;
-
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.hamcrest.Matchers.containsString;
-import static org.junit.jupiter.api.Assertions.assertEquals;
-
-@QuarkusTestResource(value = ConfigurationTestResource.class, restrictToAnnotatedClass = true)
-@CLITest
-public class OptionValidationTest {
-
- @Test
- @Launch({"build", "--db"})
- public void failMissingOptionValue(LaunchResult result) {
- CLIResult cliResult = (CLIResult) result;
- assertThat(cliResult.getErrorOutput(), containsString("Missing required value. Option '--db' (vendor) expects a single value. Expected values are: dev-file, dev-mem, mariadb, mssql, mysql, oracle, postgres"));
- }
-
- @Test
- @Launch({"build", "--db", "mysql", "postgres"})
- public void failMultipleOptionValue(LaunchResult result) {
- CLIResult cliResult = (CLIResult) result;
- assertThat(cliResult.getErrorOutput(), containsString("Option '--db' (vendor) expects a single value. Expected values are: dev-file, dev-mem, mariadb, mssql, mysql, oracle, postgres"));
- }
-
- @Test
- @Launch({"build", "--features", "linkedin-oauth", "account3"})
- public void failMultipleMultiOptionValue(LaunchResult result) {
- CLIResult cliResult = (CLIResult) result;
- assertThat(cliResult.getErrorOutput(), containsString("Option '--features' (feature) expects one or more comma separated values without whitespace. Expected values are: "));
- }
-
- @Test
- @Launch({"build", "--features", "xyz,account3"})
- public void failInvalidMultiOptionValue(LaunchResult result) {
- CLIResult cliResult = (CLIResult) result;
- assertThat(cliResult.getErrorOutput(), containsString("xyz is an unrecognized feature, it should be one of"));
- }
-
- @Test
- @Launch({"build", "--nosuch"})
- public void failUnknownOption(LaunchResult result) {
- CLIResult cliResult = (CLIResult) result;
- assertEquals("Unknown option: '--nosuch'\n" +
- "Try '" + KeycloakDistribution.SCRIPT_CMD + " build --help' for more information on the available options.", cliResult.getErrorOutput());
- }
-
- @Test
- @Launch({"start", "--db-pasword", "mytestpw"})
- public void failUnknownOptionWhitespaceSeparatorNotShowingValue(LaunchResult result) {
- CLIResult cliResult = (CLIResult) result;
- assertEquals("Unknown option: '--db-pasword'\n" +
- "Possible solutions: --db-url, --db-url-host, --db-url-database, --db-url-port, --db-url-properties, --db-username, --db-password, --db-schema, --db-pool-initial-size, --db-pool-min-size, --db-pool-max-size, --db-driver, --db\n" +
- "Try '" + KeycloakDistribution.SCRIPT_CMD + " start --help' for more information on the available options.", cliResult.getErrorOutput());
- }
-
- @Test
- @Launch({"start", "--db-pasword=mytestpw"})
- public void failUnknownOptionEqualsSeparatorNotShowingValue(LaunchResult result) {
- CLIResult cliResult = (CLIResult) result;
- assertEquals("Unknown option: '--db-pasword'\n" +
- "Possible solutions: --db-url, --db-url-host, --db-url-database, --db-url-port, --db-url-properties, --db-username, --db-password, --db-schema, --db-pool-initial-size, --db-pool-min-size, --db-pool-max-size, --db-driver, --db\n" +
- "Try '" + KeycloakDistribution.SCRIPT_CMD + " start --help' for more information on the available options.", cliResult.getErrorOutput());
- }
-
- @Test
- @Launch({"start", "--db-username=foobar", "--db-pasword=mytestpw", "--foobar=barfoo"})
- public void failWithFirstOptionOnMultipleUnknownOptions(LaunchResult result) {
- CLIResult cliResult = (CLIResult) result;
- assertEquals("Unknown option: '--db-pasword'\n" +
- "Possible solutions: --db-url, --db-url-host, --db-url-database, --db-url-port, --db-url-properties, --db-username, --db-password, --db-schema, --db-pool-initial-size, --db-pool-min-size, --db-pool-max-size, --db-driver, --db\n" +
- "Try '" + KeycloakDistribution.SCRIPT_CMD + " start --help' for more information on the available options.", cliResult.getErrorOutput());
- }
-
- @Test
- @Launch({"start", "--db postgres"})
- void failSingleParamWithSpace(LaunchResult result) {
- CLIResult cliResult = (CLIResult) result;
- cliResult.assertError("Option: '--db postgres' is not expected to contain whitespace, please remove any unnecessary quoting/escaping");
- }
-}