diff --git a/common/src/main/java/org/keycloak/common/Profile.java b/common/src/main/java/org/keycloak/common/Profile.java index e333387c90d..a7bbcea726f 100755 --- a/common/src/main/java/org/keycloak/common/Profile.java +++ b/common/src/main/java/org/keycloak/common/Profile.java @@ -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); } diff --git a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/Environment.java b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/Environment.java index ea46ba7bda7..2957a13f851 100644 --- a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/Environment.java +++ b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/Environment.java @@ -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"); } diff --git a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/KeycloakMain.java b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/KeycloakMain.java index 8b76c0fe240..76d301fd052 100644 --- a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/KeycloakMain.java +++ b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/KeycloakMain.java @@ -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 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 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()) { diff --git a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/cli/Picocli.java b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/cli/Picocli.java index 1cd0b7aa296..cbf026e57ac 100644 --- a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/cli/Picocli.java +++ b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/cli/Picocli.java @@ -100,6 +100,8 @@ public class Picocli { boolean includeBuildTime; } + private ExecutionExceptionHandler errorHandler = new ExecutionExceptionHandler(); + public void parseAndRun(List cliArgs) { // perform two passes over the cli args. First without option validation to determine the current command, then with option validation enabled CommandLine cmd = createCommandLine(spec -> spec @@ -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 cliArgs, List 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 cliArgs, CommandLine cmd, CommandLine currentCommand) { @@ -328,9 +322,8 @@ public class Picocli { * * @param cliArgs * @param abstractCommand - * @param outWriter */ - public static void validateConfig(List cliArgs, AbstractCommand abstractCommand, PrintWriter outWriter) { + public void validateConfig(List 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 { diff --git a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/cli/command/AbstractCommand.java b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/cli/command/AbstractCommand.java index 6daa65a65e1..6c7926bd9aa 100644 --- a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/cli/command/AbstractCommand.java +++ b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/cli/command/AbstractCommand.java @@ -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 getCommandLine() { + return Optional.ofNullable(spec).map(CommandSpec::commandLine); } - + public void setPicocli(Picocli picocli) { this.picocli = picocli; } diff --git a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/cli/command/AbstractStartCommand.java b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/cli/command/AbstractStartCommand.java index 2132406f1d4..66207fce163 100644 --- a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/cli/command/AbstractStartCommand.java +++ b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/cli/command/AbstractStartCommand.java @@ -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 getOptionCategories() { EnumSet excludedCategories = excludedCategories(); @@ -62,5 +65,5 @@ public abstract class AbstractStartCommand extends AbstractCommand implements Ru protected EnumSet excludedCategories() { return EnumSet.of(OptionCategory.IMPORT, OptionCategory.EXPORT); } - + } diff --git a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/cli/command/Build.java b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/cli/command/Build.java index 50ccb188ae1..ddaa3fefde6 100644 --- a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/cli/command/Build.java +++ b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/cli/command/Build.java @@ -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"); diff --git a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/cli/command/DryRunMixin.java b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/cli/command/DryRunMixin.java new file mode 100644 index 00000000000..2c73d7b3886 --- /dev/null +++ b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/cli/command/DryRunMixin.java @@ -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)); + } + +} diff --git a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/cli/command/Start.java b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/cli/command/Start.java index baacd0dff20..3221d26dcae 100644 --- a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/cli/command/Start.java +++ b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/cli/command/Start.java @@ -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()); + } + } } diff --git a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/PersistedConfigSource.java b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/PersistedConfigSource.java index 91295e2e3db..1ff7511ab2d 100644 --- a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/PersistedConfigSource.java +++ b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/PersistedConfigSource.java @@ -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())); + } + } } diff --git a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/mappers/TracingPropertyMappers.java b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/mappers/TracingPropertyMappers.java index 39d2a3a88a8..421e386958e 100644 --- a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/mappers/TracingPropertyMappers.java +++ b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/mappers/TracingPropertyMappers.java @@ -122,7 +122,6 @@ public class TracingPropertyMappers { } private static boolean isFeatureEnabled() { - Environment.getCurrentOrCreateFeatureProfile(); return Profile.isFeatureEnabled(Profile.Feature.OPENTELEMETRY); } diff --git a/quarkus/runtime/src/test/java/or/keycloak/quarkus/runtime/cli/PicocliTest.java b/quarkus/runtime/src/test/java/or/keycloak/quarkus/runtime/cli/PicocliTest.java index 7dcd867d048..d9dfe76f0e4 100644 --- a/quarkus/runtime/src/test/java/or/keycloak/quarkus/runtime/cli/PicocliTest.java +++ b/quarkus/runtime/src/test/java/or/keycloak/quarkus/runtime/cli/PicocliTest.java @@ -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 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 + } + } diff --git a/quarkus/runtime/src/test/java/org/keycloak/quarkus/runtime/configuration/test/AbstractConfigurationTest.java b/quarkus/runtime/src/test/java/org/keycloak/quarkus/runtime/configuration/test/AbstractConfigurationTest.java index e44e66527f6..139be182c00 100644 --- a/quarkus/runtime/src/test/java/org/keycloak/quarkus/runtime/configuration/test/AbstractConfigurationTest.java +++ b/quarkus/runtime/src/test/java/org/keycloak/quarkus/runtime/configuration/test/AbstractConfigurationTest.java @@ -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 expectedValues) { expectedValues.forEach(this::assertExternalConfig); } - + protected static void addPersistedConfigValues(Map values) { var configValueProps = PersistedConfigSource.getInstance().getConfigValueProperties(); values.forEach((k, v) -> configValueProps.put(k, new ConfigValueBuilder().withName(k).withValue(v).build())); diff --git a/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/BuildAndStartDistTest.java b/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/BuildAndStartDistTest.java index ec22ee91ed2..75380955a79 100644 --- a/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/BuildAndStartDistTest.java +++ b/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/BuildAndStartDistTest.java @@ -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 diff --git a/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/LoggingDistTest.java b/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/LoggingDistTest.java index 8b66939d114..2653caec8cf 100644 --- a/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/LoggingDistTest.java +++ b/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/LoggingDistTest.java @@ -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(); } diff --git a/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/OptionsDistTest.java b/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/OptionsDistTest.java index 9868ec07530..52f6cb87afc 100644 --- a/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/OptionsDistTest.java +++ b/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/OptionsDistTest.java @@ -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"}) diff --git a/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/QuarkusPropertiesDistTest.java b/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/QuarkusPropertiesDistTest.java index 313d00a5690..5e088069e68 100644 --- a/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/QuarkusPropertiesDistTest.java +++ b/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/QuarkusPropertiesDistTest.java @@ -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"}) diff --git a/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/ShowConfigCommandDistTest.java b/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/ShowConfigCommandDistTest.java index 07605c21962..2d7b88df81f 100644 --- a/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/ShowConfigCommandDistTest.java +++ b/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/ShowConfigCommandDistTest.java @@ -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) { diff --git a/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/StartAutoBuildDistTest.java b/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/StartAutoBuildDistTest.java index 2ef54321e3f..b7049928e73 100644 --- a/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/StartAutoBuildDistTest.java +++ b/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/StartAutoBuildDistTest.java @@ -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(); } - + } diff --git a/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/StartCommandDistTest.java b/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/StartCommandDistTest.java index 0f2dd43f537..d1a844b5e57 100644 --- a/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/StartCommandDistTest.java +++ b/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/StartCommandDistTest.java @@ -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="); // no default value, the 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="); - 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) { diff --git a/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/StartDevCommandDistTest.java b/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/StartDevCommandDistTest.java index 54176fbabd0..57870b8756c 100644 --- a/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/StartDevCommandDistTest.java +++ b/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/StartDevCommandDistTest.java @@ -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); diff --git a/quarkus/tests/junit5/src/main/java/org/keycloak/it/junit5/extension/CLITest.java b/quarkus/tests/junit5/src/main/java/org/keycloak/it/junit5/extension/CLITest.java index 92f37603cee..cfb5d59ad8f 100644 --- a/quarkus/tests/junit5/src/main/java/org/keycloak/it/junit5/extension/CLITest.java +++ b/quarkus/tests/junit5/src/main/java/org/keycloak/it/junit5/extension/CLITest.java @@ -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) diff --git a/quarkus/tests/junit5/src/main/java/org/keycloak/it/junit5/extension/CLITestExtension.java b/quarkus/tests/junit5/src/main/java/org/keycloak/it/junit5/extension/CLITestExtension.java index 4a181a1052f..09ad55274b5 100644 --- a/quarkus/tests/junit5/src/main/java/org/keycloak/it/junit5/extension/CLITestExtension.java +++ b/quarkus/tests/junit5/src/main/java/org/keycloak/it/junit5/extension/CLITestExtension.java @@ -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 invocation, ReflectiveInvocationContext 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")); diff --git a/quarkus/tests/junit5/src/main/java/org/keycloak/it/junit5/extension/ConfigurationTestResource.java b/quarkus/tests/junit5/src/main/java/org/keycloak/it/junit5/extension/ConfigurationTestResource.java deleted file mode 100644 index 5880931d754..00000000000 --- a/quarkus/tests/junit5/src/main/java/org/keycloak/it/junit5/extension/ConfigurationTestResource.java +++ /dev/null @@ -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 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); - } - -} diff --git a/quarkus/tests/junit5/src/main/java/org/keycloak/it/junit5/extension/DryRun.java b/quarkus/tests/junit5/src/main/java/org/keycloak/it/junit5/extension/DryRun.java new file mode 100644 index 00000000000..a0ef9d360e3 --- /dev/null +++ b/quarkus/tests/junit5/src/main/java/org/keycloak/it/junit5/extension/DryRun.java @@ -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 { + +} diff --git a/quarkus/tests/junit5/src/main/java/org/keycloak/it/junit5/extension/KeycloakDistributionDecorator.java b/quarkus/tests/junit5/src/main/java/org/keycloak/it/junit5/extension/KeycloakDistributionDecorator.java index efc42f8f948..10ed3464622 100644 --- a/quarkus/tests/junit5/src/main/java/org/keycloak/it/junit5/extension/KeycloakDistributionDecorator.java +++ b/quarkus/tests/junit5/src/main/java/org/keycloak/it/junit5/extension/KeycloakDistributionDecorator.java @@ -143,4 +143,9 @@ public class KeycloakDistributionDecorator implements KeycloakDistribution { throw new IllegalArgumentException("Not a " + type + " type"); } + + @Override + public void clearEnv() { + delegate.clearEnv(); + } } diff --git a/quarkus/tests/junit5/src/main/java/org/keycloak/it/utils/DockerKeycloakDistribution.java b/quarkus/tests/junit5/src/main/java/org/keycloak/it/utils/DockerKeycloakDistribution.java index 40d81aa33ed..72114a3d2e1 100644 --- a/quarkus/tests/junit5/src/main/java/org/keycloak/it/utils/DockerKeycloakDistribution.java +++ b/quarkus/tests/junit5/src/main/java/org/keycloak/it/utils/DockerKeycloakDistribution.java @@ -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 { + + 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(); + } + } diff --git a/quarkus/tests/junit5/src/main/java/org/keycloak/it/utils/KeycloakDistribution.java b/quarkus/tests/junit5/src/main/java/org/keycloak/it/utils/KeycloakDistribution.java index 21775b11bca..00ca4c0c42b 100644 --- a/quarkus/tests/junit5/src/main/java/org/keycloak/it/utils/KeycloakDistribution.java +++ b/quarkus/tests/junit5/src/main/java/org/keycloak/it/utils/KeycloakDistribution.java @@ -69,4 +69,6 @@ public interface KeycloakDistribution { } D unwrap(Class type); + + void clearEnv(); } diff --git a/quarkus/tests/junit5/src/main/java/org/keycloak/it/utils/RawKeycloakDistribution.java b/quarkus/tests/junit5/src/main/java/org/keycloak/it/utils/RawKeycloakDistribution.java index 4f8b32284f5..a9349d91b29 100644 --- a/quarkus/tests/junit5/src/main/java/org/keycloak/it/utils/RawKeycloakDistribution.java +++ b/quarkus/tests/junit5/src/main/java/org/keycloak/it/utils/RawKeycloakDistribution.java @@ -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(); + } }