fix: refining arg handling (#44579)

relying on picocli parsing as much as possible

closes: #44578

Signed-off-by: Steve Hawkins <shawkins@redhat.com>
This commit is contained in:
Steven Hawkins 2026-01-14 10:57:48 -05:00 committed by GitHub
parent f09c906f87
commit a4df602f62
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
18 changed files with 94 additions and 241 deletions

View file

@ -1,12 +0,0 @@
package org.keycloak.config;
public class ClassLoaderOptions {
public static final String QUARKUS_REMOVED_ARTIFACTS_PROPERTY = "quarkus.class-loading.removed-artifacts";
public static final Option<String> IGNORE_ARTIFACTS = new OptionBuilder<>("class-loader-ignore-artifacts", String.class)
.category(OptionCategory.GENERAL)
.hidden()
.buildTime(true)
.build();
}

View file

@ -19,6 +19,7 @@ package org.keycloak.quarkus.runtime;
import java.io.File;
import java.io.FilenameFilter;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
@ -119,7 +120,7 @@ public final class Environment {
public static Map<String, File> getProviderFiles() {
Path providersPath = Environment.getProvidersPath().orElse(null);
if (providersPath == null) {
if (providersPath == null || !Files.exists(providersPath)) {
return Collections.emptyMap();
}

View file

@ -17,7 +17,6 @@
package org.keycloak.quarkus.runtime;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ForkJoinPool;
@ -27,7 +26,6 @@ import org.keycloak.common.Version;
import org.keycloak.infinispan.util.InfinispanUtils;
import org.keycloak.quarkus.runtime.cli.ExecutionExceptionHandler;
import org.keycloak.quarkus.runtime.cli.Picocli;
import org.keycloak.quarkus.runtime.cli.PropertyException;
import org.keycloak.quarkus.runtime.cli.command.AbstractNonServerCommand;
import org.keycloak.quarkus.runtime.cli.command.DryRunMixin;
import org.keycloak.quarkus.runtime.configuration.PersistedConfigSource;
@ -80,24 +78,12 @@ public class KeycloakMain implements QuarkusApplication {
}
public static void main(String[] args, Picocli picocli) {
List<String> cliArgs = null;
try {
cliArgs = Picocli.parseArgs(args);
} catch (PropertyException e) {
picocli.usageException(e.getMessage(), e.getCause());
return;
}
List<String> cliArgs = List.of(args.length == 0 ? new String[] {"-h"} : args);
if (DryRunMixin.isDryRunBuild() && (cliArgs.contains(DryRunMixin.DRYRUN_OPTION_LONG) || Boolean.valueOf(System.getenv().get(DryRunMixin.KC_DRY_RUN_ENV)))) {
PersistedConfigSource.getInstance().useDryRunProperties();
}
if (cliArgs.isEmpty()) {
cliArgs = new ArrayList<>(cliArgs);
// default to show help message
cliArgs.add("-h");
}
// parse arguments and execute any of the configured commands
picocli.parseAndRun(cliArgs);
}

View file

@ -103,6 +103,7 @@ public class Picocli {
private Ansi colorMode = hasColorSupport() ? Ansi.ON : Ansi.OFF;
private IncludeOptions options;
private Set<String> duplicatedOptionsNames = new HashSet<String>();
public static boolean hasColorSupport() {
return QuarkusConsole.hasColorSupport();
@ -139,31 +140,37 @@ public class Picocli {
} else {
currentCommand = null;
}
// any unrecognized args can now be normalized to our property mapper based argument expectations
Map<String, String> normalizedArgs = new LinkedHashMap<String, String>();
List<String> unknown = new ArrayList<String>();
ConfigArgsConfigSource.parseConfigArgs(unrecognizedArgs, (k, v) -> {
if (normalizedArgs.put(k, v) != null) {
duplicatedOptionsNames.add(k);
}
}, unknown::add);
unrecognizedArgs = null;
ConfigArgsConfigSource.setCliArgs(normalizedArgs.entrySet().stream().map(e -> e.getKey() + "=" + e.getValue()).toArray(String[]::new));
initConfig(currentCommand);
if (!unrecognizedArgs.isEmpty() && options.allowUnrecognized) {
// TODO: further refactor this as these args should be the source for ConfigArgsConfigSource
unrecognizedArgs.removeIf(arg -> {
boolean hasArg = false;
if (arg.contains("=")) {
arg = arg.substring(0, arg.indexOf("="));
hasArg = true;
}
PropertyMapper<?> mapper = PropertyMappers.getMapperByCliKey(arg);
if (mapper != null) {
if (!hasArg) {
addCommandOptions(cl, currentCommand);
throw new MissingParameterException(cl, cl.getCommandSpec().optionsMap().get(arg), null);
}
return true;
}
return false;
});
// now that the property mappers are properly initalized further refine the args
if (options.allowUnrecognized) {
normalizedArgs.keySet().removeIf(arg -> PropertyMappers.getMapperByCliKey(arg) != null || arg.startsWith(ConfigArgsConfigSource.SPI_OPTION_PREFIX));
}
if (!unrecognizedArgs.isEmpty()) {
unknown.forEach(arg -> {
if (PropertyMappers.getMapperByCliKey(arg) != null) {
addCommandOptions(cl, currentCommand);
throw new MissingParameterException(cl, cl.getCommandSpec().optionsMap().get(arg), null);
} else if (arg.startsWith(ConfigArgsConfigSource.SPI_OPTION_PREFIX)) {
throw new PropertyException(format("spi argument %s requires a value.", arg));
}
});
unknown.addAll(normalizedArgs.keySet());
if (!unknown.isEmpty()) {
addCommandOptions(cl, currentCommand);
throw new KcUnmatchedArgumentException(cl, unrecognizedArgs);
throw new KcUnmatchedArgumentException(cl, unknown);
}
if (isHelpRequested(result)) {
@ -823,33 +830,6 @@ public class Picocli {
getOutWriter().println(message);
}
public static List<String> parseArgs(String[] rawArgs) throws PropertyException {
if (rawArgs.length == 0) {
return List.of();
}
// makes sure cli args are available to the config source
ConfigArgsConfigSource.setCliArgs(rawArgs);
// TODO: ignore properties for providers for now, need to fetch them from the providers, otherwise CLI will complain about invalid options
// also ignores system properties as they are set when starting the JVM
// change this once we are able to obtain properties from providers
List<String> args = new ArrayList<>();
ConfigArgsConfigSource.parseConfigArgs(List.of(rawArgs), (arg, value) -> {
if (!arg.startsWith(ConfigArgsConfigSource.SPI_OPTION_PREFIX) && !arg.startsWith("-D")) {
args.add(arg + "=" + value);
}
}, arg -> {
if (arg.startsWith(ConfigArgsConfigSource.SPI_OPTION_PREFIX)) {
throw new PropertyException(format("spi argument %s requires a value.", arg));
}
if (!arg.startsWith("-D")) {
args.add(arg);
}
});
return args;
}
public void checkChangesInBuildOptionsDuringAutoBuild(PrintWriter out) {
StringBuilder options = new StringBuilder();
@ -913,6 +893,7 @@ public class Picocli {
}
public void build() throws Throwable {
Environment.setRebuild();
QuarkusEntryPoint.main();
}
@ -923,10 +904,8 @@ public class Picocli {
this.parsedCommand = Optional.ofNullable(command);
options = getIncludeOptions(command);
if (!Environment.isRebuilt() && command instanceof AbstractAutoBuildCommand
&& !command.isOptimized()) {
Environment.setRebuildCheck(true);
}
Environment.setRebuildCheck(!Environment.isRebuilt() && command instanceof AbstractAutoBuildCommand
&& !command.isOptimized());
String profile = Optional.ofNullable(org.keycloak.common.util.Environment.getProfile())
.or(() -> parsedCommand.map(AbstractCommand::getInitProfile)).orElse(Environment.PROD_PROFILE_VALUE);
@ -939,10 +918,8 @@ 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();
}
}

View file

@ -17,26 +17,19 @@
package org.keycloak.quarkus.runtime.cli.command;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import org.keycloak.quarkus.runtime.Environment;
import org.keycloak.quarkus.runtime.cli.Picocli;
import org.keycloak.quarkus.runtime.configuration.ConfigArgsConfigSource;
import org.keycloak.quarkus.runtime.configuration.Configuration;
import org.keycloak.quarkus.runtime.configuration.mappers.PropertyMapper;
import org.keycloak.quarkus.runtime.configuration.mappers.PropertyMappers;
import picocli.CommandLine;
import static org.keycloak.quarkus.runtime.Environment.isDevMode;
import static org.keycloak.quarkus.runtime.Environment.isDevProfile;
import static org.keycloak.quarkus.runtime.Environment.isRebuildCheck;
import static org.keycloak.quarkus.runtime.configuration.ConfigArgsConfigSource.parseConfigArgs;
import static org.keycloak.quarkus.runtime.configuration.mappers.PropertyMappers.maskValue;
public abstract class AbstractAutoBuildCommand extends AbstractCommand {
@ -94,7 +87,7 @@ public abstract class AbstractAutoBuildCommand extends AbstractCommand {
directBuild();
if(!isDevMode()) {
spec.commandLine().getOut().printf("Next time you run the server, just run:%n%n\t%s %s %s%n%n", Environment.getCommand(), String.join(" ", getSanitizedRuntimeCliOptions()), OPTIMIZED_BUILD_OPTION_LONG);
spec.commandLine().getOut().printf("Next time you run the server, just add %s to the command to ensure this build is used.\n", OPTIMIZED_BUILD_OPTION_LONG);
}
}
@ -106,26 +99,6 @@ public abstract class AbstractAutoBuildCommand extends AbstractCommand {
build.runCommand();
}
/**
* checks the raw cli input for possible credentials / properties which should be masked,
* and masks them.
* @return a list of potentially masked properties in CLI format, e.g. `--db-password=*******`
* instead of the actual passwords value.
*/
private static List<String> getSanitizedRuntimeCliOptions() {
List<String> properties = new ArrayList<>();
parseConfigArgs(ConfigArgsConfigSource.getAllCliArgs(), (key, value) -> {
PropertyMapper<?> mapper = PropertyMappers.getMapperByCliKey(key);
if (mapper == null || mapper.isRunTime()) {
properties.add(key + "=" + maskValue(value, mapper));
}
}, properties::add);
return properties;
}
@Override
protected void runCommand() {
doBeforeRun();

View file

@ -17,20 +17,17 @@
package org.keycloak.quarkus.runtime.cli.command;
import java.util.Optional;
import org.keycloak.quarkus.runtime.Environment;
import org.keycloak.quarkus.runtime.Messages;
import org.keycloak.quarkus.runtime.configuration.Configuration;
import org.keycloak.quarkus.runtime.configuration.IgnoredArtifacts;
import org.keycloak.quarkus.runtime.configuration.PersistedConfigSource;
import io.quarkus.bootstrap.runner.RunnerClassLoader;
import io.quarkus.runtime.LaunchMode;
import io.smallrye.config.ConfigValue;
import picocli.CommandLine;
import picocli.CommandLine.Command;
import static org.keycloak.config.ClassLoaderOptions.QUARKUS_REMOVED_ARTIFACTS_PROPERTY;
import static org.keycloak.config.DatabaseOptions.DB;
import static org.keycloak.quarkus.runtime.Environment.getHomePath;
import static org.keycloak.quarkus.runtime.Environment.isDevProfile;
@ -59,6 +56,8 @@ public final class Build extends AbstractCommand {
public static final String NAME = "build";
public static final String QUARKUS_REMOVED_ARTIFACTS_PROPERTY = "quarkus.class-loading.removed-artifacts";
@CommandLine.Mixin
HelpAllMixin helpAllMixin;
@ -70,17 +69,15 @@ public final class Build extends AbstractCommand {
checkProfileAndDb();
// validate before setting that we're rebuilding so that runtime options are still seen
// the validation and setting the artifacts to remove need to be done without the current persisted properties
PersistedConfigSource.getInstance().runWithDisabled(() -> {
validateConfig();
System.setProperty(QUARKUS_REMOVED_ARTIFACTS_PROPERTY, String.join(",", IgnoredArtifacts.getDefaultIgnoredArtifacts()));
return null;
});
Environment.setRebuild();
picocli.println("Updating the configuration and installing your custom providers, if any. Please wait.");
try {
configureBuildClassLoader();
beforeReaugmentationOnWindows();
if (!Boolean.TRUE.equals(dryRunMixin.dryRun)) {
picocli.build();
@ -99,13 +96,6 @@ public final class Build extends AbstractCommand {
}
}
private static void configureBuildClassLoader() {
// ignored artifacts must be set prior to starting re-augmentation
Optional.ofNullable(Configuration.getNonPersistedConfigValue(QUARKUS_REMOVED_ARTIFACTS_PROPERTY))
.map(ConfigValue::getValue)
.ifPresent(s -> System.setProperty(QUARKUS_REMOVED_ARTIFACTS_PROPERTY, s));
}
private void checkProfileAndDb() {
if (Environment.isDevProfile()) {
String cmd = picocli.getParsedCommand().map(AbstractCommand::getName).orElse(getName());

View file

@ -20,7 +20,6 @@ package org.keycloak.quarkus.runtime.configuration;
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;
@ -31,11 +30,11 @@ import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.keycloak.quarkus.runtime.cli.command.Main;
import org.keycloak.quarkus.runtime.configuration.mappers.PropertyMapper;
import org.keycloak.quarkus.runtime.configuration.mappers.PropertyMappers;
import io.smallrye.config.PropertiesConfigSource;
import static org.keycloak.quarkus.runtime.cli.Picocli.ARG_PREFIX;
import static org.keycloak.quarkus.runtime.cli.Picocli.ARG_SHORT_PREFIX;
/**
@ -53,12 +52,14 @@ 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<String> duplicatedArgNames;
protected ConfigArgsConfigSource() {
super(parseArguments(), NAME, 600);
}
/**
* Should only be called with sanitized args --property=value
*/
public static void setCliArgs(String... args) {
System.setProperty(CLI_ARGS,
Stream.of(args).map(arg -> arg.replaceAll(",", ",,")).collect(Collectors.joining(", ")));
@ -106,28 +107,14 @@ public class ConfigArgsConfigSource extends PropertiesConfigSource {
private static Map<String, String> parseArguments() {
final Map<String, String> properties = new HashMap<>();
final Set<String> allPropertiesNames = new HashSet<>();
duplicatedArgNames = new HashSet<>();
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<String> getDuplicatedArgNames() {
return Collections.unmodifiableSet(duplicatedArgNames);
}
public static void clearDuplicatedArgNames() {
// after handling these duplicates, clear the memory
duplicatedArgNames = Collections.emptySet();
}
public static void parseConfigArgs(List<String> args, BiConsumer<String, String> valueArgConsumer, Consumer<String> unaryConsumer) {
for (int i = 0; i < args.size(); i++) {
String arg = args.get(i);
@ -147,12 +134,7 @@ public class ConfigArgsConfigSource extends PropertiesConfigSource {
unaryConsumer.accept(arg);
continue;
}
PropertyMapper<?> mapper = PropertyMappers.getMapperByCliKey(key);
// the weaknesses here:
// - runs before propertymapper sanitizing
// - needs to know all of the short name options that accept a value
// - Even though We've explicitly disabled PosixClusteredShortOptionsAllowed, it may not know all of the picocli parsing rules.
if (mapper != null || SHORT_OPTIONS_ACCEPTING_VALUE.contains(key) || arg.startsWith(SPI_OPTION_PREFIX)) {
if (arg.startsWith(ARG_PREFIX)) {
i++; // consume next as a value to the key
value = args.get(i);
} else {

View file

@ -1,33 +0,0 @@
package org.keycloak.quarkus.runtime.configuration.mappers;
import java.util.List;
import org.keycloak.config.ClassLoaderOptions;
import org.keycloak.quarkus.runtime.Environment;
import org.keycloak.quarkus.runtime.configuration.IgnoredArtifacts;
import io.smallrye.config.ConfigSourceInterceptorContext;
import static org.keycloak.config.ClassLoaderOptions.QUARKUS_REMOVED_ARTIFACTS_PROPERTY;
import static org.keycloak.quarkus.runtime.configuration.mappers.PropertyMapper.fromOption;
final class ClassLoaderPropertyMappers implements PropertyMapperGrouping {
@Override
public List<PropertyMapper<?>> getPropertyMappers() {
return List.of(
fromOption(ClassLoaderOptions.IGNORE_ARTIFACTS)
.to(QUARKUS_REMOVED_ARTIFACTS_PROPERTY)
.transformer(ClassLoaderPropertyMappers::resolveIgnoredArtifacts)
.build()
);
}
private static String resolveIgnoredArtifacts(String value, ConfigSourceInterceptorContext context) {
if (Environment.isRebuildCheck() || Environment.isRebuild()) {
return String.join(",", IgnoredArtifacts.getDefaultIgnoredArtifacts());
}
return value;
}
}

View file

@ -48,7 +48,7 @@ public final class PropertyMappers {
private final static List<PropertyMapperGrouping> GROUPINGS;
static {
GROUPINGS = List.of(new CachingPropertyMappers(), new DatabasePropertyMappers(),
new ConfigKeystorePropertyMappers(), new EventPropertyMappers(), new ClassLoaderPropertyMappers(),
new ConfigKeystorePropertyMappers(), new EventPropertyMappers(),
new ExportPropertyMappers(), new BootstrapAdminPropertyMappers(), new HostnameV2PropertyMappers(),
new HttpPropertyMappers(), new HttpAccessLogPropertyMappers(), new HealthPropertyMappers(),
new FeaturePropertyMappers(), new ImportPropertyMappers(), new ManagementPropertyMappers(),

View file

@ -1017,11 +1017,6 @@ public class PicocliTest extends AbstractConfigurationTest {
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")));
@ -1723,6 +1718,9 @@ public class PicocliTest extends AbstractConfigurationTest {
KeycloakMain.main(new String[] {"tools", "windows-service"}, nonRunningPicocli);
assertEquals(CommandLine.ExitCode.USAGE, nonRunningPicocli.exitCode);
assertTrue(nonRunningPicocli.getErrString().contains("Missing required subcommand"));
onAfter();
KeycloakMain.main(new String[] {"tools", "windows-service", "uninstall", "--db=bar"}, nonRunningPicocli);
assertEquals(CommandLine.ExitCode.USAGE, nonRunningPicocli.exitCode);
}
@Test

View file

@ -30,23 +30,23 @@ public class ConfigArgsConfigSourceTest {
@Test
public void testParseArgs() {
List<String> values = new ArrayList<>();
ConfigArgsConfigSource.parseConfigArgs(Arrays.asList("--key=value", "-cf", "file", "command", "arg", "--db", "value"), (key, value) -> values.add(key+'='+value), values::add);
assertEquals(Arrays.asList("--key=value", "-cf=file", "command", "arg", "--db=value"), values);
ConfigArgsConfigSource.parseConfigArgs(Arrays.asList("--key=value", "command", "arg", "--db", "value"), (key, value) -> values.add(key+'='+value), values::add);
assertEquals(Arrays.asList("--key=value", "command", "arg", "--db=value"), values);
}
@Test
public void testParseArgsWithSpi() {
List<String> values = new ArrayList<>();
ConfigArgsConfigSource.parseConfigArgs(Arrays.asList("--spi-some-thing-enabled=value", "--spi-some-thing-else", "other-value"), (key, value) -> values.add(key+'='+value), ignored -> {});
assertEquals(Arrays.asList("--spi-some-thing-enabled=value", "--spi-some-thing-else=other-value"), values);
}
@Test
public void testArgEndingInSemiColon() {
ConfigArgsConfigSource.setCliArgs("--some=thing;", "--else=value");
assertEquals(Arrays.asList("--some=thing;", "--else=value"), ConfigArgsConfigSource.getAllCliArgs());
}
@Test
public void testArgCommas() {
ConfigArgsConfigSource.setCliArgs("--some=,,,", "--else=,");

View file

@ -196,7 +196,6 @@ public class QuarkusPropertiesDistTest {
}
@Test
@BeforeStartDistribution(ForceRebuild.class)
@DisabledOnOs(value = { OS.WINDOWS }, disabledReason = "Windows uses a different path separator.")
@Launch({ "start", "--verbose", "--http-enabled=true", "--hostname-strict=false",
"--https-certificate-file=/tmp/kc/bin/../conf/server.crt.pem",
@ -208,7 +207,6 @@ public class QuarkusPropertiesDistTest {
}
@Test
@BeforeStartDistribution(ForceRebuild.class)
@EnabledOnOs(value = { OS.WINDOWS }, disabledReason = "Windows uses a different path separator.")
@Launch({ "start", "--http-enabled=true", "--hostname-strict=false",
"--https-certificate-file=C:\\tmp\\kc\\bin\\..\\conf/server.crt.pem",
@ -255,12 +253,4 @@ public class QuarkusPropertiesDistTest {
}
}
public static class ForceRebuild implements Consumer<KeycloakDistribution> {
@Override
public void accept(KeycloakDistribution distribution) {
CLIResult buildResult = distribution.run("build");
buildResult.assertBuild();
}
}
}

View file

@ -108,7 +108,7 @@ public class ShowConfigCommandDistTest {
distribution.setEnvVar("KC_LOG", "file");
distribution.copyOrReplaceFile(Paths.get("src/test/resources/ShowConfigCommandTest/quarkus.properties"), Path.of("conf", "quarkus.properties"));
result = distribution.run(String.format("%s=%s", CONFIG_FILE_LONG_NAME, Paths.get("src/test/resources/ShowConfigCommandTest/keycloak-keystore.conf").toAbsolutePath().normalize()), ShowConfig.NAME, "all");
result = distribution.run(String.format("%s=%s", CONFIG_FILE_LONG_NAME, Paths.get("src/test/resources/ShowConfigCommandTest/keycloak-keystore.conf").toAbsolutePath().normalize()), ShowConfig.NAME, "all", "--db=dev-file");
result.assertMessage("(CLI)");
result.assertMessage("(ENV)");

View file

@ -49,8 +49,7 @@ public class StartAutoBuildDistTest {
cliResult.assertMessage("Updating the configuration and installing your custom providers, if any. Please wait.");
cliResult.assertMessage("Server configuration updated and persisted. Run the following command to review the configuration:");
cliResult.assertMessage(KeycloakDistribution.SCRIPT_CMD + " show-config");
cliResult.assertMessage("Next time you run the server, just run:");
cliResult.assertMessage(KeycloakDistribution.SCRIPT_CMD + " --verbose start --http-enabled=true --hostname-strict=false " + OPTIMIZED_BUILD_OPTION_LONG);
cliResult.assertMessage("Next time you run the server, just add --optimized to the command to ensure this build is used.");
cliResult.assertNoMessage("--cache");
assertTrue(cliResult.getErrorOutput().isBlank());
}

View file

@ -31,7 +31,6 @@ import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestMethodOrder;
import static org.keycloak.quarkus.runtime.cli.command.AbstractAutoBuildCommand.OPTIMIZED_BUILD_OPTION_LONG;
import static org.keycloak.quarkus.runtime.cli.command.Main.CONFIG_FILE_LONG_NAME;
import static org.hamcrest.CoreMatchers.containsString;
@ -165,8 +164,7 @@ public class StartCommandDistTest {
cliResult.assertMessage("Updating the configuration and installing your custom providers, if any. Please wait.");
cliResult.assertMessage("Server configuration updated and persisted. Run the following command to review the configuration:");
cliResult.assertMessage(KeycloakDistribution.SCRIPT_CMD + " show-config");
cliResult.assertMessage("Next time you run the server, just run:");
cliResult.assertMessage(KeycloakDistribution.SCRIPT_CMD + " start --http-enabled=true --hostname-strict=false " + OPTIMIZED_BUILD_OPTION_LONG);
cliResult.assertMessage("Next time you run the server, just add --optimized to the command to ensure this build is used.");
assertFalse(cliResult.getOutput().contains("--metrics-enabled"));
assertTrue(cliResult.getErrorOutput().isBlank(), cliResult.getErrorOutput());
}

View file

@ -17,18 +17,12 @@
package org.keycloak.it.storage.database;
import java.util.function.Consumer;
import org.keycloak.it.junit5.extension.BeforeStartDistribution;
import org.keycloak.it.junit5.extension.CLIResult;
import org.keycloak.it.junit5.extension.CLITest;
import org.keycloak.it.junit5.extension.WithDatabase;
import org.keycloak.it.utils.KeycloakDistribution;
import org.keycloak.it.utils.RawKeycloakDistribution;
@CLITest
@WithDatabase(alias = "oracle")
@BeforeStartDistribution(OracleTest.CopyOracleJdbcDriver.class)
public class OracleTest extends BasicDatabaseTest {
@Override
@ -41,13 +35,4 @@ public class OracleTest extends BasicDatabaseTest {
cliResult.assertMessage("ORA-01017: invalid username/password; logon denied");
}
public static class CopyOracleJdbcDriver implements Consumer<KeycloakDistribution> {
@Override
public void accept(KeycloakDistribution distribution) {
RawKeycloakDistribution rawDist = distribution.unwrap(RawKeycloakDistribution.class);
rawDist.copyProvider("com.oracle.database.jdbc", "ojdbc17");
rawDist.copyProvider("com.oracle.database.nls", "orai18n");
}
}
}

View file

@ -33,7 +33,6 @@ import org.keycloak.config.SecurityOptions;
import org.keycloak.platform.Platform;
import org.keycloak.quarkus.runtime.Environment;
import org.keycloak.quarkus.runtime.cli.Picocli;
import org.keycloak.quarkus.runtime.configuration.ConfigArgsConfigSource;
import org.keycloak.quarkus.runtime.configuration.Configuration;
import org.keycloak.quarkus.runtime.configuration.IgnoredArtifacts;
@ -61,7 +60,6 @@ public class Keycloak {
static {
System.setProperty("java.util.logging.manager", "org.jboss.logmanager.LogManager");
System.setProperty(Environment.KC_CONFIG_BUILT, "true");
System.setProperty("quarkus.http.test-port", "${kc.http-port}");
System.setProperty("quarkus.http.test-ssl-port", "${kc.https-port}");
System.setProperty("java.util.concurrent.ForkJoinPool.common.threadFactory", QuarkusForkJoinWorkerThreadFactory.class.getName());
@ -204,7 +202,7 @@ public class Keycloak {
curated = builder.build().bootstrap();
AugmentAction action = curated.createAugmentor();
Environment.setHomeDir(homeDir);
ConfigArgsConfigSource.setCliArgs(args.toArray(new String[0]));
initSys(args.toArray(String[]::new));
System.setProperty(Environment.KC_TEST_REBUILD, "true");
StartupAction startupAction = action.createInitialRuntimeApplication();
System.getProperties().remove(Environment.KC_TEST_REBUILD);
@ -312,4 +310,30 @@ public class Keycloak {
application = null;
curated = null;
}
/**
* Uses a dummy {@link Picocli} to process the args and set system
* variables needed to run augmentation
*/
public static void initSys(String... args) {
Picocli picocli = new Picocli() {
@Override
public void build() throws Throwable {
// do nothing
}
@Override
public void start() {
throw new AssertionError();
}
@Override
public void exit(int exitCode) {
// do nothing
}
};
picocli.parseAndRun(List.of(args));
System.setProperty(Environment.KC_CONFIG_BUILT, "true");
}
}

View file

@ -24,9 +24,9 @@ import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.keycloak.Keycloak;
import org.keycloak.it.utils.KeycloakDistribution;
import org.keycloak.it.utils.RawDistRootPath;
import org.keycloak.it.utils.RawKeycloakDistribution;
@ -34,9 +34,7 @@ import org.keycloak.quarkus.runtime.Environment;
import org.keycloak.quarkus.runtime.cli.command.DryRunMixin;
import org.keycloak.quarkus.runtime.cli.command.Start;
import org.keycloak.quarkus.runtime.cli.command.StartDev;
import org.keycloak.quarkus.runtime.configuration.ConfigArgsConfigSource;
import org.keycloak.quarkus.runtime.configuration.Configuration;
import org.keycloak.quarkus.runtime.configuration.KeycloakPropertiesConfigSource;
import org.keycloak.quarkus.runtime.integration.QuarkusPlatform;
import io.quarkus.deployment.util.FileUtil;
@ -56,8 +54,6 @@ import static java.lang.System.setProperty;
import static org.keycloak.it.junit5.extension.DistributionTest.ReInstall.BEFORE_ALL;
import static org.keycloak.it.junit5.extension.DistributionType.RAW;
import static org.keycloak.quarkus.runtime.Environment.forceTestLaunchMode;
import static org.keycloak.quarkus.runtime.cli.command.Main.CONFIG_FILE_LONG_NAME;
import static org.keycloak.quarkus.runtime.cli.command.Main.CONFIG_FILE_SHORT_NAME;
public class CLITestExtension extends QuarkusMainTestExtension {
@ -74,15 +70,14 @@ public class CLITestExtension extends QuarkusMainTestExtension {
getStore(context).put(SYS_PROPS, new HashMap<>(System.getProperties()));
if (launch != null && distConfig == null) {
ConfigArgsConfigSource.parseConfigArgs(List.of(launch.value()), (arg, value) -> {
if (arg.equals(CONFIG_FILE_SHORT_NAME) || arg.equals(CONFIG_FILE_LONG_NAME)) {
setProperty(KeycloakPropertiesConfigSource.KEYCLOAK_CONFIG_FILE_PROP, value);
} else if (arg.startsWith("-D")) {
setProperty(arg, value);
}
}, arg -> {
Stream.of(launch.value()).forEach(arg -> {
if (arg.startsWith("-D")) {
setProperty(arg, "");
int index = arg.indexOf("=");
if (index > 0) {
setProperty(arg.substring(2, index), arg.substring(index + 1, arg.length()));
} else {
setProperty(arg.substring(2), "");
}
}
});
}
@ -112,10 +107,10 @@ public class CLITestExtension extends QuarkusMainTestExtension {
}
if (launch != null) {
result = dist.run(Stream.concat(List.of(launch.value()).stream(), List.of(distConfig.defaultOptions()).stream()).collect(Collectors.toList()));
result = dist.run(List.of(launch.value()));
}
} else {
ConfigArgsConfigSource.setCliArgs(launch == null ? new String[] {} : launch.value());
Keycloak.initSys(launch == null ? new String[] {} : launch.value());
configureProfile(context);
super.beforeEach(context);
}