fix: refining the usage of distribution tests (#34272)

* fix: refining distribution tests

allows for the capturing of dry run build values for subsequent commands

closes: #34058

Signed-off-by: Steve Hawkins <shawkins@redhat.com>

* converting a few more tests to dry run and several other cleanups

also splitting the stdout and stderr collection for docker

Signed-off-by: Steve Hawkins <shawkins@redhat.com>

---------

Signed-off-by: Steve Hawkins <shawkins@redhat.com>
This commit is contained in:
Steven Hawkins
2024-11-15 10:28:45 -05:00
committed by GitHub
parent 80bbb0be10
commit 799ee85b7f
29 changed files with 393 additions and 239 deletions

View File

@@ -392,6 +392,10 @@ public class Profile {
return CURRENT;
}
public static void reset() {
CURRENT = null;
}
public static boolean isFeatureEnabled(Feature feature) {
return getInstance().features.get(feature);
}

View File

@@ -216,7 +216,7 @@ public final class Environment {
public static boolean isRebuildCheck() {
return Boolean.getBoolean("kc.config.build-and-exit");
}
public static void setRebuildCheck() {
System.setProperty("kc.config.build-and-exit", "true");
}

View File

@@ -18,7 +18,6 @@
package org.keycloak.quarkus.runtime;
import static org.keycloak.quarkus.runtime.Environment.getKeycloakModeFromProfile;
import static org.keycloak.quarkus.runtime.Environment.isDevProfile;
import static org.keycloak.quarkus.runtime.Environment.isNonServerMode;
import static org.keycloak.quarkus.runtime.Environment.isTestLaunchMode;
import static org.keycloak.quarkus.runtime.cli.command.AbstractStartCommand.OPTIMIZED_BUILD_OPTION_LONG;
@@ -29,9 +28,7 @@ import java.util.List;
import java.util.concurrent.ForkJoinPool;
import jakarta.enterprise.context.ApplicationScoped;
import org.keycloak.common.profile.ProfileException;
import org.keycloak.quarkus.runtime.configuration.mappers.PropertyMappers;
import picocli.CommandLine.ExitCode;
import org.keycloak.quarkus.runtime.configuration.PersistedConfigSource;
import io.quarkus.runtime.ApplicationLifecycleManager;
import io.quarkus.runtime.Quarkus;
@@ -41,6 +38,7 @@ import org.keycloak.quarkus.runtime.cli.ExecutionExceptionHandler;
import org.keycloak.quarkus.runtime.cli.PropertyException;
import org.keycloak.quarkus.runtime.cli.Picocli;
import org.keycloak.common.Version;
import org.keycloak.quarkus.runtime.cli.command.DryRunMixin;
import org.keycloak.quarkus.runtime.cli.command.Start;
import io.quarkus.runtime.QuarkusApplication;
@@ -66,47 +64,34 @@ public class KeycloakMain implements QuarkusApplication {
ensureForkJoinPoolThreadFactoryHasBeenSetToQuarkus();
System.setProperty("kc.version", Version.VERSION);
main(args, new Picocli());
}
public static void main(String[] args, Picocli picocli) {
List<String> cliArgs = null;
try {
cliArgs = Picocli.parseArgs(args);
} catch (PropertyException e) {
handleUsageError(e.getMessage());
picocli.usageException(e.getMessage(), e.getCause());
return;
}
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");
} else if (isFastStart(cliArgs)) { // fast path for starting the server without bootstrapping CLI
Environment.updateProfile(true);
if (Environment.isDevProfile()) {
handleUsageError(Messages.devProfileNotAllowedError(Start.NAME));
return;
}
Environment.setParsedCommand(new Start());
try {
PropertyMappers.sanitizeDisabledMappers();
PrintWriter outStream = new PrintWriter(System.out, true);
Picocli.validateConfig(cliArgs, new Start(), outStream);
} catch (PropertyException | ProfileException e) {
handleUsageError(e.getMessage(), e.getCause());
return;
}
ExecutionExceptionHandler errorHandler = new ExecutionExceptionHandler();
PrintWriter errStream = new PrintWriter(System.err, true);
start(errorHandler, errStream, args);
Start.fastStart(picocli, Boolean.valueOf(System.getenv().get(DryRunMixin.KC_DRY_RUN_ENV)));
return;
}
// parse arguments and execute any of the configured commands
new Picocli().parseAndRun(cliArgs);
picocli.parseAndRun(cliArgs);
}
/**
@@ -128,23 +113,12 @@ public class KeycloakMain implements QuarkusApplication {
}
}
private static void handleUsageError(String message) {
handleUsageError(message, null);
}
private static void handleUsageError(String message, Throwable cause) {
ExecutionExceptionHandler errorHandler = new ExecutionExceptionHandler();
PrintWriter errStream = new PrintWriter(System.err, true);
errorHandler.error(errStream, message, cause);
System.exit(ExitCode.USAGE);
}
private static boolean isFastStart(List<String> cliArgs) {
// 'start --optimized' should start the server without parsing CLI
return cliArgs.size() == 2 && cliArgs.get(0).equals(Start.NAME) && cliArgs.stream().anyMatch(OPTIMIZED_BUILD_OPTION_LONG::equals);
}
public static void start(ExecutionExceptionHandler errorHandler, PrintWriter errStream, String[] args) {
public static void start(ExecutionExceptionHandler errorHandler, PrintWriter errStream) {
try {
Quarkus.run(KeycloakMain.class, (exitCode, cause) -> {
if (cause != null) {
@@ -158,7 +132,7 @@ public class KeycloakMain implements QuarkusApplication {
// as we are replacing the default exit handler, we need to force exit
System.exit(exitCode);
}
}, args);
});
} catch (Throwable cause) {
errorHandler.error(errStream,
String.format("Unexpected error when starting the server in (%s) mode", getKeycloakModeFromProfile(org.keycloak.common.util.Environment.getProfile())),
@@ -172,10 +146,6 @@ public class KeycloakMain implements QuarkusApplication {
*/
@Override
public int run(String... args) throws Exception {
if (isDevProfile()) {
Logger.getLogger(KeycloakMain.class).warnf("Running the server in development mode. DO NOT use this configuration in production.");
}
int exitCode = ApplicationLifecycleManager.getExitCode();
if (isTestLaunchMode() || isNonServerMode()) {

View File

@@ -100,6 +100,8 @@ public class Picocli {
boolean includeBuildTime;
}
private ExecutionExceptionHandler errorHandler = new ExecutionExceptionHandler();
public void parseAndRun(List<String> 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
@@ -127,21 +129,17 @@ public class Picocli {
exitCode = runReAugmentationIfNeeded(cliArgs, cmd, currentCommand);
} else {
PropertyMappers.sanitizeDisabledMappers();
exitCode = run(cmd, argArray);
exitCode = cmd.execute(argArray);
}
exitOnFailure(exitCode, cmd);
exit(exitCode);
} catch (ParameterException parEx) {
catchParameterException(parEx, cmd, argArray);
} catch (ProfileException | PropertyException proEx) {
catchProfileException(proEx.getMessage(), proEx.getCause(), cmd);
usageException(proEx.getMessage(), proEx.getCause());
}
}
protected int run(CommandLine cmd, String[] argArray) {
return cmd.execute(argArray);
}
private CommandLine createCommandLineForCommand(List<String> cliArgs, List<CommandLine> commandLineList) {
return createCommandLine(spec -> {
// use the incoming commandLineList from the initial parsing to determine the current command
@@ -184,24 +182,20 @@ public class Picocli {
try {
exitCode = cmd.getParameterExceptionHandler().handleParseException(parEx, args);
} catch (Exception e) {
ExecutionExceptionHandler errorHandler = new ExecutionExceptionHandler();
errorHandler.error(cmd.getErr(), e.getMessage(), null);
exitCode = parEx.getCommandLine().getCommandSpec().exitCodeOnInvalidInput();
}
exitOnFailure(exitCode, cmd);
exit(exitCode);
}
private void catchProfileException(String message, Throwable cause, CommandLine cmd) {
ExecutionExceptionHandler errorHandler = new ExecutionExceptionHandler();
errorHandler.error(cmd.getErr(), message, cause);
exitOnFailure(CommandLine.ExitCode.USAGE, cmd);
public void usageException(String message, Throwable cause) {
errorHandler.error(getErrWriter(), message, cause);
exit(CommandLine.ExitCode.USAGE);
}
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);
}
public void exit(int exitCode) {
// hard exit wanted, as build failed and no subsequent command should be executed. no quarkus involved.
System.exit(exitCode);
}
private int runReAugmentationIfNeeded(List<String> cliArgs, CommandLine cmd, CommandLine currentCommand) {
@@ -328,9 +322,8 @@ public class Picocli {
*
* @param cliArgs
* @param abstractCommand
* @param outWriter
*/
public static void validateConfig(List<String> cliArgs, AbstractCommand abstractCommand, PrintWriter outWriter) {
public void validateConfig(List<String> cliArgs, AbstractCommand abstractCommand) {
if (cliArgs.contains(OPTIMIZED_BUILD_OPTION_LONG) && !wasBuildEverRun()) {
throw new PropertyException(Messages.optimizedUsedForFirstStartup());
}
@@ -390,7 +383,7 @@ public class Picocli {
// only check build-time for a rebuild, we'll check the runtime later
if (!mapper.isRunTime() || !isRebuild()) {
if (PropertyMapper.isCliOption(configValue)) {
throw new KcUnmatchedArgumentException(abstractCommand.getCommandLine(), List.of(mapper.getCliFormat()));
throw new KcUnmatchedArgumentException(abstractCommand.getCommandLine().orElseThrow(), List.of(mapper.getCliFormat()));
} else {
handleDisabled(mapper.isRunTime() ? disabledRunTime : disabledBuildTime, mapper);
}
@@ -426,17 +419,17 @@ public class Picocli {
String.join(", ", ignoredBuildTime)));
} else if (!ignoredRunTime.isEmpty()) {
warn(format("The following run time options were found, but will be ignored during build time: %s\n",
String.join(", ", ignoredRunTime)), outWriter);
String.join(", ", ignoredRunTime)), getOutWriter());
}
if (!disabledBuildTime.isEmpty()) {
outputDisabledProperties(disabledBuildTime, true, outWriter);
outputDisabledProperties(disabledBuildTime, true, getOutWriter());
} else if (!disabledRunTime.isEmpty()) {
outputDisabledProperties(disabledRunTime, false, outWriter);
outputDisabledProperties(disabledRunTime, false, getOutWriter());
}
if (!deprecatedInUse.isEmpty()) {
warn("The following used options or option values are DEPRECATED and will be removed or their behaviour changed in a future release:\n" + String.join("\n", deprecatedInUse) + "\nConsult the Release Notes for details.", outWriter);
warn("The following used options or option values are DEPRECATED and will be removed or their behaviour changed in a future release:\n" + String.join("\n", deprecatedInUse) + "\nConsult the Release Notes for details.", getOutWriter());
}
} finally {
DisabledMappersInterceptor.enable(disabledMappersInterceptorEnabled);
@@ -563,7 +556,7 @@ public class Picocli {
return;
}
} else if (name.startsWith(MicroProfileConfigProvider.NS_QUARKUS)) {
// TODO: this is not correct - we are including runtime properties here, but at least they
// TODO: this is not correct - we are including runtime properties here, but at least they
// are already coming from a file
quarkus = true;
} else if (!PropertyMappers.isSpiBuildTimeProperty(name)) {
@@ -618,7 +611,7 @@ public class Picocli {
CommandLine cmd = new CommandLine(spec);
cmd.setExecutionExceptionHandler(new ExecutionExceptionHandler());
cmd.setExecutionExceptionHandler(this.errorHandler);
cmd.setParameterExceptionHandler(new ShortErrorMessageHandler());
cmd.setHelpFactory(new HelpFactory());
cmd.getHelpSectionMap().put(SECTION_KEY_COMMAND_LIST, new SubCommandListRenderer());
@@ -627,11 +620,11 @@ public class Picocli {
return cmd;
}
protected PrintWriter getErrWriter() {
public PrintWriter getErrWriter() {
return new PrintWriter(System.err, true);
}
protected PrintWriter getOutWriter() {
public PrintWriter getOutWriter() {
return new PrintWriter(System.out, true);
}
@@ -909,8 +902,8 @@ public class Picocli {
}
}
public void start(CommandLine cmd) {
KeycloakMain.start((ExecutionExceptionHandler) cmd.getExecutionExceptionHandler(), cmd.getErr(), cmd.getParseResult().originalArgs().toArray(new String[0]));
public void start() {
KeycloakMain.start(errorHandler, getErrWriter());
}
public void build() throws Throwable {

View File

@@ -19,6 +19,10 @@ package org.keycloak.quarkus.runtime.cli.command;
import static org.keycloak.quarkus.runtime.Messages.cliExecutionError;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import org.keycloak.config.OptionCategory;
import org.keycloak.quarkus.runtime.cli.Picocli;
import org.keycloak.quarkus.runtime.configuration.ConfigArgsConfigSource;
@@ -27,13 +31,10 @@ import picocli.CommandLine;
import picocli.CommandLine.Model.CommandSpec;
import picocli.CommandLine.Spec;
import java.util.Arrays;
import java.util.List;
public abstract class AbstractCommand {
@Spec
protected CommandSpec spec;
protected CommandSpec spec; // will be null for "start --optimized"
protected Picocli picocli;
protected void executionError(CommandLine cmd, String message) {
@@ -66,15 +67,15 @@ public abstract class AbstractCommand {
}
protected void validateConfig() {
Picocli.validateConfig(ConfigArgsConfigSource.getAllCliArgs(), this, spec.commandLine().getOut());
picocli.validateConfig(ConfigArgsConfigSource.getAllCliArgs(), this);
}
public abstract String getName();
public CommandLine getCommandLine() {
return spec.commandLine();
public Optional<CommandLine> getCommandLine() {
return Optional.ofNullable(spec).map(CommandSpec::commandLine);
}
public void setPicocli(Picocli picocli) {
this.picocli = picocli;
}

View File

@@ -18,7 +18,6 @@
package org.keycloak.quarkus.runtime.cli.command;
import org.keycloak.config.OptionCategory;
import org.keycloak.quarkus.runtime.Environment;
import org.keycloak.quarkus.runtime.configuration.mappers.HostnameV2PropertyMappers;
import org.keycloak.quarkus.runtime.configuration.mappers.HttpPropertyMappers;
@@ -27,32 +26,36 @@ import java.util.List;
import java.util.stream.Collectors;
import picocli.CommandLine;
import picocli.CommandLine.Help.Ansi;
import static org.keycloak.quarkus.runtime.configuration.Configuration.getRawPersistedProperties;
import static org.keycloak.quarkus.runtime.Environment.isDevProfile;
public abstract class AbstractStartCommand extends AbstractCommand implements Runnable {
public static final String OPTIMIZED_BUILD_OPTION_LONG = "--optimized";
@CommandLine.Mixin
DryRunMixin dryRunMixin = new DryRunMixin();
@Override
public void run() {
Environment.setParsedCommand(this);
doBeforeRun();
CommandLine cmd = spec.commandLine();
HttpPropertyMappers.validateConfig();
HostnameV2PropertyMappers.validateConfig();
validateConfig();
picocli.start(cmd);
if (isDevProfile()) {
picocli.getOutWriter().println(Ansi.AUTO.string(
"@|bold,red Running the server in development mode. DO NOT use this configuration in production.|@"));
}
if (!Boolean.TRUE.equals(dryRunMixin.dryRun)) {
picocli.start();
}
}
protected void doBeforeRun() {
}
public static boolean wasBuildEverRun() {
return !getRawPersistedProperties().isEmpty();
}
@Override
public List<OptionCategory> getOptionCategories() {
EnumSet<OptionCategory> excludedCategories = excludedCategories();
@@ -62,5 +65,5 @@ public abstract class AbstractStartCommand extends AbstractCommand implements Ru
protected EnumSet<OptionCategory> excludedCategories() {
return EnumSet.of(OptionCategory.IMPORT, OptionCategory.EXPORT);
}
}

View File

@@ -28,6 +28,7 @@ import org.keycloak.config.OptionCategory;
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.PersistedConfigSource;
import io.quarkus.bootstrap.runner.RunnerClassLoader;
@@ -65,6 +66,9 @@ public final class Build extends AbstractCommand implements Runnable {
@CommandLine.Mixin
HelpAllMixin helpAllMixin;
@CommandLine.Mixin
DryRunMixin dryRunMixin;
@Override
public void run() {
if (org.keycloak.common.util.Environment.getProfile() == null) {
@@ -81,7 +85,11 @@ public final class Build extends AbstractCommand implements Runnable {
configureBuildClassLoader();
beforeReaugmentationOnWindows();
picocli.build();
if (!Boolean.TRUE.equals(dryRunMixin.dryRun)) {
picocli.build();
} else if (DryRunMixin.isDryRunBuild()) {
PersistedConfigSource.getInstance().saveDryRunProperties();
}
if (!isDevProfile()) {
println(spec.commandLine(), "Server configuration updated and persisted. Run the following command to review the configuration:\n");

View File

@@ -0,0 +1,41 @@
/*
* 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.command;
import picocli.CommandLine;
import static org.keycloak.quarkus.runtime.cli.Picocli.NO_PARAM_LABEL;
public final class DryRunMixin {
public static final String DRYRUN_OPTION_LONG = "--dry-run";
public static final String KC_DRY_RUN_ENV = "KC_DRY_RUN";
public static final String KC_DRY_RUN_BUILD_ENV = "KC_DRY_RUN_BUILD";
@CommandLine.Option(names = {DRYRUN_OPTION_LONG},
description = "Use this option to validate the command, but do nothing",
paramLabel = NO_PARAM_LABEL,
hidden = true,
defaultValue = "${env:" + KC_DRY_RUN_ENV + "}")
Boolean dryRun;
public static boolean isDryRunBuild() {
return Boolean.valueOf(System.getenv().get(KC_DRY_RUN_BUILD_ENV));
}
}

View File

@@ -21,6 +21,10 @@ import static org.keycloak.quarkus.runtime.cli.command.AbstractStartCommand.OPTI
import org.keycloak.quarkus.runtime.Environment;
import org.keycloak.quarkus.runtime.Messages;
import org.keycloak.common.profile.ProfileException;
import org.keycloak.quarkus.runtime.cli.Picocli;
import org.keycloak.quarkus.runtime.cli.PropertyException;
import org.keycloak.quarkus.runtime.configuration.mappers.PropertyMappers;
import picocli.CommandLine;
import picocli.CommandLine.Command;
@@ -38,7 +42,7 @@ public final class Start extends AbstractStartCommand implements Runnable {
public static final String NAME = "start";
@CommandLine.Mixin
OptimizedMixin optimizedMixin;
OptimizedMixin optimizedMixin = new OptimizedMixin();
@CommandLine.Mixin
ImportRealmMixin importRealmMixin;
@@ -50,7 +54,7 @@ public final class Start extends AbstractStartCommand implements Runnable {
protected void doBeforeRun() {
Environment.updateProfile(true);
if (Environment.isDevProfile()) {
executionError(spec.commandLine(), Messages.devProfileNotAllowedError(NAME));
throw new PropertyException(Messages.devProfileNotAllowedError(NAME));
}
}
@@ -63,4 +67,18 @@ public final class Start extends AbstractStartCommand implements Runnable {
public String getName() {
return NAME;
}
public static void fastStart(Picocli picocli, boolean dryRun) {
try {
Start start = new Start();
Environment.setParsedCommand(start);
PropertyMappers.sanitizeDisabledMappers();
start.optimizedMixin.optimized = true;
start.dryRunMixin.dryRun = dryRun;
start.setPicocli(picocli);
start.run();
} catch (PropertyException | ProfileException e) {
picocli.usageException(e.getMessage(), e.getCause());
}
}
}

View File

@@ -20,8 +20,11 @@ package org.keycloak.quarkus.runtime.configuration;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Collections;
import java.util.HashMap;
@@ -33,8 +36,10 @@ import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import io.smallrye.config.ConfigValue;
import io.smallrye.config.ConfigValue.ConfigValueBuilder;
import io.smallrye.config.PropertiesConfigSource;
import org.keycloak.quarkus.runtime.Environment;
import org.keycloak.quarkus.runtime.cli.Picocli;
/**
* A {@link org.eclipse.microprofile.config.spi.ConfigSource} based on the configuration properties persisted into the server
@@ -170,4 +175,29 @@ public final class PersistedConfigSource extends PropertiesConfigSource {
enable();
}
}
public void saveDryRunProperties() throws FileNotFoundException, IOException {
Path path = Environment.getHomePath().resolve("lib").resolve("dryRun.properties");
var properties = Picocli.getNonPersistedBuildTimeOptions();
try (FileOutputStream fos = new FileOutputStream(path.toFile())) {
properties.store(fos, null);
}
}
public void useDryRunProperties() {
Path path = Environment.getHomePath().resolve("lib").resolve("dryRun.properties");
if (Files.exists(path)) {
Properties properties = new Properties();
try (FileInputStream fis = new FileInputStream(path.toFile())) {
properties.load(fis);
} catch (IOException e) {
throw new RuntimeException(e);
}
var config = this.getConfigValueProperties();
config.clear();
properties.forEach((k, v) -> config.put((String) k,
new ConfigValueBuilder().withName((String) k).withValue((String) v).withRawValue((String) v)
.withConfigSourceName(this.getName()).withConfigSourceOrdinal(this.getOrdinal()).build()));
}
}
}

View File

@@ -122,7 +122,6 @@ public class TracingPropertyMappers {
}
private static boolean isFeatureEnabled() {
Environment.getCurrentOrCreateFeatureProfile();
return Profile.isFeatureEnabled(Profile.Feature.OPENTELEMETRY);
}

View File

@@ -27,13 +27,13 @@ import static org.junit.Assert.assertTrue;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.stream.Stream;
import org.junit.Test;
import org.keycloak.quarkus.runtime.Environment;
import org.keycloak.quarkus.runtime.KeycloakMain;
import org.keycloak.quarkus.runtime.cli.Picocli;
import org.keycloak.quarkus.runtime.configuration.ConfigArgsConfigSource;
import org.keycloak.quarkus.runtime.configuration.test.AbstractConfigurationTest;
@@ -57,44 +57,38 @@ public class PicocliTest extends AbstractConfigurationTest {
String getErrString() {
return normalize(err);
}
// normalize line endings - TODO: could also normalize non-printable chars
// but for now those are part of the expected output
String normalize(StringWriter writer) {
return System.lineSeparator().equals("\n") ? writer.toString()
: writer.toString().replace(System.lineSeparator(), "\n");
}
String getOutString() {
return normalize(out);
}
@Override
protected PrintWriter getErrWriter() {
public PrintWriter getErrWriter() {
return new PrintWriter(err, true);
}
@Override
protected PrintWriter getOutWriter() {
public PrintWriter getOutWriter() {
return new PrintWriter(out, true);
}
@Override
protected void exitOnFailure(int exitCode, CommandLine cmd) {
public void exit(int exitCode) {
this.exitCode = exitCode;
}
@Override
public void parseAndRun(List<String> cliArgs) {
config = createConfig();
super.parseAndRun(cliArgs);
}
@Override
public void start(CommandLine cmd) {
public void start() {
// skip
}
@Override
public void build() throws Throwable {
reaug = true;
@@ -106,8 +100,9 @@ public class PicocliTest extends AbstractConfigurationTest {
NonRunningPicocli pseudoLaunch(String... args) {
NonRunningPicocli nonRunningPicocli = new NonRunningPicocli();
ConfigArgsConfigSource.setCliArgs(args);
var cliArgs = Picocli.parseArgs(args);
nonRunningPicocli.parseAndRun(cliArgs);
// TODO: this needs refined, otherwise profile handling will not be correct
nonRunningPicocli.config = createConfig();
KeycloakMain.main(args, nonRunningPicocli);
return nonRunningPicocli;
}
@@ -211,22 +206,22 @@ public class PicocliTest extends AbstractConfigurationTest {
.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 httpStoreTypeValidation() {
NonRunningPicocli nonRunningPicocli = pseudoLaunch("start", "--https-key-store-file=not-there.ks", "--hostname-strict=false");
assertEquals(CommandLine.ExitCode.USAGE, nonRunningPicocli.exitCode);
assertThat(nonRunningPicocli.getErrString(), containsString("Unable to determine 'https-key-store-type' automatically. Adjust the file extension or specify the property"));
nonRunningPicocli = pseudoLaunch("start", "--https-key-store-file=not-there.ks", "--hostname-strict=false", "--https-key-store-type=jdk");
assertEquals(CommandLine.ExitCode.USAGE, nonRunningPicocli.exitCode);
assertThat(nonRunningPicocli.getErrString(), containsString("Failed to load 'https-key-' material: NoSuchFileException not-there.ks"));
nonRunningPicocli = pseudoLaunch("start", "--https-trust-store-file=not-there.jks", "--https-key-store-file=not-there.ks", "--hostname-strict=false", "--https-key-store-type=jdk");
assertEquals(CommandLine.ExitCode.USAGE, nonRunningPicocli.exitCode);
assertThat(nonRunningPicocli.getErrString(), containsString("No trust store password provided"));
}
@Test
public void testShowConfigHidesSystemProperties() {
setSystemProperty("kc.something", "password", () -> {
@@ -245,42 +240,42 @@ public class PicocliTest extends AbstractConfigurationTest {
assertThat(nonRunningPicocli.getErrString(), containsString(
"Option: '--db postgres' is not expected to contain whitespace, please remove any unnecessary quoting/escaping"));
}
@Test
public void spiRuntimeAllowedWithStart() {
NonRunningPicocli nonRunningPicocli = pseudoLaunch("start", "--http-enabled=true", "--spi-something-pass=changeme");
assertEquals(CommandLine.ExitCode.OK, nonRunningPicocli.exitCode);
assertThat(nonRunningPicocli.getOutString(), not(containsString("kc.spi-something-pass")));
}
@Test
public void spiRuntimeWarnWithBuild() {
NonRunningPicocli nonRunningPicocli = pseudoLaunch("build", "--spi-something-pass=changeme");
assertEquals(CommandLine.ExitCode.OK, nonRunningPicocli.exitCode);
assertThat(nonRunningPicocli.getOutString(), containsString("The following run time options were found, but will be ignored during build time: kc.spi-something-pass"));
}
@Test
public void failBuildDev() {
NonRunningPicocli nonRunningPicocli = pseudoLaunch("--profile=dev", "build");
assertThat(nonRunningPicocli.getErrString(), containsString("You can not 'build' the server in development mode."));
assertEquals(CommandLine.ExitCode.SOFTWARE, nonRunningPicocli.exitCode);
}
@Test
public void failStartBuildDev() {
NonRunningPicocli nonRunningPicocli = pseudoLaunch("--profile=dev", "start");
assertThat(nonRunningPicocli.getErrString(), containsString("You can not 'start' the server in development mode."));
assertEquals(CommandLine.ExitCode.SOFTWARE, nonRunningPicocli.exitCode);
assertEquals(CommandLine.ExitCode.USAGE, nonRunningPicocli.exitCode);
}
@Test
public void failIfOptimizedUsedForFirstStartupExport() {
NonRunningPicocli nonRunningPicocli = pseudoLaunch("export", "--optimized", "--dir=data");
assertEquals(CommandLine.ExitCode.USAGE, nonRunningPicocli.exitCode);
assertThat(nonRunningPicocli.getErrString(), containsString("The '--optimized' flag was used for first ever server start."));
}
@Test
public void testReaugFromProdToDev() {
build("build");
@@ -305,37 +300,37 @@ public class PicocliTest extends AbstractConfigurationTest {
onAfter();
addPersistedConfigValues((Map)nonRunningPicocli.buildProps);
}
@Test
public void testReaugFromProdToDevExport() {
build("build");
Environment.setRebuildCheck(); // will be reset by the system properties logic
NonRunningPicocli nonRunningPicocli = pseudoLaunch("--profile=dev", "export", "--file=file");
assertEquals(CommandLine.ExitCode.OK, nonRunningPicocli.exitCode);
assertTrue(nonRunningPicocli.reaug);
}
@Test
public void testNoReaugFromProdToExport() {
build("build");
Environment.setRebuildCheck(); // will be reset by the system properties logic
NonRunningPicocli nonRunningPicocli = pseudoLaunch("export", "--file=file");
assertEquals(CommandLine.ExitCode.OK, nonRunningPicocli.exitCode);
assertFalse(nonRunningPicocli.reaug);
}
@Test
public void testReaugFromDevToProd() {
build("start-dev");
Environment.setRebuildCheck(); // will be reset by the system properties logic
NonRunningPicocli nonRunningPicocli = pseudoLaunch("start", "--hostname=name", "--http-enabled=true");
assertEquals(CommandLine.ExitCode.OK, nonRunningPicocli.exitCode);
assertTrue(nonRunningPicocli.reaug);
}
@Test
public void testNoReaugFromDevToDevExport() {
build("start-dev");
@@ -345,22 +340,22 @@ public class PicocliTest extends AbstractConfigurationTest {
assertEquals(CommandLine.ExitCode.OK, nonRunningPicocli.exitCode);
assertFalse(nonRunningPicocli.reaug);
}
@Test
public void testReaugFromDevToProdExport() {
build("start-dev");
Environment.setRebuildCheck(); // will be reset by the system properties logic
NonRunningPicocli nonRunningPicocli = pseudoLaunch("export", "--file=file");
assertEquals(CommandLine.ExitCode.OK, nonRunningPicocli.exitCode);
assertTrue(nonRunningPicocli.reaug);
assertEquals("prod", nonRunningPicocli.buildProps.getProperty(org.keycloak.common.util.Environment.PROFILE));;
}
@Test
public void testOptimizedReaugmentationMessage() {
build("build");
Environment.setRebuildCheck(); // will be reset by the system properties logic
NonRunningPicocli nonRunningPicocli = pseudoLaunch("start", "--features=docker", "--hostname=name", "--http-enabled=true");
assertEquals(CommandLine.ExitCode.OK, nonRunningPicocli.exitCode);
@@ -368,4 +363,15 @@ public class PicocliTest extends AbstractConfigurationTest {
assertTrue(nonRunningPicocli.reaug);
}
@Test
public void fastStartOptimizedSucceeds() {
build("build");
System.setProperty("kc.http-enabled", "true");
System.setProperty("kc.hostname-strict", "false");
NonRunningPicocli nonRunningPicocli = pseudoLaunch("start", "--optimized");
assertEquals(Integer.MAX_VALUE, nonRunningPicocli.exitCode); // "running" state
}
}

View File

@@ -30,6 +30,7 @@ import org.junit.After;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.keycloak.Config;
import org.keycloak.common.Profile;
import org.keycloak.quarkus.runtime.configuration.ConfigArgsConfigSource;
import org.keycloak.quarkus.runtime.configuration.Configuration;
import org.keycloak.quarkus.runtime.configuration.KeycloakConfigSourceProvider;
@@ -98,7 +99,7 @@ public abstract class AbstractConfigurationTest {
System.clearProperty(key);
}
}
@AfterClass
public static void resetConfigruation() {
ConfigurationTest.createConfig(); // onAfter doesn't actually reset the config
@@ -124,6 +125,7 @@ public abstract class AbstractConfigurationTest {
PropertyMappers.reset();
ConfigArgsConfigSource.setCliArgs();
PersistedConfigSource.getInstance().getConfigValueProperties().clear();
Profile.reset();
}
protected Config.Scope initConfig(String... scope) {
@@ -164,7 +166,7 @@ public abstract class AbstractConfigurationTest {
protected void assertExternalConfig(Map<String, String> expectedValues) {
expectedValues.forEach(this::assertExternalConfig);
}
protected static void addPersistedConfigValues(Map<String, String> values) {
var configValueProps = PersistedConfigSource.getInstance().getConfigValueProperties();
values.forEach((k, v) -> configValueProps.put(k, new ConfigValueBuilder().withName(k).withValue(v).build()));

View File

@@ -24,6 +24,7 @@ import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestMethodOrder;
import org.keycloak.it.junit5.extension.CLIResult;
import org.keycloak.it.junit5.extension.DistributionTest;
import org.keycloak.it.junit5.extension.DryRun;
import org.keycloak.it.junit5.extension.RawDistOnly;
import org.keycloak.it.junit5.extension.WithEnvVars;
import org.keycloak.it.utils.KeycloakDistribution;
@@ -38,6 +39,7 @@ import static org.keycloak.quarkus.runtime.cli.command.AbstractStartCommand.OPTI
@TestMethodOrder(OrderAnnotation.class)
public class BuildAndStartDistTest {
@DryRun
@Test
void testBuildAndStart(KeycloakDistribution dist) {
RawKeycloakDistribution rawDist = dist.unwrap(RawKeycloakDistribution.class);
@@ -46,7 +48,7 @@ public class BuildAndStartDistTest {
cliResult.assertBuild();
cliResult = rawDist.run("start", "--http-enabled=true", "--hostname-strict=false", OPTIMIZED_BUILD_OPTION_LONG);
cliResult.assertNoBuild();
cliResult.assertStarted();
assertTrue(cliResult.getErrorOutput().isBlank());
// start using based on the build options set via conf file
rawDist.setProperty("http-enabled", "true");
@@ -56,17 +58,17 @@ public class BuildAndStartDistTest {
cliResult.assertBuild();
cliResult = rawDist.run("start", OPTIMIZED_BUILD_OPTION_LONG);
cliResult.assertNoBuild();
cliResult.assertStarted();
assertTrue(cliResult.getErrorOutput().isBlank());
// running start without optimized flag should not cause a build
cliResult = rawDist.run("start");
cliResult.assertNoBuild();
cliResult.assertStarted();
assertTrue(cliResult.getErrorOutput().isBlank());
// remove the build option from conf file to force a build during start
rawDist.removeProperty("http-relative-path");
cliResult = rawDist.run("start");
cliResult.assertBuild();
cliResult.assertStarted();
assertTrue(cliResult.getErrorOutput().isBlank());
}
@Test

View File

@@ -82,7 +82,7 @@ public class LoggingDistTest {
assertFalse(cliResult.getOutput().contains("INFO"));
assertFalse(cliResult.getOutput().contains("DEBUG"));
assertFalse(cliResult.getOutput().contains("Listening on:"));
assertTrue(cliResult.getOutput().contains("WARN [org.keycloak"));
assertTrue(cliResult.getOutput().contains("Running the server in development mode."));
cliResult.assertStartedDevMode();
}

View File

@@ -28,6 +28,7 @@ import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestMethodOrder;
import org.keycloak.it.junit5.extension.CLIResult;
import org.keycloak.it.junit5.extension.DistributionTest;
import org.keycloak.it.junit5.extension.DryRun;
import org.keycloak.it.junit5.extension.RawDistOnly;
import org.keycloak.it.junit5.extension.WithEnvVars;
import org.keycloak.it.utils.KeycloakDistribution;
@@ -40,6 +41,7 @@ import static org.keycloak.quarkus.runtime.cli.command.Main.CONFIG_FILE_LONG_NAM
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class OptionsDistTest {
@DryRun
@Test
@Order(1)
@Launch({"build", "--db=invalid"})
@@ -47,6 +49,7 @@ public class OptionsDistTest {
Assertions.assertTrue(result.getErrorOutput().contains("Invalid value for option '--db': invalid. Expected values are: dev-file, dev-mem, mariadb, mssql, mysql, oracle, postgres"));
}
@DryRun
@Test
@Order(2)
@Launch({"start", "--test=invalid"})
@@ -54,6 +57,7 @@ public class OptionsDistTest {
assertEquals(1, result.getErrorStream().stream().filter(s -> s.contains("Unknown option: '--test'")).count());
}
@DryRun
@Test
@Order(3)
@Launch({"start", "--log=console", "--log-file-output=json", "--http-enabled=true", "--hostname-strict=false"})
@@ -62,6 +66,7 @@ public class OptionsDistTest {
assertEquals(1, result.getErrorStream().stream().filter(s -> s.contains("Possible solutions: --log, --log-console-output, --log-console-level, --log-console-format, --log-console-color, --log-level")).count());
}
@DryRun
@Test
@Order(4)
@Launch({"start", "--log=file", "--log-file-output=json", "--http-enabled=true", "--hostname-strict=false"})
@@ -82,6 +87,7 @@ public class OptionsDistTest {
cliResult.assertMessage("config property is deprecated and should not be used anymore");
}
@DryRun
@Test
@Order(6)
@RawDistOnly(reason = "Raw is enough and we avoid issues with including custom conf file in the container")
@@ -106,6 +112,7 @@ public class OptionsDistTest {
// Start-dev should be executed as last tests - build is done for development mode
@DryRun
@Test
@Order(8)
@Launch({"start-dev", "--test=invalid"})
@@ -113,6 +120,7 @@ public class OptionsDistTest {
assertEquals(1, result.getErrorStream().stream().filter(s -> s.contains("Unknown option: '--test'")).count());
}
@DryRun
@Test
@Order(9)
@Launch({"start-dev", "--log=console", "--log-file-output=json"})
@@ -121,6 +129,7 @@ public class OptionsDistTest {
assertEquals(1, result.getErrorStream().stream().filter(s -> s.contains("Possible solutions: --log, --log-console-output, --log-console-level, --log-console-format, --log-console-color, --log-level")).count());
}
@DryRun
@Test
@Order(10)
@Launch({"start-dev", "--log=file", "--log-file-output=json", "--log-console-color=true"})
@@ -130,6 +139,7 @@ public class OptionsDistTest {
assertEquals(1, result.getErrorStream().stream().filter(s -> s.contains("Possible solutions: --log, --log-file, --log-file-level, --log-file-format, --log-file-output, --log-level")).count());
}
@DryRun
@Test
@Order(10)
@Launch({"start-dev", "--cache-remote-host=localhost"})
@@ -137,6 +147,7 @@ public class OptionsDistTest {
assertErrorStreamContains(result, "cache-remote-host available only when feature 'multi-site', 'clusterless' or 'cache-embedded-remote-store' is set");
}
@DryRun
@Test
@Order(11)
@Launch({"start-dev", "--cache-remote-port=11222"})
@@ -144,6 +155,7 @@ public class OptionsDistTest {
assertDisabledDueToMissingRemoteHost(result, "--cache-remote-port");
}
@DryRun
@Test
@Order(12)
@Launch({"start-dev", "--cache-remote-username=user"})
@@ -151,6 +163,7 @@ public class OptionsDistTest {
assertDisabledDueToMissingRemoteHost(result, "--cache-remote-username");
}
@DryRun
@Test
@Order(13)
@Launch({"start-dev", "--cache-remote-password=pass"})
@@ -158,6 +171,7 @@ public class OptionsDistTest {
assertDisabledDueToMissingRemoteHost(result, "--cache-remote-password");
}
@DryRun
@Test
@Order(14)
@Launch({"start-dev", "--cache-remote-tls-enabled=false"})
@@ -165,6 +179,7 @@ public class OptionsDistTest {
assertDisabledDueToMissingRemoteHost(result, "--cache-remote-tls-enabled");
}
@DryRun
@Test
@Order(15)
@Launch({"start-dev", "--features=multi-site"})
@@ -172,6 +187,7 @@ public class OptionsDistTest {
assertErrorStreamContains(result, "- cache-remote-host: Required when feature 'multi-site' or 'clusterless' is set.");
}
@DryRun
@Test
@Order(16)
@Launch({"start-dev", "--features=multi-site", "--cache-remote-host=localhost", "--cache-remote-username=user"})
@@ -179,6 +195,7 @@ public class OptionsDistTest {
assertErrorStreamContains(result, "The option 'cache-remote-password' is required when 'cache-remote-username' is set.");
}
@DryRun
@Test
@Order(17)
@Launch({"start-dev", "--features=multi-site", "--cache-remote-host=localhost", "--cache-remote-password=secret"})

View File

@@ -30,6 +30,7 @@ import org.junit.jupiter.api.condition.OS;
import org.keycloak.it.junit5.extension.BeforeStartDistribution;
import org.keycloak.it.junit5.extension.CLIResult;
import org.keycloak.it.junit5.extension.DistributionTest;
import org.keycloak.it.junit5.extension.DryRun;
import org.keycloak.it.junit5.extension.KeepServerAlive;
import org.keycloak.it.junit5.extension.RawDistOnly;
import org.keycloak.it.utils.KeycloakDistribution;
@@ -52,6 +53,7 @@ public class QuarkusPropertiesDistTest {
private static final String QUARKUS_BUILDTIME_HIBERNATE_METRICS_KEY = "quarkus.datasource.metrics.enabled";
private static final String QUARKUS_RUNTIME_CONSOLE_HANDLER_ENABLED_KEY = "quarkus.log.handler.console.\"console-2\".enable";
@DryRun
@Test
@Launch({"build"})
@Order(1)
@@ -86,6 +88,7 @@ public class QuarkusPropertiesDistTest {
cliResult.assertBuild();
}
@DryRun
@Test
@BeforeStartDistribution(UpdateConsoleHandlerFromKeycloakConf.class)
@Launch({"build"})
@@ -96,6 +99,7 @@ public class QuarkusPropertiesDistTest {
cliResult.assertBuild();
}
@DryRun
@Test
@BeforeStartDistribution(UpdateConsoleHandlerFromQuarkusProps.class)
@Launch({"start", "--http-enabled=true", "--hostname-strict=false"})

View File

@@ -6,6 +6,7 @@ import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.keycloak.it.junit5.extension.CLIResult;
import org.keycloak.it.junit5.extension.DistributionTest;
import org.keycloak.it.junit5.extension.DryRun;
import org.keycloak.it.junit5.extension.RawDistOnly;
import org.keycloak.it.junit5.extension.WithEnvVars;
import org.keycloak.it.utils.KeycloakDistribution;
@@ -22,6 +23,7 @@ import static org.keycloak.quarkus.runtime.cli.command.Main.CONFIG_FILE_LONG_NAM
@DistributionTest
public class ShowConfigCommandDistTest {
@DryRun
@Test
@RawDistOnly(reason = "Containers are immutable")
void testShowConfigPicksUpRightConfigDependingOnCurrentMode(KeycloakDistribution distribution) {

View File

@@ -25,6 +25,7 @@ import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestMethodOrder;
import org.keycloak.it.junit5.extension.CLIResult;
import org.keycloak.it.junit5.extension.DistributionTest;
import org.keycloak.it.junit5.extension.DryRun;
import org.keycloak.it.junit5.extension.RawDistOnly;
import org.keycloak.it.junit5.extension.TestProvider;
import org.keycloak.it.utils.KeycloakDistribution;
@@ -40,6 +41,7 @@ import static org.keycloak.quarkus.runtime.cli.command.AbstractStartCommand.OPTI
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class StartAutoBuildDistTest {
@DryRun
@Test
@Launch({ "--verbose", "start", "--http-enabled=true", "--hostname-strict=false" })
@Order(1)
@@ -52,36 +54,40 @@ public class StartAutoBuildDistTest {
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);
assertFalse(cliResult.getOutput().contains("--cache"));
cliResult.assertStarted();
assertTrue(cliResult.getErrorOutput().isBlank());
}
@DryRun
@Test
@Launch({ "start", "--http-enabled=true", "--hostname-strict=false" })
@Order(2)
void testShouldNotReAugIfConfigIsSame(LaunchResult result) {
CLIResult cliResult = (CLIResult) result;
cliResult.assertNoBuild();
cliResult.assertStarted();
assertTrue(cliResult.getErrorOutput().isBlank());
}
@DryRun
@Test
@Launch({ "start", "--db=dev-mem", "--http-enabled=true", "--hostname-strict=false" })
@Order(3)
void testShouldReAugIfConfigChanged(LaunchResult result) {
CLIResult cliResult = (CLIResult) result;
cliResult.assertBuild();
cliResult.assertStarted();
assertTrue(cliResult.getErrorOutput().isBlank());
}
@DryRun
@Test
@Launch({ "start", "--db=dev-mem", "--http-enabled=true", "--hostname-strict=false" })
@Order(4)
void testShouldNotReAugIfSameDatabase(LaunchResult result) {
CLIResult cliResult = (CLIResult) result;
cliResult.assertNoBuild();
cliResult.assertStarted();
assertTrue(cliResult.getErrorOutput().isBlank());
}
@DryRun
@Test
@Launch({ "build", "--db=postgres" })
@Order(5)
@@ -90,15 +96,17 @@ public class StartAutoBuildDistTest {
cliResult.assertBuild();
}
@DryRun
@Test
@Launch({ "start", "--http-enabled=true", "--hostname-strict=false" })
@Order(6)
void testReAugWhenNoOptionAfterBuild(LaunchResult result) {
CLIResult cliResult = (CLIResult) result;
cliResult.assertBuild();
cliResult.assertStarted();
assertTrue(cliResult.getErrorOutput().isBlank());
}
@DryRun
@Test
@Launch({ "start", "--db=postgres", "--http-enabled=true", "--hostname-strict=false" })
@Order(7)
@@ -107,6 +115,7 @@ public class StartAutoBuildDistTest {
cliResult.assertBuild();
}
@DryRun
@Test
@Launch({ "start", "--db=dev-file", "--http-enabled=true", "--hostname-strict=false", OPTIMIZED_BUILD_OPTION_LONG})
@Order(8)
@@ -115,6 +124,7 @@ public class StartAutoBuildDistTest {
cliResult.assertNoBuild();
}
@DryRun
@Test
@Launch({ "start-dev" })
@Order(8)
@@ -124,6 +134,7 @@ public class StartAutoBuildDistTest {
cliResult.assertStartedDevMode();
}
@DryRun
@Test
@Launch({ "start-dev" })
@Order(9)
@@ -132,7 +143,8 @@ public class StartAutoBuildDistTest {
assertFalse(cliResult.getOutput().contains("Updating the configuration and installing your custom providers, if any. Please wait."));
cliResult.assertStartedDevMode();
}
@DryRun
@Test
@TestProvider(CustomUserProvider.class)
@Order(10)
@@ -141,11 +153,11 @@ public class StartAutoBuildDistTest {
cliResult.assertMessage("Updating the configuration");
cliResult.assertStartedDevMode();
dist.stop();
// we should persist the spi provider and know not to rebuild
cliResult = dist.run("start-dev", "--spi-user-provider=custom_jpa", "--spi-user-jpa-enabled=false");
cliResult.assertNoMessage("Updating the configuration");
cliResult.assertStartedDevMode();
}
}

View File

@@ -31,22 +31,20 @@ import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestMethodOrder;
import org.keycloak.it.junit5.extension.CLIResult;
import org.keycloak.it.junit5.extension.DistributionTest;
import org.keycloak.it.junit5.extension.DryRun;
import io.quarkus.test.junit.main.Launch;
import io.quarkus.test.junit.main.LaunchResult;
import org.keycloak.it.junit5.extension.RawDistOnly;
import org.keycloak.it.junit5.extension.WithEnvVars;
import org.keycloak.it.utils.KeycloakDistribution;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@WithEnvVars({"KC_CACHE", "local"}) // avoid flakey port conflicts
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
@DistributionTest
public class StartCommandDistTest {
private static final Logger log = LoggerFactory.getLogger(StartCommandDistTest.class);
@DryRun
@Test
@Launch({ "start", "--hostname-strict=false" })
void failNoTls(LaunchResult result) {
@@ -54,6 +52,7 @@ public class StartCommandDistTest {
() -> "The Output:\n" + result.getErrorOutput() + "doesn't contains the expected string.");
}
@DryRun
@Test
@Launch({ "start", "--spi-events-listener-jboss-logging-success-level" })
void failSpiArgMissingValue(LaunchResult result) {
@@ -61,6 +60,7 @@ public class StartCommandDistTest {
() -> "The Output:\n" + result.getErrorOutput() + "doesn't contains the expected string.");
}
@DryRun
@Test
@Launch({ "build", "--spi-events-listener-jboss-logging-success-level=debug" })
void warnSpiRuntimeAtBuildtime(LaunchResult result) {
@@ -68,6 +68,7 @@ public class StartCommandDistTest {
() -> "The Output:\n" + result.getOutput() + "doesn't contains the expected string.");
}
@DryRun
@Test
@RawDistOnly(reason = "Containers are immutable")
void errorSpiBuildtimeAtRuntime(KeycloakDistribution dist) {
@@ -78,6 +79,7 @@ public class StartCommandDistTest {
result.assertError("The following build time options have values that differ from what is persisted - the new values will NOT be used until another build is run: kc.spi-events-listener-jboss-logging-enabled");
}
@DryRun
@WithEnvVars({"KC_SPI_EVENTS_LISTENER_JBOSS_LOGGING_ENABLED", "false"})
@Test
@RawDistOnly(reason = "Containers are immutable")
@@ -86,9 +88,10 @@ public class StartCommandDistTest {
result.assertBuild();
result = dist.run("start", "--optimized", "--http-enabled=true", "--hostname-strict=false");
result.assertStarted();
result.assertNoError("The following build time options");
}
@DryRun
@Test
@Launch({ "--profile=dev", "start" })
void failUsingDevProfile(LaunchResult result) {
@@ -103,6 +106,7 @@ public class StartCommandDistTest {
cliResult.assertStarted();
}
@DryRun
@Test
@Launch({ "-v", "start", "--db=dev-mem", OPTIMIZED_BUILD_OPTION_LONG})
void failBuildPropertyNotAvailable(LaunchResult result) {
@@ -110,6 +114,7 @@ public class StartCommandDistTest {
cliResult.assertError("Build time option: '--db' not usable with pre-built image and --optimized");
}
@DryRun
@Test
@Launch({ "--profile=dev", "start", "--http-enabled=true", "--hostname-strict=false" })
void failIfAutoBuildUsingDevProfile(LaunchResult result) {
@@ -118,6 +123,8 @@ public class StartCommandDistTest {
assertEquals(4, cliResult.getErrorStream().size());
}
@DryRun
@WithEnvVars({"KC_HTTP_ENABLED", "true", "KC_HOSTNAME_STRICT", "false"})
@Test
@Launch({ "start", "--optimized" })
@Order(1)
@@ -126,6 +133,7 @@ public class StartCommandDistTest {
cliResult.assertError("The '--optimized' flag was used for first ever server start.");
}
@DryRun
@Test
@Launch({ "start", "--optimized", "--http-enabled=true", "--hostname-strict=false" })
@Order(2)
@@ -141,6 +149,7 @@ public class StartCommandDistTest {
() -> "The Output:\n" + result.getOutput() + "doesn't contains the expected string.");
}
@DryRun
@Test
@Launch({ "start", "--http-enabled=true", "--hostname-strict=false", "--metrics-enabled=true" })
void testStartUsingAutoBuild(LaunchResult result) {
@@ -153,9 +162,10 @@ public class StartCommandDistTest {
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);
assertFalse(cliResult.getOutput().contains("--metrics-enabled"));
cliResult.assertStarted();
assertTrue(cliResult.getErrorOutput().isBlank(), cliResult.getErrorOutput());
}
@DryRun
@Test
@Launch({ "start", "--optimized", "--http-enabled=true", "--hostname-strict=false", "--db=postgres" })
void testStartUsingOptimizedDoesNotAllowBuildOptions(LaunchResult result) {
@@ -163,6 +173,7 @@ public class StartCommandDistTest {
cliResult.assertError("Build time option: '--db' not usable with pre-built image and --optimized");
}
@DryRun
@Test
@Launch({ "start", "--http-enabled=true", "--cache-remote-host=localhost", "--hostname-strict=false", "--cache-remote-tls-enabled=false", "--transaction-xa-enabled=true" })
void testStartNoWarningOnDisabledRuntimeOption(LaunchResult result) {
@@ -170,14 +181,16 @@ public class StartCommandDistTest {
cliResult.assertNoMessage("cache-remote-tls-enabled: Available only when remote host is set");
}
@DryRun
@Test
@WithEnvVars({"KC_LOG", "invalid"})
@Launch({ "start" })
@Launch({ "start", "--http-enabled=false", "--hostname-strict=false" })
void testStartUsingOptimizedInvalidEnvOption(LaunchResult result) {
CLIResult cliResult = (CLIResult) result;
cliResult.assertError("Invalid value for option 'KC_LOG': invalid. Expected values are: console, file, syslog");
}
@DryRun
@Test
@RawDistOnly(reason = "Containers are immutable")
void testWarningWhenOverridingBuildOptionsDuringStart(KeycloakDistribution dist) {
@@ -188,30 +201,31 @@ public class StartCommandDistTest {
cliResult.assertMessage("- db=postgres > db=dev-file"); // back to the default value
cliResult.assertMessage("- features=preview > features=<unset>"); // no default value, the <unset> is shown
cliResult.assertMessage("To avoid that, run the 'build' command again and then start the optimized server instance using the '--optimized' flag.");
cliResult.assertStarted();
assertTrue(cliResult.getErrorOutput().isBlank());
// should not show warning if the re-augmentation did not happen through the build command
// an optimized server image should ideally be created by running a build
cliResult = dist.run("start", "--db=dev-mem", "--hostname=localhost", "--http-enabled=true");
cliResult.assertNoMessage("The previous optimized build will be overridden with the following build options:");
cliResult.assertStarted();
assertTrue(cliResult.getErrorOutput().isBlank());
dist.run("build", "--db=postgres");
cliResult = dist.run("start", "--hostname=localhost", "--http-enabled=true");
cliResult.assertMessage("- db=postgres > db=dev-file");
cliResult.assertNoMessage("- features=preview > features=<unset>");
cliResult.assertStarted();
assertTrue(cliResult.getErrorOutput().isBlank());
dist.run("build", "--db=postgres");
cliResult = dist.run("start", "--db=dev-mem", "--hostname=localhost", "--http-enabled=true");
cliResult.assertMessage("- db=postgres > db=dev-mem"); // option overridden during the start
cliResult.assertStarted();
assertTrue(cliResult.getErrorOutput().isBlank());
dist.run("build", "--db=dev-mem");
cliResult = dist.run("start", "--db=dev-mem", "--hostname=localhost", "--http-enabled=true");
cliResult.assertNoMessage("- db=postgres > db=postgres"); // option did not change not need to show
cliResult.assertStarted();
assertTrue(cliResult.getErrorOutput().isBlank());
dist.run("build", "--db=dev-mem");
cliResult = dist.run("start", "--db=dev-mem", "--cache=local", "--hostname=localhost", "--http-enabled=true");
cliResult.assertNoMessage("The previous optimized build will be overridden with the following build options:"); // no message, same values provided during auto-build
}
@DryRun
@Test
@RawDistOnly(reason = "Containers are immutable")
void testStartAfterStartDev(KeycloakDistribution dist) {
@@ -219,9 +233,11 @@ public class StartCommandDistTest {
cliResult.assertStartedDevMode();
cliResult = dist.run("start", "--http-enabled", "true", "--hostname-strict", "false");
cliResult.assertStarted();
cliResult.assertNotDevMode();
assertTrue(cliResult.getErrorOutput().isBlank());
}
@DryRun
@Test
@RawDistOnly(reason = "Containers are immutable")
void testErrorWhenOverridingNonCliBuildOptionsDuringStart(KeycloakDistribution dist) {
@@ -232,6 +248,7 @@ public class StartCommandDistTest {
cliResult.assertError("The following build time options have values that differ from what is persisted - the new values will NOT be used until another build is run: kc.db");
}
@DryRun
@Test
@Launch({CONFIG_FILE_LONG_NAME + "=src/test/resources/non-existing.conf", "start"})
void testInvalidConfigFileOption(LaunchResult result) {

View File

@@ -26,6 +26,7 @@ import org.junit.jupiter.api.condition.DisabledOnOs;
import org.junit.jupiter.api.condition.OS;
import org.keycloak.it.junit5.extension.CLIResult;
import org.keycloak.it.junit5.extension.DistributionTest;
import org.keycloak.it.junit5.extension.DryRun;
import org.keycloak.it.junit5.extension.RawDistOnly;
import org.keycloak.it.utils.KeycloakDistribution;
import org.keycloak.it.utils.RawKeycloakDistribution;
@@ -40,6 +41,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class StartDevCommandDistTest {
@DryRun
@Test
@Launch({ "start-dev" })
void testDevModeWarning(LaunchResult result) {
@@ -47,6 +49,7 @@ public class StartDevCommandDistTest {
cliResult.assertStartedDevMode();
}
@DryRun
@Test
@Launch({ "start-dev", "--db=dev-mem" })
void testBuildPropertyAvailable(LaunchResult result) {
@@ -63,6 +66,7 @@ public class StartDevCommandDistTest {
cliResult.assertMessage("passkeys");
}
@DryRun
@Test
@Launch({ "build", "--debug" })
void testBuildMustNotRunTwoJVMs(LaunchResult result) {
@@ -71,6 +75,7 @@ public class StartDevCommandDistTest {
cliResult.assertBuild();
}
@DryRun
@Test
@Launch({ "start-dev", "--verbose" })
void testVerboseAfterCommand(LaunchResult result) {
@@ -88,7 +93,8 @@ public class StartDevCommandDistTest {
assertTrue(cliResult.getOutput().contains("Listening on:"));
cliResult.assertStartedDevMode();
}
@DryRun
@Test
void testStartDevThenImportRebuild(KeycloakDistribution dist) throws Exception {
RawKeycloakDistribution rawDist = dist.unwrap(RawKeycloakDistribution.class);

View File

@@ -23,9 +23,6 @@ import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.junit.jupiter.api.extension.ExtendWith;
/**
* When used for in-vm tests, use {@link ConfigurationTestResource} to reset the configuration
*/
@Target(ElementType.TYPE)
@ExtendWith({ CLITestExtension.class })
@Retention(RetentionPolicy.RUNTIME)

View File

@@ -32,6 +32,7 @@ import org.keycloak.it.utils.KeycloakDistribution;
import org.keycloak.it.utils.RawDistRootPath;
import org.keycloak.it.utils.RawKeycloakDistribution;
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;
@@ -101,6 +102,11 @@ public class CLITestExtension extends QuarkusMainTestExtension {
configureEnvVars(context.getRequiredTestClass().getAnnotation(WithEnvVars.class));
configureEnvVars(context.getRequiredTestMethod().getAnnotation(WithEnvVars.class));
boolean dryRun = context.getRequiredTestMethod().getAnnotation(DryRun.class) != null;
if (dryRun && isRaw()) {
dist.setEnvVar(DryRunMixin.KC_DRY_RUN_ENV, "true");
dist.setEnvVar(DryRunMixin.KC_DRY_RUN_BUILD_ENV, "true");
}
if (launch != null) {
result = dist.run(Stream.concat(List.of(launch.value()).stream(), List.of(distConfig.defaultOptions()).stream()).collect(Collectors.toList()));
@@ -125,7 +131,7 @@ public class CLITestExtension extends QuarkusMainTestExtension {
return;
}
if (RAW.equals(DistributionType.getCurrent().orElse(RAW))) {
if (isRaw()) {
try {
dist.unwrap(RawKeycloakDistribution.class).copyProvider(provider.value().getDeclaredConstructor().newInstance());
} catch (Exception cause) {
@@ -134,6 +140,10 @@ public class CLITestExtension extends QuarkusMainTestExtension {
}
}
private boolean isRaw() {
return RAW.equals(DistributionType.getCurrent().orElse(RAW));
}
@Override
public void interceptTestMethod(Invocation<Void> invocation,
ReflectiveInvocationContext<Method> invocationContext, ExtensionContext extensionContext) throws Throwable {
@@ -171,6 +181,7 @@ public class CLITestExtension extends QuarkusMainTestExtension {
if (dist != null) {
onKeepServerAlive(context.getRequiredTestMethod().getAnnotation(KeepServerAlive.class), false);
dist.stop();
dist.clearEnv();
if (distConfig != null && DistributionTest.ReInstall.BEFORE_TEST.equals(distConfig.reInstall())) {
dist = null;
@@ -195,7 +206,7 @@ public class CLITestExtension extends QuarkusMainTestExtension {
infinispanContainer.stop();
}
result = null;
if (RAW.equals(DistributionType.getCurrent().orElse(RAW))) {
if (isRaw()) {
if (distConfig != null && !DistributionTest.ReInstall.NEVER.equals(distConfig.reInstall()) && dist != null) {
try {
FileUtil.deleteDirectory(getDistPath().getDistRootPath().resolve("conf"));

View File

@@ -1,55 +0,0 @@
/*
* 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 org.keycloak.it.junit5.extension;
import io.quarkus.runtime.LaunchMode;
import io.quarkus.runtime.configuration.ConfigUtils;
import io.quarkus.test.common.QuarkusTestResourceLifecycleManager;
import io.smallrye.config.SmallRyeConfig;
import io.smallrye.config.SmallRyeConfigProviderResolver;
import org.eclipse.microprofile.config.spi.ConfigProviderResolver;
import org.keycloak.quarkus.runtime.configuration.KeycloakConfigSourceProvider;
import java.util.Map;
/**
* Used to reset the configuration for {@link CLITest}s
*/
public class ConfigurationTestResource implements QuarkusTestResourceLifecycleManager {
@Override
public Map<String, String> start() {
return Map.of();
}
@Override
public void stop() {
}
@Override
public void inject(Object testInstance) {
KeycloakConfigSourceProvider.reload();
SmallRyeConfig config = ConfigUtils.configBuilder(true, LaunchMode.NORMAL).build();
SmallRyeConfigProviderResolver resolver = new SmallRyeConfigProviderResolver();
resolver.registerConfig(config, Thread.currentThread().getContextClassLoader());
ConfigProviderResolver.setInstance(resolver);
}
}

View File

@@ -0,0 +1,32 @@
/*
* 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.junit5.extension;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* {@link DryRun} is used to configure a non-running, non-augmenting distribution
*/
@Target({ ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
public @interface DryRun {
}

View File

@@ -143,4 +143,9 @@ public class KeycloakDistributionDecorator implements KeycloakDistribution {
throw new IllegalArgumentException("Not a " + type + " type");
}
@Override
public void clearEnv() {
delegate.clearEnv();
}
}

View File

@@ -9,6 +9,7 @@ import org.keycloak.it.junit5.extension.CLIResult;
import org.testcontainers.DockerClientFactory;
import org.testcontainers.containers.GenericContainer;
import org.testcontainers.containers.output.OutputFrame;
import org.testcontainers.containers.output.OutputFrame.OutputType;
import org.testcontainers.containers.output.ToStringConsumer;
import org.testcontainers.containers.wait.strategy.Wait;
import org.testcontainers.images.RemoteDockerImage;
@@ -23,10 +24,26 @@ import java.util.List;
import java.util.Map;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.function.Consumer;
import java.util.stream.IntStream;
public final class DockerKeycloakDistribution implements KeycloakDistribution {
private static class BackupConsumer implements Consumer<OutputFrame> {
final ToStringConsumer stdOut = new ToStringConsumer();
final ToStringConsumer stdErr = new ToStringConsumer();
@Override
public void accept(OutputFrame t) {
if (t.getType() == OutputType.STDERR) {
stdErr.accept(t);
} else if (t.getType() == OutputType.STDOUT) {
stdOut.accept(t);
}
}
}
private static final Logger LOGGER = Logger.getLogger(DockerKeycloakDistribution.class);
private final boolean debug;
@@ -38,7 +55,7 @@ public final class DockerKeycloakDistribution implements KeycloakDistribution {
private String stdout = "";
private String stderr = "";
private ToStringConsumer backupConsumer = new ToStringConsumer();
private BackupConsumer backupConsumer = new BackupConsumer();
private final File dockerScriptFile = new File("../../container/ubi-null.sh");
private GenericContainer<?> keycloakContainer = null;
@@ -100,7 +117,7 @@ public final class DockerKeycloakDistribution implements KeycloakDistribution {
this.stdout = "";
this.stderr = "";
this.containerId = null;
this.backupConsumer = new ToStringConsumer();
this.backupConsumer = new BackupConsumer();
keycloakContainer = getKeycloakContainer();
@@ -113,15 +130,14 @@ public final class DockerKeycloakDistribution implements KeycloakDistribution {
waitForStableOutput();
} catch (Exception cause) {
this.exitCode = -1;
this.stdout = backupConsumer.toUtf8String();
this.stderr = backupConsumer.toUtf8String();
this.stdout = backupConsumer.stdOut.toUtf8String();
this.stderr = backupConsumer.stdErr.toUtf8String();
cleanupContainer();
keycloakContainer = null;
LOGGER.warn("Failed to start Keycloak container", cause);
} finally {
if (!manualStop) {
stop();
envVars.clear();
}
}
@@ -193,7 +209,9 @@ public final class DockerKeycloakDistribution implements KeycloakDistribution {
@Override
public void run() {
try {
if (containerId == null) return;
if (containerId == null) {
return;
}
DockerClient dockerClient = DockerClientFactory.lazyClient();
dockerClient.killContainerCmd(containerId).exec();
dockerClient.removeContainerCmd(containerId).withRemoveVolumes(true).withForce(true).exec();
@@ -215,7 +233,7 @@ public final class DockerKeycloakDistribution implements KeycloakDistribution {
if (keycloakContainer != null && keycloakContainer.isRunning()) {
return keycloakContainer.getLogs(OutputFrame.OutputType.STDOUT);
} else if (this.stdout.isEmpty()) {
return backupConsumer.toUtf8String();
return backupConsumer.stdOut.toUtf8String();
} else {
return this.stdout;
}
@@ -230,7 +248,7 @@ public final class DockerKeycloakDistribution implements KeycloakDistribution {
if (keycloakContainer != null && keycloakContainer.isRunning()) {
return keycloakContainer.getLogs(OutputFrame.OutputType.STDERR);
} else if (this.stderr.isEmpty()) {
return backupConsumer.toUtf8String();
return backupConsumer.stdErr.toUtf8String();
} else {
return this.stderr;
}
@@ -269,4 +287,9 @@ public final class DockerKeycloakDistribution implements KeycloakDistribution {
throw new IllegalArgumentException("Not a " + type + " type");
}
@Override
public void clearEnv() {
this.envVars.clear();
}
}

View File

@@ -69,4 +69,6 @@ public interface KeycloakDistribution {
}
<D extends KeycloakDistribution> D unwrap(Class<D> type);
void clearEnv();
}

View File

@@ -187,7 +187,6 @@ public final class RawKeycloakDistribution implements KeycloakDistribution {
}
if (!manualStop) {
stop();
envVars.clear();
}
}
@@ -708,4 +707,9 @@ public final class RawKeycloakDistribution implements KeycloakDistribution {
throw new IllegalArgumentException("Not a " + type + " type");
}
@Override
public void clearEnv() {
this.envVars.clear();
}
}