From 927f110aef14a639ff2b8a612dff88cec4ef7edd Mon Sep 17 00:00:00 2001 From: Steven Hawkins Date: Tue, 5 Nov 2024 10:42:56 -0500 Subject: [PATCH] fix: consolidating logic dealing with persisted property handling (#34260) closes: #34258 Signed-off-by: Steve Hawkins --- .../quarkus/deployment/KeycloakProcessor.java | 70 +-- .../keycloak/quarkus/runtime/Environment.java | 26 +- .../quarkus/runtime/KeycloakMain.java | 17 +- .../keycloak/quarkus/runtime/cli/Picocli.java | 399 +++++++----------- .../cli/command/AbstractStartCommand.java | 8 - .../quarkus/runtime/cli/command/Build.java | 21 +- .../runtime/cli/command/Completion.java | 4 +- .../runtime/cli/command/ShowConfig.java | 23 +- .../quarkus/runtime/cli/command/Start.java | 20 +- .../runtime/configuration/Configuration.java | 67 +-- .../KeycloakPropertiesConfigSource.java | 5 +- .../QuarkusPropertiesConfigSource.java | 34 +- .../configuration/mappers/PropertyMapper.java | 25 +- .../mappers/PropertyMappers.java | 23 - .../quarkus/runtime/cli/PicocliTest.java | 123 +++++- .../test/AbstractConfigurationTest.java | 9 + .../test/ConfigRegExPatternMatchingTest.java | 24 -- .../configuration/test/ConfigurationTest.java | 1 - .../QuarkusPropertiesAutoBuildDistTest.java | 5 + .../cli/dist/QuarkusPropertiesDistTest.java | 1 + .../it/cli/dist/StartDevCommandDistTest.java | 15 + 21 files changed, 387 insertions(+), 533 deletions(-) delete mode 100644 quarkus/runtime/src/test/java/org/keycloak/quarkus/runtime/configuration/test/ConfigRegExPatternMatchingTest.java diff --git a/quarkus/deployment/src/main/java/org/keycloak/quarkus/deployment/KeycloakProcessor.java b/quarkus/deployment/src/main/java/org/keycloak/quarkus/deployment/KeycloakProcessor.java index 04eb994285..71e32997b1 100644 --- a/quarkus/deployment/src/main/java/org/keycloak/quarkus/deployment/KeycloakProcessor.java +++ b/quarkus/deployment/src/main/java/org/keycloak/quarkus/deployment/KeycloakProcessor.java @@ -45,12 +45,11 @@ import io.quarkus.hibernate.orm.deployment.PersistenceXmlDescriptorBuildItem; import io.quarkus.hibernate.orm.deployment.integration.HibernateOrmIntegrationRuntimeConfiguredBuildItem; import io.quarkus.hibernate.orm.deployment.spi.AdditionalJpaModelBuildItem; import io.quarkus.resteasy.reactive.server.spi.MethodScannerBuildItem; -import io.quarkus.runtime.LaunchMode; import io.quarkus.runtime.configuration.ConfigurationException; import io.quarkus.vertx.http.deployment.HttpRootPathBuildItem; import io.quarkus.vertx.http.deployment.NonApplicationRootPathBuildItem; import io.quarkus.vertx.http.deployment.RouteBuildItem; -import io.smallrye.config.ConfigValue; + import org.eclipse.microprofile.health.Readiness; import org.hibernate.cfg.AvailableSettings; import org.hibernate.jpa.boot.spi.PersistenceUnitDescriptor; @@ -97,11 +96,11 @@ import org.keycloak.provider.ProviderManager; import org.keycloak.provider.Spi; import org.keycloak.quarkus.runtime.Environment; import org.keycloak.quarkus.runtime.KeycloakRecorder; +import org.keycloak.quarkus.runtime.cli.Picocli; import org.keycloak.quarkus.runtime.configuration.Configuration; import org.keycloak.quarkus.runtime.configuration.KeycloakConfigSourceProvider; import org.keycloak.quarkus.runtime.configuration.MicroProfileConfigProvider; import org.keycloak.quarkus.runtime.configuration.PersistedConfigSource; -import org.keycloak.quarkus.runtime.configuration.QuarkusPropertiesConfigSource; import org.keycloak.quarkus.runtime.configuration.mappers.PropertyMapper; import org.keycloak.quarkus.runtime.configuration.mappers.PropertyMappers; import org.keycloak.quarkus.runtime.integration.resteasy.KeycloakHandlerChainCustomizer; @@ -130,7 +129,6 @@ import org.keycloak.vault.FilesPlainTextVaultProviderFactory; import jakarta.persistence.Entity; import jakarta.persistence.spi.PersistenceUnitTransactionType; import java.io.ByteArrayOutputStream; -import java.io.File; import java.io.IOException; import java.io.InputStream; import java.net.URL; @@ -153,14 +151,10 @@ import java.util.logging.Handler; import static org.keycloak.connections.jpa.util.JpaUtils.loadSpecificNamedQueries; import static org.keycloak.quarkus.runtime.Environment.getCurrentOrCreateFeatureProfile; -import static org.keycloak.quarkus.runtime.Environment.getProviderFiles; import static org.keycloak.quarkus.runtime.Providers.getProviderManager; import static org.keycloak.quarkus.runtime.configuration.Configuration.getOptionalKcValue; import static org.keycloak.quarkus.runtime.configuration.Configuration.getOptionalValue; -import static org.keycloak.quarkus.runtime.configuration.Configuration.getPropertyNames; import static org.keycloak.quarkus.runtime.configuration.MicroProfileConfigProvider.NS_KEYCLOAK_PREFIX; -import static org.keycloak.quarkus.runtime.configuration.MicroProfileConfigProvider.NS_QUARKUS; -import static org.keycloak.quarkus.runtime.configuration.QuarkusPropertiesConfigSource.QUARKUS_PROPERTY_ENABLED; import static org.keycloak.quarkus.runtime.storage.database.jpa.QuarkusJpaConnectionProviderFactory.QUERY_PROPERTY_PREFIX; import static org.keycloak.representations.provider.ScriptProviderDescriptor.AUTHENTICATORS; import static org.keycloak.representations.provider.ScriptProviderDescriptor.MAPPERS; @@ -578,31 +572,7 @@ class KeycloakProcessor { */ @BuildStep(onlyIf = IsReAugmentation.class) void persistBuildTimeProperties(BuildProducer resources) { - Properties properties = new Properties(); - - putPersistedProperty(properties, "kc.db"); - - for (String name : getPropertyNames()) { - putPersistedProperty(properties, name); - } - - for (File jar : getProviderFiles().values()) { - properties.put(String.format("kc.provider.file.%s.last-modified", jar.getName()), String.valueOf(jar.lastModified())); - } - - if (!Environment.isRebuildCheck()) { - // not auto-build (e.g.: start without optimized option) but a regular build to create an optimized server image - Configuration.markAsOptimized(properties); - } - - String profile = org.keycloak.common.util.Environment.getProfile(); - - if (profile != null) { - properties.put(org.keycloak.common.util.Environment.PROFILE, profile); - properties.put(LaunchMode.current().getProfileKey(), profile); - } - - properties.put(QUARKUS_PROPERTY_ENABLED, String.valueOf(QuarkusPropertiesConfigSource.getConfigurationFile() != null)); + Properties properties = Picocli.getNonPersistedBuildTimeOptions(); try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) { properties.store(outputStream, " Auto-generated, DO NOT change this file"); @@ -612,40 +582,6 @@ class KeycloakProcessor { } } - private void putPersistedProperty(Properties properties, String name) { - PropertyMapper mapper = PropertyMappers.getMapper(name); - ConfigValue value = null; - - if (mapper == null) { - if (name.startsWith(NS_QUARKUS)) { - value = Configuration.getConfigValue(name); - - if (!QuarkusPropertiesConfigSource.isSameSource(value)) { - return; - } - } else if (PropertyMappers.isSpiBuildTimeProperty(name)) { - value = Configuration.getConfigValue(name); - } - } else if (mapper.isBuildTime()) { - name = mapper.getFrom(); - value = Configuration.getConfigValue(name); - } - - if (value != null && value.getValue() != null) { - if (value.getConfigSourceName() == null) { - // only persist build options resolved from config sources and not default values - return; - } - String rawValue = value.getRawValue(); - - if (rawValue == null) { - rawValue = value.getValue(); - } - - properties.put(name, rawValue); - } - } - /** * This will cause quarkus to include specified modules in the jandex index. For example keycloak-services is needed as it includes * most of the JAX-RS resources, which are required to register Resteasy builtin providers. 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 f915b86dde..ea46ba7bda 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 @@ -17,8 +17,6 @@ package org.keycloak.quarkus.runtime; -import static org.keycloak.quarkus.runtime.configuration.Configuration.getBuildTimeProperty; - import java.io.File; import java.io.FilenameFilter; import java.nio.file.Path; @@ -36,6 +34,7 @@ import io.smallrye.config.SmallRyeConfig; import org.apache.commons.lang3.SystemUtils; import org.keycloak.common.Profile; import org.keycloak.quarkus.runtime.cli.command.AbstractCommand; +import org.keycloak.quarkus.runtime.configuration.Configuration; import org.keycloak.quarkus.runtime.configuration.PersistedConfigSource; public final class Environment { @@ -109,21 +108,18 @@ public final class Environment { } } - public static String getCurrentOrPersistedProfile() { + /** + * Update the profile settings based upon what was set in the system, environment, or optionally persistent values + */ + public static String updateProfile(boolean usePersistent) { String profile = org.keycloak.common.util.Environment.getProfile(); - if(profile == null) { + if(profile == null && usePersistent) { profile = PersistedConfigSource.getInstance().getValue(org.keycloak.common.util.Environment.PROFILE); } - return profile; - } - - public static String getProfileOrDefault(String defaultProfile) { - String profile = org.keycloak.common.util.Environment.getProfile(); - if (profile == null) { - profile = defaultProfile; + profile = Environment.PROD_PROFILE_VALUE; } - + setProfile(profile); return profile; } @@ -132,7 +128,7 @@ public final class Environment { return true; } - return org.keycloak.common.util.Environment.DEV_PROFILE_VALUE.equals(getBuildTimeProperty(org.keycloak.common.util.Environment.PROFILE).orElse(null)); + return org.keycloak.common.util.Environment.DEV_PROFILE_VALUE.equals(Configuration.getNonPersistedConfigValue(org.keycloak.common.util.Environment.PROFILE).getValue()); } public static boolean isDevProfile(){ @@ -220,6 +216,10 @@ 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"); + } public static boolean isRebuilt() { return Boolean.getBoolean("kc.config.built"); 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 f1263646e6..8b76c0fe24 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 @@ -19,12 +19,9 @@ 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.getProfileOrDefault; 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; -import static org.keycloak.quarkus.runtime.cli.command.AbstractStartCommand.wasBuildEverRun; -import static org.keycloak.quarkus.runtime.cli.command.Start.isDevProfileNotAllowed; import java.io.PrintWriter; import java.util.ArrayList; @@ -76,19 +73,15 @@ public class KeycloakMain implements QuarkusApplication { handleUsageError(e.getMessage()); return; } - + 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 - if (!wasBuildEverRun()) { - handleUsageError(Messages.optimizedUsedForFirstStartup()); - return; - } - - if (isDevProfileNotAllowed()) { + Environment.updateProfile(true); + if (Environment.isDevProfile()) { handleUsageError(Messages.devProfileNotAllowedError(Start.NAME)); return; } @@ -156,7 +149,7 @@ public class KeycloakMain implements QuarkusApplication { Quarkus.run(KeycloakMain.class, (exitCode, cause) -> { if (cause != null) { errorHandler.error(errStream, - String.format("Failed to start server in (%s) mode", getKeycloakModeFromProfile(getProfileOrDefault("prod"))), + String.format("Failed to start server in (%s) mode", getKeycloakModeFromProfile(org.keycloak.common.util.Environment.getProfile())), cause.getCause()); } @@ -168,7 +161,7 @@ public class KeycloakMain implements QuarkusApplication { }, args); } catch (Throwable cause) { errorHandler.error(errStream, - String.format("Unexpected error when starting the server in (%s) mode", getKeycloakModeFromProfile(getProfileOrDefault("prod"))), + String.format("Unexpected error when starting the server in (%s) mode", getKeycloakModeFromProfile(org.keycloak.common.util.Environment.getProfile())), cause.getCause()); System.exit(1); } 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 7e6fb6be01..1478ef2799 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 @@ -18,8 +18,8 @@ package org.keycloak.quarkus.runtime.cli; import static java.lang.String.format; -import static java.util.Optional.ofNullable; -import static java.util.stream.StreamSupport.stream; +import static org.keycloak.quarkus.runtime.Environment.getProviderFiles; +import static org.keycloak.quarkus.runtime.Environment.isDevMode; import static org.keycloak.quarkus.runtime.Environment.isRebuild; import static org.keycloak.quarkus.runtime.Environment.isRebuildCheck; import static org.keycloak.quarkus.runtime.Environment.isRebuilt; @@ -27,16 +27,9 @@ import static org.keycloak.quarkus.runtime.cli.OptionRenderer.decorateDuplicitOp import static org.keycloak.quarkus.runtime.cli.command.AbstractStartCommand.OPTIMIZED_BUILD_OPTION_LONG; import static org.keycloak.quarkus.runtime.configuration.ConfigArgsConfigSource.parseConfigArgs; import static org.keycloak.quarkus.runtime.configuration.Configuration.OPTION_PART_SEPARATOR; -import static org.keycloak.quarkus.runtime.configuration.Configuration.getBuildTimeProperty; -import static org.keycloak.quarkus.runtime.configuration.Configuration.getConfig; -import static org.keycloak.quarkus.runtime.Environment.isDevMode; -import static org.keycloak.quarkus.runtime.configuration.Configuration.getCurrentBuiltTimeProperty; import static org.keycloak.quarkus.runtime.configuration.Configuration.getRawPersistedProperty; -import static org.keycloak.quarkus.runtime.configuration.Configuration.getRuntimeProperty; import static org.keycloak.quarkus.runtime.configuration.MicroProfileConfigProvider.NS_KEYCLOAK_PREFIX; import static org.keycloak.quarkus.runtime.configuration.mappers.PropertyMappers.maskValue; -import static org.keycloak.quarkus.runtime.configuration.mappers.PropertyMappers.isBuildTimeProperty; -import static org.keycloak.utils.StringUtil.isNotBlank; import static picocli.CommandLine.Model.UsageMessageSpec.SECTION_KEY_COMMAND_LIST; import java.io.File; @@ -44,62 +37,59 @@ import java.io.PrintWriter; import java.util.ArrayList; import java.util.Arrays; import java.util.EnumMap; -import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; -import java.util.Objects; import java.util.Optional; +import java.util.Properties; import java.util.Set; import java.util.function.BiConsumer; import java.util.function.Consumer; -import java.util.regex.Pattern; -import java.util.stream.Collectors; -import org.eclipse.microprofile.config.spi.ConfigSource; import org.keycloak.common.profile.ProfileException; import org.keycloak.config.DeprecatedMetadata; import org.keycloak.config.Option; import org.keycloak.config.OptionCategory; +import org.keycloak.quarkus.runtime.Environment; +import org.keycloak.quarkus.runtime.KeycloakMain; +import org.keycloak.quarkus.runtime.Messages; import org.keycloak.quarkus.runtime.cli.command.AbstractCommand; -import org.keycloak.quarkus.runtime.cli.command.BootstrapAdmin; import org.keycloak.quarkus.runtime.cli.command.Build; +import org.keycloak.quarkus.runtime.cli.command.Completion; import org.keycloak.quarkus.runtime.cli.command.Main; import org.keycloak.quarkus.runtime.cli.command.ShowConfig; -import org.keycloak.quarkus.runtime.cli.command.Start; import org.keycloak.quarkus.runtime.cli.command.StartDev; -import org.keycloak.quarkus.runtime.cli.command.Tools; import org.keycloak.quarkus.runtime.configuration.ConfigArgsConfigSource; import org.keycloak.quarkus.runtime.configuration.Configuration; import org.keycloak.quarkus.runtime.configuration.DisabledMappersInterceptor; import org.keycloak.quarkus.runtime.configuration.KcUnmatchedArgumentException; -import org.keycloak.quarkus.runtime.configuration.PersistedConfigSource; +import org.keycloak.quarkus.runtime.configuration.KeycloakPropertiesConfigSource; +import org.keycloak.quarkus.runtime.configuration.MicroProfileConfigProvider; import org.keycloak.quarkus.runtime.configuration.PropertyMappingInterceptor; import org.keycloak.quarkus.runtime.configuration.QuarkusPropertiesConfigSource; -import org.keycloak.quarkus.runtime.configuration.mappers.PropertyMappers; import org.keycloak.quarkus.runtime.configuration.mappers.PropertyMapper; -import org.keycloak.quarkus.runtime.Environment; -import org.keycloak.quarkus.runtime.KeycloakMain; +import org.keycloak.quarkus.runtime.configuration.mappers.PropertyMappers; import io.quarkus.bootstrap.runner.QuarkusEntryPoint; +import io.quarkus.runtime.LaunchMode; import io.smallrye.config.ConfigValue; - import picocli.CommandLine; -import picocli.CommandLine.ParameterException; -import picocli.CommandLine.ParseResult; import picocli.CommandLine.DuplicateOptionAnnotationsException; import picocli.CommandLine.Help.Ansi; import picocli.CommandLine.Help.Ansi.Style; import picocli.CommandLine.Help.ColorScheme; import picocli.CommandLine.IFactory; +import picocli.CommandLine.Model.ArgGroupSpec; import picocli.CommandLine.Model.CommandSpec; import picocli.CommandLine.Model.ISetter; import picocli.CommandLine.Model.OptionSpec; -import picocli.CommandLine.Model.ArgGroupSpec; +import picocli.CommandLine.ParameterException; +import picocli.CommandLine.ParseResult; public class Picocli { + private static final String KC_PROVIDER_FILE_PREFIX = "kc.provider.file."; public static final String ARG_PREFIX = "--"; public static final String ARG_SHORT_PREFIX = "-"; public static final String NO_PARAM_LABEL = "none"; @@ -172,7 +162,13 @@ public class Picocli { } if (currentSpec != null) { - addCommandOptions(cliArgs, currentSpec.commandLine()); + CommandLine commandLine = currentSpec.commandLine(); + addCommandOptions(cliArgs, commandLine); + + if (commandLine != null && commandLine.getCommand() instanceof AbstractCommand ac) { + // set current parsed command + Environment.setParsedCommand(ac); + } } if (isRebuildCheck()) { @@ -207,7 +203,7 @@ public class Picocli { } } - protected int runReAugmentationIfNeeded(List cliArgs, CommandLine cmd, CommandLine currentCommand) { + private int runReAugmentationIfNeeded(List cliArgs, CommandLine cmd, CommandLine currentCommand) { int exitCode = 0; if (currentCommand == null) { @@ -220,14 +216,10 @@ public class Picocli { return exitCode; } - if (currentCommandName.equals(StartDev.NAME)) { - String profile = org.keycloak.common.util.Environment.getProfile(); - - if (profile == null) { - // force the server image to be set with the dev profile - Environment.forceDevProfile(); - } - } + // TODO: ensure that the config has not yet been initialized + // - there's currently no good way to do that directly on ConfigProviderResolver + initProfile(cliArgs, currentCommandName); + if (requiresReAugmentation(currentCommand)) { PropertyMappers.sanitizeDisabledMappers(); exitCode = runReAugmentation(cliArgs, cmd); @@ -236,34 +228,42 @@ public class Picocli { return exitCode; } + protected void initProfile(List cliArgs, String currentCommandName) { + if (currentCommandName.equals(StartDev.NAME)) { + // force the server image to be set with the dev profile + Environment.forceDevProfile(); + } else { + Environment.updateProfile(false); + + // override from the cli if specified + parseConfigArgs(cliArgs, (k, v) -> { + if (k.equals(Main.PROFILE_SHORT_NAME) || k.equals(Main.PROFILE_LONG_NAME)) { + Environment.setProfile(v); + } + }, ignored -> {}); + } + } + private static boolean shouldSkipRebuild(List cliArgs, String currentCommandName) { return cliArgs.contains("--help") || cliArgs.contains("-h") || cliArgs.contains("--help-all") || currentCommandName.equals(Build.NAME) || currentCommandName.equals(ShowConfig.NAME) - || currentCommandName.equals(BootstrapAdmin.NAME) - || currentCommandName.equals(Tools.NAME); + || currentCommandName.equals(Completion.NAME); } private static boolean requiresReAugmentation(CommandLine cmdCommand) { - if (ConfigArgsConfigSource.getAllCliArgs().contains(Start.NAME) - // run time dev mode is not set - && !org.keycloak.common.util.Environment.isDevMode() - // build time dev mode was set - && org.keycloak.common.util.Environment.DEV_PROFILE_VALUE.equals(getBuildTimeProperty(org.keycloak.common.util.Environment.PROFILE).orElse(null))) { - return true; + Map rawPersistedProperties = Configuration.getRawPersistedProperties(); + if (rawPersistedProperties.isEmpty()) { + return true; // no build yet } - - if (hasConfigChanges(cmdCommand)) { - if (!ConfigArgsConfigSource.getAllCliArgs().contains(StartDev.NAME) && "dev".equals(getConfig().getOptionalValue("kc.profile", String.class).orElse(null))) { - return false; - } - - return true; - } - - return hasProviderChanges(); + var current = getNonPersistedBuildTimeOptions(); + + // everything but the optimized value must match + String key = Configuration.KC_OPTIMIZED; + Optional.ofNullable(rawPersistedProperties.get(key)).ifPresentOrElse(value -> current.put(key, value), () -> current.remove(key)); + return !rawPersistedProperties.equals(current); } /** @@ -294,7 +294,9 @@ public class Picocli { private static int runReAugmentation(List cliArgs, CommandLine cmd) { if(!isDevMode() && cmd != null) { cmd.getOut().println("Changes detected in configuration. Updating the server image."); - checkChangesInBuildOptionsDuringAutoBuild(); + if (Configuration.isOptimized()) { + checkChangesInBuildOptionsDuringAutoBuild(cmd.getOut()); + } } List configArgsList = new ArrayList<>(); @@ -316,36 +318,8 @@ public class Picocli { return exitCode; } - private static boolean hasProviderChanges() { - Map persistedProps = PersistedConfigSource.getInstance().getProperties(); - Map deployedProviders = Environment.getProviderFiles(); - - if (persistedProps.isEmpty()) { - return !deployedProviders.isEmpty(); - } - - Set providerKeys = persistedProps.keySet().stream().filter(Picocli::isProviderKey).collect(Collectors.toSet()); - - if (deployedProviders.size() != providerKeys.size()) { - return true; - } - - for (String key : providerKeys) { - String fileName = key.substring("kc.provider.file".length() + 1, key.lastIndexOf('.')); - - if (!deployedProviders.containsKey(fileName)) { - return true; - } - - File file = deployedProviders.get(fileName); - String lastModified = persistedProps.get(key); - - if (!lastModified.equals(String.valueOf(file.lastModified()))) { - return true; - } - } - - return false; + private static boolean wasBuildEverRun() { + return !Configuration.getRawPersistedProperties().isEmpty(); } /** @@ -356,6 +330,10 @@ public class Picocli { * @param outWriter */ public static void validateConfig(List cliArgs, AbstractCommand abstractCommand, PrintWriter outWriter) { + if (cliArgs.contains(OPTIMIZED_BUILD_OPTION_LONG) && !wasBuildEverRun()) { + throw new PropertyException(Messages.optimizedUsedForFirstStartup()); + } + IncludeOptions options = getIncludeOptions(cliArgs, abstractCommand, abstractCommand.getName()); if (!options.includeBuildTime && !options.includeRuntime) { @@ -391,8 +369,7 @@ public class Picocli { ConfigValue configValue = Configuration.getConfigValue(mapper.getFrom()); String configValueStr = configValue.getValue(); - // don't consider missing or anything below standard env properties - if (configValueStr == null || configValue.getConfigSourceOrdinal() < 300) { + if (configValueStr == null || !isUserModifiable(configValue)) { continue; } @@ -455,14 +432,19 @@ public class Picocli { } } + private static boolean isUserModifiable(ConfigValue configValue) { + // This could check as low as SysPropConfigSource DEFAULT_ORDINAL, which is 400 + // for now we won't validate these as it's not expected for the user to specify options via system properties + return configValue.getConfigSourceOrdinal() >= KeycloakPropertiesConfigSource.PROPERTIES_FILE_ORDINAL; + } + private static void checkSpiOptions(IncludeOptions options, final List ignoredBuildTime, final List ignoredRunTime) { - String kcSpiPrefix = NS_KEYCLOAK_PREFIX + "spi"; for (String key : Configuration.getConfig().getPropertyNames()) { - if (!key.startsWith(kcSpiPrefix)) { + if (!key.startsWith(PropertyMappers.KC_SPI_PREFIX)) { continue; } - boolean buildTimeOption = key.endsWith("-provider") || key.endsWith("-provider-default") || key.endsWith("-enabled"); + boolean buildTimeOption = PropertyMappers.isSpiBuildTimeProperty(key); ConfigValue configValue = Configuration.getConfigValue(key); String configValueStr = configValue.getValue(); @@ -558,124 +540,59 @@ public class Picocli { String.join("\n", properties)), outWriter); } - private static boolean hasConfigChanges(CommandLine cmdCommand) { - Optional currentProfile = ofNullable(org.keycloak.common.util.Environment.getProfile()); - Optional persistedProfile = getBuildTimeProperty("kc.profile"); - - if (!persistedProfile.orElse("").equals(currentProfile.orElse(""))) { - return true; - } - - for (String propertyName : getConfig().getPropertyNames()) { - // only check keycloak build-time properties - if (!isBuildTimeProperty(propertyName)) { - continue; - } - - ConfigValue configValue = getConfig().getConfigValue(propertyName); - - if (configValue == null || configValue.getConfigSourceName() == null) { - continue; - } - - // try to resolve any property set using profiles - if (propertyName.startsWith("%")) { - propertyName = propertyName.substring(propertyName.indexOf('.') + 1); - } - - String persistedValue = getBuildTimeProperty(propertyName).orElse(""); - String runtimeValue = getRuntimeProperty(propertyName).orElse(null); - - // compare only the relevant options for this command, as not all options might be set for this command - if (cmdCommand.getCommand() instanceof AbstractCommand) { - AbstractCommand abstractCommand = cmdCommand.getCommand(); - PropertyMapper mapper = PropertyMappers.getMapper(propertyName); - if (mapper != null) { - if (!abstractCommand.getOptionCategories().contains(mapper.getCategory())) { - continue; - } + public static Properties getNonPersistedBuildTimeOptions() { + Properties properties = new Properties(); + // TODO: could get only non-persistent property names + Configuration.getPropertyNames().forEach(name -> { + boolean quarkus = false; + PropertyMapper mapper = PropertyMappers.getMapper(name); + if (mapper != null) { + if (!mapper.isBuildTime()) { + return; } - } - - if (runtimeValue == null && isNotBlank(persistedValue)) { - PropertyMapper mapper = PropertyMappers.getMapper(propertyName); - - if (mapper != null && persistedValue.equals(Option.getDefaultValueString(mapper.getDefaultValue().orElse(null)))) { - // same as default - continue; + name = mapper.getFrom(); + if (properties.containsKey(name)) { + return; } - - // probably because it was unset - return true; + } else if (name.startsWith(MicroProfileConfigProvider.NS_QUARKUS)) { + // 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)) { + return; } - - // changes to a single property is enough to indicate changes to configuration - if (!persistedValue.equals(runtimeValue)) { - return true; + ConfigValue value = Configuration.getNonPersistedConfigValue(name); + if (value.getValue() == null || value.getConfigSourceName() == null + || (quarkus && !value.getConfigSourceName().equals(QuarkusPropertiesConfigSource.NAME))) { + // only persist build options resolved from config sources and not default values + return; } + // since we're presisting all quarkus values, this may leak some runtime information - we don't want + // to capture expanded expressions that may be referencing environment variables + String stringValue = value.getValue(); + if (quarkus && value.getRawValue() != null) { + stringValue = value.getRawValue(); + } + properties.put(name, stringValue); + }); + + // the following should be ignored when output the optimized check message + // they are either not set by the user, or not properly initialized + + for (File jar : getProviderFiles().values()) { + properties.put(String.format(KC_PROVIDER_FILE_PREFIX + "%s.last-modified", jar.getName()), String.valueOf(jar.lastModified())); } - //check for defined quarkus raw build properties for UserStorageProvider extensions - if (QuarkusPropertiesConfigSource.getConfigurationFile() != null) { - Optional quarkusPropertiesConfigSource = getConfig().getConfigSource(QuarkusPropertiesConfigSource.NAME); - - if (quarkusPropertiesConfigSource.isPresent()) { - Map foundQuarkusBuildProperties = findSupportedRawQuarkusBuildProperties(quarkusPropertiesConfigSource.get().getProperties().entrySet()); - - //only check if buildProps are found in quarkus properties file. - if (!foundQuarkusBuildProperties.isEmpty()) { - Optional persistedConfigSource = getConfig().getConfigSource(PersistedConfigSource.NAME); - - if(persistedConfigSource.isPresent()) { - for(String key : foundQuarkusBuildProperties.keySet()) { - if (notContainsKey(persistedConfigSource.get(), key)) { - //if persisted cs does not contain raw quarkus key from quarkus.properties, assume build is needed as the key is new. - return true; - } - } - - //if it contains the key, check if the value actually changed from the persisted one. - return hasAtLeastOneChangedBuildProperty(foundQuarkusBuildProperties, persistedConfigSource.get().getProperties().entrySet()); - } - } - } + if (!Environment.isRebuildCheck()) { + // not auto-build (e.g.: start without optimized option) but a regular build to create an optimized server image + Configuration.markAsOptimized(properties); } - return false; - } + String profile = org.keycloak.common.util.Environment.getProfile(); + properties.put(org.keycloak.common.util.Environment.PROFILE, profile); + properties.put(LaunchMode.current().getProfileKey(), profile); - private static boolean hasAtLeastOneChangedBuildProperty(Map foundQuarkusBuildProperties, Set> persistedEntries) { - for(Map.Entry persistedEntry : persistedEntries) { - if (foundQuarkusBuildProperties.containsKey(persistedEntry.getKey())) { - return isChangedValue(foundQuarkusBuildProperties, persistedEntry); - } - } - - return false; - } - - private static boolean notContainsKey(ConfigSource persistedConfigSource, String key) { - return !persistedConfigSource.getProperties().containsKey(key); - } - - private static Map findSupportedRawQuarkusBuildProperties(Set> entries) { - Pattern buildTimePattern = Pattern.compile(QuarkusPropertiesConfigSource.QUARKUS_DATASOURCE_BUILDTIME_REGEX); - Map result = new HashMap<>(); - - for(Map.Entry entry : entries) { - if (buildTimePattern.matcher(entry.getKey()).matches()) { - result.put(entry.getKey(), entry.getValue()); - } - } - return result; - } - - private static boolean isChangedValue(Map foundQuarkusBuildProps, Map.Entry persistedEntry) { - return !foundQuarkusBuildProps.get(persistedEntry.getKey()).equals(persistedEntry.getValue()); - } - - private static boolean isProviderKey(String key) { - return key.startsWith("kc.provider.file"); + return properties; } public CommandLine createCommandLine(Consumer consumer) { @@ -740,12 +657,9 @@ public class Picocli { } private static void addCommandOptions(List cliArgs, CommandLine command) { - if (command != null && command.getCommand() instanceof AbstractCommand ac) { + if (command != null && command.getCommand() instanceof AbstractCommand) { IncludeOptions options = getIncludeOptions(cliArgs, command.getCommand(), command.getCommandName()); - // set current parsed command - Environment.setParsedCommand(ac); - if (!options.includeBuildTime && !options.includeRuntime) { return; } @@ -938,48 +852,51 @@ public class Picocli { return args; } - private static void checkChangesInBuildOptionsDuringAutoBuild() { - if (Configuration.isOptimized()) { - List> buildOptions = stream(Configuration.getPropertyNames(true).spliterator(), false) - .sorted() - .map(PropertyMappers::getMapper) - .filter(Objects::nonNull).collect(Collectors.toList()); - - if (buildOptions.isEmpty()) { - return; + private static void checkChangesInBuildOptionsDuringAutoBuild(PrintWriter out) { + StringBuilder options = new StringBuilder(); + + var current = getNonPersistedBuildTimeOptions(); + var persisted = Configuration.getRawPersistedProperties(); + + // TODO: order is not well defined here + + current.forEach((key, value) -> { + String persistedValue = persisted.get(key); + if (!value.equals(persistedValue)) { + optionChanged(options, (String)key, persistedValue, (String)value); } - - StringBuilder options = new StringBuilder(); - - for (PropertyMapper mapper : buildOptions) { - String newValue = ofNullable(getCurrentBuiltTimeProperty(mapper.getFrom())) - .map(ConfigValue::getValue) - .orElse(""); - String currentValue = getRawPersistedProperty(mapper.getFrom()).get(); - - if (newValue.equals(currentValue)) { - continue; - } - - String name = mapper.getOption().getKey(); - - options.append("\n\t- ") - .append(name).append("=").append(currentValue) - .append(" > ") - .append(name).append("=").append(newValue); + }); + + persisted.forEach((key, value) -> { + if (current.get(key) == null) { + optionChanged(options, key, value, null); } + }); - if (options.length() > 0) { - System.out.println( - Ansi.AUTO.string( - new StringBuilder("@|bold,red ") - .append("The previous optimized build will be overridden with the following build options:") - .append(options) - .append("\nTo avoid that, run the 'build' command again and then start the optimized server instance using the '--optimized' flag.") - .append("|@").toString() - ) - ); - } + if (options.length() > 0) { + out.println( + Ansi.AUTO.string( + new StringBuilder("@|bold,red ") + .append("The previous optimized build will be overridden with the following build options:") + .append(options) + .append("\nTo avoid that, run the 'build' command again and then start the optimized server instance using the '--optimized' flag.") + .append("|@").toString() + ) + ); + } + } + + private static void optionChanged(StringBuilder options, String key, String oldValue, String newValue) { + // the assumption here is that no build time options need mask handling + boolean isIgnored = !key.startsWith(MicroProfileConfigProvider.NS_KEYCLOAK_PREFIX) + || key.startsWith(KC_PROVIDER_FILE_PREFIX) || key.equals(Configuration.KC_OPTIMIZED) + || key.equals(org.keycloak.common.util.Environment.PROFILE); + if (!isIgnored) { + key = key.substring(3); + options.append("\n\t- ").append(key).append("=") + .append(Optional.ofNullable(oldValue).orElse("")).append(" > ") + .append(key).append("=") + .append(Optional.ofNullable(newValue).orElse("")); } } 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 ae3051190f..2132406f1d 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 @@ -19,10 +19,6 @@ package org.keycloak.quarkus.runtime.cli.command; import org.keycloak.config.OptionCategory; import org.keycloak.quarkus.runtime.Environment; -import org.keycloak.quarkus.runtime.KeycloakMain; -import org.keycloak.quarkus.runtime.Messages; -import org.keycloak.quarkus.runtime.cli.ExecutionExceptionHandler; -import org.keycloak.quarkus.runtime.configuration.ConfigArgsConfigSource; import org.keycloak.quarkus.runtime.configuration.mappers.HostnameV2PropertyMappers; import org.keycloak.quarkus.runtime.configuration.mappers.HttpPropertyMappers; @@ -46,10 +42,6 @@ public abstract class AbstractStartCommand extends AbstractCommand implements Ru HostnameV2PropertyMappers.validateConfig(); validateConfig(); - if (ConfigArgsConfigSource.getAllCliArgs().contains(OPTIMIZED_BUILD_OPTION_LONG) && !wasBuildEverRun()) { - executionError(spec.commandLine(), Messages.optimizedUsedForFirstStartup()); - } - picocli.start(cmd); } 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 4fd368432e..50ccb188ae 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 @@ -21,15 +21,14 @@ import static org.keycloak.config.ClassLoaderOptions.QUARKUS_REMOVED_ARTIFACTS_P import static org.keycloak.quarkus.runtime.Environment.getHomePath; import static org.keycloak.quarkus.runtime.Environment.isDevProfile; import static org.keycloak.quarkus.runtime.cli.Picocli.println; -import static org.keycloak.quarkus.runtime.configuration.ConfigArgsConfigSource.getAllCliArgs; import io.quarkus.runtime.LaunchMode; + 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 io.quarkus.bootstrap.runner.QuarkusEntryPoint; import io.quarkus.bootstrap.runner.RunnerClassLoader; import io.smallrye.config.ConfigValue; @@ -68,7 +67,10 @@ public final class Build extends AbstractCommand implements Runnable { @Override public void run() { - exitWithErrorIfDevProfileIsSetAndNotStartDev(); + if (org.keycloak.common.util.Environment.getProfile() == null) { + Environment.setProfile(Environment.PROD_PROFILE_VALUE); + } + exitWithErrorIfDevProfileIsSet(); System.setProperty("quarkus.launch.rebuild", "true"); validateConfig(); @@ -94,7 +96,7 @@ public final class Build extends AbstractCommand implements Runnable { private static void configureBuildClassLoader() { // ignored artifacts must be set prior to starting re-augmentation - Optional.ofNullable(Configuration.getCurrentBuiltTimeProperty(QUARKUS_REMOVED_ARTIFACTS_PROPERTY)) + Optional.ofNullable(Configuration.getNonPersistedConfigValue(QUARKUS_REMOVED_ARTIFACTS_PROPERTY)) .map(ConfigValue::getValue) .ifPresent(s -> System.setProperty(QUARKUS_REMOVED_ARTIFACTS_PROPERTY, s)); } @@ -110,9 +112,14 @@ public final class Build extends AbstractCommand implements Runnable { return super.getOptionCategories(); } - private void exitWithErrorIfDevProfileIsSetAndNotStartDev() { - if (Environment.isDevProfile() && !getAllCliArgs().contains(StartDev.NAME)) { - executionError(spec.commandLine(), Messages.devProfileNotAllowedError(NAME)); + private void exitWithErrorIfDevProfileIsSet() { + if (Environment.isDevProfile()) { + String cmd = Environment.getParsedCommand().map(AbstractCommand::getName).orElse(getName()); + // we allow start-dev, and import|export|bootstrap-admin --profile=dev + // but not start --profile=dev, nor build --profile=dev + if (Start.NAME.equals(cmd) || Build.NAME.equals(cmd)) { + executionError(spec.commandLine(), Messages.devProfileNotAllowedError(cmd)); + } } } diff --git a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/cli/command/Completion.java b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/cli/command/Completion.java index b937efc4c8..05516a43ff 100644 --- a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/cli/command/Completion.java +++ b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/cli/command/Completion.java @@ -20,7 +20,7 @@ package org.keycloak.quarkus.runtime.cli.command; import picocli.AutoComplete; import picocli.CommandLine.Command; -@Command(name = "completion", +@Command(name = Completion.NAME, header = "Generate bash/zsh completion script for ${ROOT-COMMAND-NAME:-the root command of this command}.", description = { "", @@ -29,4 +29,6 @@ import picocli.CommandLine.Command; "", " source <(${PARENT-COMMAND-FULL-NAME:-$PARENTCOMMAND} ${COMMAND-NAME})"}) public class Completion extends AutoComplete.GenerateCompletion { + + public static final String NAME = "completion"; } diff --git a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/cli/command/ShowConfig.java b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/cli/command/ShowConfig.java index ba29f19533..ebb17a6485 100644 --- a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/cli/command/ShowConfig.java +++ b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/cli/command/ShowConfig.java @@ -17,17 +17,13 @@ package org.keycloak.quarkus.runtime.cli.command; -import static org.keycloak.quarkus.runtime.Environment.getCurrentOrPersistedProfile; -import static org.keycloak.quarkus.runtime.Environment.setProfile; import static org.keycloak.quarkus.runtime.configuration.Configuration.getConfigValue; import static org.keycloak.quarkus.runtime.configuration.Configuration.getPropertyNames; -import static org.keycloak.quarkus.runtime.configuration.Configuration.getRuntimeProperty; import static org.keycloak.quarkus.runtime.configuration.mappers.PropertyMappers.maskValue; import java.util.HashSet; import java.util.List; import java.util.Map; -import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; import java.util.stream.StreamSupport; @@ -60,23 +56,18 @@ public final class ShowConfig extends AbstractCommand implements Runnable { @Override public void run() { - System.setProperty("kc.show.config", filter); - String configArgs = System.getProperty("kc.show.config"); - String profile = Optional.ofNullable(getCurrentOrPersistedProfile()).orElse(Environment.PROD_PROFILE_VALUE); - setProfile(profile); + String profile = Environment.updateProfile(true); Map> properties = getPropertiesByGroup(); printRunTimeConfig(properties, profile); - if (configArgs.equalsIgnoreCase("all")) { + if (filter.equalsIgnoreCase("all")) { spec.commandLine().getOut().println("Quarkus Configuration:"); properties.get(MicroProfileConfigProvider.NS_QUARKUS).stream().sorted() .forEachOrdered(this::printProperty); } - if (!Boolean.getBoolean("kc.show.config.runtime")) { - Quarkus.asyncExit(0); - } + Quarkus.asyncExit(0); } private void printRunTimeConfig(Map> properties, String profile) { @@ -119,12 +110,8 @@ public final class ShowConfig extends AbstractCommand implements Runnable { PropertyMapper mapper = PropertyMappers.getMapper(property); - if (mapper == null) { - if (configValue.getSourceName().equals("SysPropConfigSource") && !allowedSystemPropertyKeys.contains(property)) { - return; // most system properties are internally used, and not relevant during show-config - } - } else if (mapper.isRunTime()) { - value = getRuntimeProperty(property).orElse(value); + if (mapper == null && configValue.getSourceName().equals("SysPropConfigSource") && !allowedSystemPropertyKeys.contains(property)) { + return; // most system properties are internally used, and not relevant during show-config } value = maskValue(configValue.getName(), value, configValue.getConfigSourceName()); 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 f30191ef64..baacd0dff2 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 @@ -17,9 +17,7 @@ package org.keycloak.quarkus.runtime.cli.command; -import static org.keycloak.quarkus.runtime.Environment.setProfile; import static org.keycloak.quarkus.runtime.cli.command.AbstractStartCommand.OPTIMIZED_BUILD_OPTION_LONG; -import static org.keycloak.quarkus.runtime.configuration.Configuration.getRawPersistedProperty; import org.keycloak.quarkus.runtime.Environment; import org.keycloak.quarkus.runtime.Messages; @@ -27,8 +25,6 @@ import org.keycloak.quarkus.runtime.Messages; import picocli.CommandLine; import picocli.CommandLine.Command; -import java.util.Optional; - @Command(name = Start.NAME, header = "Start the server.", description = { @@ -52,24 +48,12 @@ public final class Start extends AbstractStartCommand implements Runnable { @Override protected void doBeforeRun() { - devProfileNotAllowedError(); - } - - private void devProfileNotAllowedError() { - if (isDevProfileNotAllowed()) { + Environment.updateProfile(true); + if (Environment.isDevProfile()) { executionError(spec.commandLine(), Messages.devProfileNotAllowedError(NAME)); } } - public static boolean isDevProfileNotAllowed() { - Optional currentProfile = Optional.ofNullable(org.keycloak.common.util.Environment.getProfile()); - Optional persistedProfile = getRawPersistedProperty("kc.profile"); - - setProfile(currentProfile.orElse(persistedProfile.orElse("prod"))); - - return Environment.isDevProfile(); - } - @Override public boolean includeRuntime() { return true; diff --git a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/Configuration.java b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/Configuration.java index 49f85e6c6d..d196111ee5 100644 --- a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/Configuration.java +++ b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/Configuration.java @@ -17,7 +17,6 @@ package org.keycloak.quarkus.runtime.configuration; -import static org.keycloak.quarkus.runtime.Environment.getProfileOrDefault; import static org.keycloak.quarkus.runtime.cli.Picocli.ARG_PREFIX; import java.util.Map; @@ -28,7 +27,6 @@ import io.smallrye.config.ConfigValue; import io.smallrye.config.SmallRyeConfig; import org.eclipse.microprofile.config.spi.ConfigProviderResolver; -import org.eclipse.microprofile.config.spi.ConfigSource; import org.keycloak.config.Option; import org.keycloak.quarkus.runtime.configuration.mappers.PropertyMapper; import org.keycloak.quarkus.runtime.configuration.mappers.PropertyMappers; @@ -43,7 +41,7 @@ public final class Configuration { public static final char OPTION_PART_SEPARATOR_CHAR = '-'; public static final String OPTION_PART_SEPARATOR = String.valueOf(OPTION_PART_SEPARATOR_CHAR); - private static final String KC_OPTIMIZED = NS_KEYCLOAK_PREFIX + "optimized"; + public static final String KC_OPTIMIZED = NS_KEYCLOAK_PREFIX + "optimized"; private Configuration() { @@ -79,34 +77,9 @@ public final class Configuration { return (SmallRyeConfig) ConfigProviderResolver.instance().getConfig(); } - public static Optional getBuildTimeProperty(String name) { - Optional value = getRawPersistedProperty(name); - - if (value.isEmpty()) { - PropertyMapper mapper = PropertyMappers.getMapper(name); - - if (mapper != null) { - value = getRawPersistedProperty(mapper.getFrom()); - - if (value.isEmpty() && mapper.getTo() != null) { - value = getRawPersistedProperty(mapper.getTo()); - } - } - } - - if (value.isEmpty()) { - String profile = org.keycloak.common.util.Environment.getProfile(); - - if (profile == null) { - profile = getConfig().getRawValue(org.keycloak.common.util.Environment.PROFILE); - } - - value = getRawPersistedProperty("%" + profile + "." + name); - } - - return value; - } - + /** + * Raw persisted keycloak properties will match the resolved value of what was originally specified by the user + */ public static Optional getRawPersistedProperty(String name) { return Optional.ofNullable(PersistedConfigSource.getInstance().getValue(name)); } @@ -170,26 +143,6 @@ public final class Configuration { return mapper.getTo() == null ? mapper.getFrom() : mapper.getTo(); } - public static Optional getRuntimeProperty(String name) { - for (ConfigSource configSource : getConfig().getConfigSources()) { - if (PersistedConfigSource.NAME.equals(configSource.getName())) { - continue; - } - - String value = getValue(configSource, name); - - if (value == null) { - value = getValue(configSource, getMappedPropertyName(name)); - } - - if (value != null) { - return Optional.of(value); - } - } - - return Optional.empty(); - } - public static String toEnvVarFormat(String key) { return replaceNonAlphanumericByUnderscores(key).toUpperCase(); } @@ -233,16 +186,6 @@ public final class Configuration { return sb.toString(); } - private static String getValue(ConfigSource configSource, String name) { - String value = configSource.getValue("%".concat(getProfileOrDefault("prod").concat(".").concat(name))); - - if (value == null) { - value = configSource.getValue(name); - } - - return value; - } - public static boolean isOptimized() { return Configuration.getRawPersistedProperty(KC_OPTIMIZED).isPresent(); } @@ -251,7 +194,7 @@ public final class Configuration { properties.put(Configuration.KC_OPTIMIZED, Boolean.TRUE.toString()); } - public static ConfigValue getCurrentBuiltTimeProperty(String name) { + public static ConfigValue getNonPersistedConfigValue(String name) { return PersistedConfigSource.getInstance().runWithDisabled(() -> getConfigValue(name)); } } diff --git a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/KeycloakPropertiesConfigSource.java b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/KeycloakPropertiesConfigSource.java index e09069bf90..2c4e717376 100644 --- a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/KeycloakPropertiesConfigSource.java +++ b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/KeycloakPropertiesConfigSource.java @@ -40,7 +40,6 @@ import io.smallrye.config.AbstractLocationConfigSourceLoader; import io.smallrye.config.PropertiesConfigSource; import io.smallrye.config.common.utils.ConfigSourceUtil; -import static org.keycloak.common.util.StringPropertyReplacer.replaceProperties; import static org.keycloak.quarkus.runtime.configuration.Configuration.getMappedPropertyName; import static org.keycloak.quarkus.runtime.configuration.MicroProfileConfigProvider.NS_KEYCLOAK; import static org.keycloak.quarkus.runtime.configuration.MicroProfileConfigProvider.NS_KEYCLOAK_PREFIX; @@ -50,6 +49,8 @@ import static org.keycloak.quarkus.runtime.configuration.MicroProfileConfigProvi * A configuration source for {@code keycloak.conf}. */ public class KeycloakPropertiesConfigSource extends AbstractLocationConfigSourceLoader { + + public static final int PROPERTIES_FILE_ORDINAL = 475; private static final Pattern DOT_SPLIT = Pattern.compile("\\."); private static final String KEYCLOAK_CONFIG_FILE_ENV = "KC_CONFIG_FILE"; @@ -124,7 +125,7 @@ public class KeycloakPropertiesConfigSource extends AbstractLocationConfigSource } public List getConfigSources(final ClassLoader classLoader, Path configFile) { - return loadConfigSources(configFile.toUri().toString(), 450, classLoader); + return loadConfigSources(configFile.toUri().toString(), PROPERTIES_FILE_ORDINAL, classLoader); } @Override diff --git a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/QuarkusPropertiesConfigSource.java b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/QuarkusPropertiesConfigSource.java index d433739540..d6b56a61c2 100644 --- a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/QuarkusPropertiesConfigSource.java +++ b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/QuarkusPropertiesConfigSource.java @@ -17,8 +17,6 @@ package org.keycloak.quarkus.runtime.configuration; -import static java.lang.Boolean.parseBoolean; -import static org.keycloak.quarkus.runtime.configuration.Configuration.getRawPersistedProperty; import static org.keycloak.quarkus.runtime.configuration.MicroProfileConfigProvider.NS_QUARKUS; import java.io.File; @@ -36,7 +34,6 @@ import org.eclipse.microprofile.config.spi.ConfigSourceProvider; import org.keycloak.quarkus.runtime.Environment; import io.smallrye.config.AbstractLocationConfigSourceLoader; -import io.smallrye.config.ConfigValue; import io.smallrye.config.PropertiesConfigSource; import io.smallrye.config.common.utils.ConfigSourceUtil; @@ -46,23 +43,8 @@ import io.smallrye.config.common.utils.ConfigSourceUtil; public final class QuarkusPropertiesConfigSource extends AbstractLocationConfigSourceLoader implements ConfigSourceProvider { private static final String FILE_NAME = "quarkus.properties"; - public static final String QUARKUS_PROPERTY_ENABLED = "kc.quarkus-properties-enabled"; public static final String NAME = "QuarkusProperties"; - //for auto-build working with multiple datasources - public static final String QUARKUS_DATASOURCE_BUILDTIME_REGEX = "^quarkus\\.datasource\\.[A-Za-z0-9\\-_]+\\.(db-kind|jdbc\\.driver|jdbc\\.transactions|jdbc\\.enable-metrics)$"; - - public static boolean isSameSource(ConfigValue value) { - if (value == null) { - return false; - } - - // workaround for https://github.com/smallrye/smallrye-config/issues/1207 - // replace by the following line when fixed: - // return NAME.equals(value.getConfigSourceName()); - return value.getConfigSourceName() != null && value.getConfigSourceName().endsWith(FILE_NAME); - } - public static Path getConfigurationFile() { String homeDir = Environment.getHomeDir(); @@ -77,6 +59,8 @@ public final class QuarkusPropertiesConfigSource extends AbstractLocationConfigS return null; } + private boolean loadingFile; + @Override protected String[] getFileExtensions() { return new String[] { "properties" }; @@ -84,10 +68,11 @@ public final class QuarkusPropertiesConfigSource extends AbstractLocationConfigS @Override protected ConfigSource loadConfigSource(URL url, int ordinal) throws IOException { - return new PropertiesConfigSource(ConfigSourceUtil.urlToMap(url), FILE_NAME, ordinal) { + String name = loadingFile ? NAME : (NAME + " " + url); + return new PropertiesConfigSource(ConfigSourceUtil.urlToMap(url), name, ordinal) { @Override public String getName() { - return NAME; + return name; } @Override @@ -102,7 +87,7 @@ public final class QuarkusPropertiesConfigSource extends AbstractLocationConfigS } @Override - public List getConfigSources(final ClassLoader classLoader) { + public synchronized List getConfigSources(final ClassLoader classLoader) { List configSources = new ArrayList<>(); configSources.addAll(loadConfigSources("META-INF/services/" + FILE_NAME, 450, classLoader)); @@ -110,7 +95,12 @@ public final class QuarkusPropertiesConfigSource extends AbstractLocationConfigS Path configFile = getConfigurationFile(); if (configFile != null) { - configSources.addAll(loadConfigSources(configFile.toUri().toString(), 500, classLoader)); + loadingFile = true; + try { + configSources.addAll(loadConfigSources(configFile.toUri().toString(), KeycloakPropertiesConfigSource.PROPERTIES_FILE_ORDINAL, classLoader)); + } finally { + loadingFile = false; + } } return configSources; diff --git a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/mappers/PropertyMapper.java b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/mappers/PropertyMapper.java index 7df87133a5..f412a01822 100644 --- a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/mappers/PropertyMapper.java +++ b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/mappers/PropertyMapper.java @@ -34,6 +34,7 @@ import java.util.stream.Stream; import io.smallrye.config.ConfigSourceInterceptorContext; import io.smallrye.config.ConfigValue; +import io.smallrye.config.ConfigValue.ConfigValueBuilder; import org.keycloak.config.DeprecatedMetadata; import org.keycloak.config.Option; @@ -131,9 +132,12 @@ public class PropertyMapper { } if (config != null && config.getValue() != null) { - config = transformValue(name, config.getValue(), context, config.getConfigSourceName(), parentValue); + config = transformValue(name, config, context, parentValue); } else { - config = transformValue(name, this.option.getDefaultValue().map(Option::getDefaultValueString).orElse(null), context, null, false); + String defaultValue = this.option.getDefaultValue().map(Option::getDefaultValueString).orElse(null); + config = transformValue(name, new ConfigValueBuilder().withName(name) + .withValue(defaultValue).withRawValue(defaultValue).build(), + context, false); } if (config != null) { @@ -232,24 +236,27 @@ public class PropertyMapper { return option.getDeprecatedMetadata(); } - private ConfigValue transformValue(String name, String value, ConfigSourceInterceptorContext context, String configSourceName, boolean parentValue) { + private ConfigValue transformValue(String name, ConfigValue configValue, ConfigSourceInterceptorContext context, boolean parentValue) { + String value = configValue.getValue(); String mappedValue = value; + boolean mapped = false; var theMapper = parentValue ? this.parentMapper : this.mapper; if (theMapper != null && (!name.equals(getFrom()) || parentValue)) { mappedValue = theMapper.apply(value, context); + mapped = true; } if (value == null && mappedValue == null) { return null; } + + if (!mapped && name.equals(configValue.getName())) { + return configValue; + } - return ConfigValue.builder() - .withName(name) - .withValue(mappedValue) - .withRawValue(value) - .withConfigSourceName(configSourceName) - .build(); + // by unsetting the ordinal this will not be seen as directly modified by the user + return configValue.from().withValue(mappedValue).withRawValue(value).withConfigSourceOrdinal(0).build(); } private ConfigValue convertValue(ConfigValue configValue) { diff --git a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/mappers/PropertyMappers.java b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/mappers/PropertyMappers.java index 0a28d55ab2..b3baaaaa14 100644 --- a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/mappers/PropertyMappers.java +++ b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/mappers/PropertyMappers.java @@ -13,7 +13,6 @@ import org.keycloak.quarkus.runtime.cli.PropertyException; import org.keycloak.quarkus.runtime.cli.command.AbstractCommand; import org.keycloak.quarkus.runtime.cli.command.Build; import org.keycloak.quarkus.runtime.cli.command.ShowConfig; -import org.keycloak.quarkus.runtime.configuration.ConfigArgsConfigSource; import org.keycloak.quarkus.runtime.configuration.DisabledMappersInterceptor; import org.keycloak.quarkus.runtime.configuration.PersistedConfigSource; @@ -78,32 +77,10 @@ public final class PropertyMappers { return getMapperOrDefault(name, PropertyMapper.IDENTITY).getConfigValue(name, context); } - public static boolean isBuildTimeProperty(String name) { - if (isFeaturesBuildTimeProperty(name) || isSpiBuildTimeProperty(name)) { - return true; - } - - final PropertyMapper mapper = getMapperOrDefault(name, null); - boolean isBuildTimeProperty = mapper == null ? false : mapper.isBuildTime(); - - return isBuildTimeProperty - && !"kc.version".equals(name) - && !"kc.home.dir".equals(name) - && !"kc.config.file".equals(name) - && !org.keycloak.common.util.Environment.PROFILE.equals(name) - && !"kc.show.config".equals(name) - && !"kc.show.config.runtime".equals(name) - && !"kc.config-file".equals(name); - } - public static boolean isSpiBuildTimeProperty(String name) { return name.startsWith(KC_SPI_PREFIX) && (name.endsWith("-provider") || name.endsWith("-enabled") || name.endsWith("-provider-default")); } - private static boolean isFeaturesBuildTimeProperty(String name) { - return name.startsWith("kc.features"); - } - public static Map>> getRuntimeMappers() { return MAPPERS.getRuntimeMappers(); } 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 4b108014c3..7dcd867d04 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 @@ -21,13 +21,19 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.not; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNull; +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.cli.Picocli; import org.keycloak.quarkus.runtime.configuration.ConfigArgsConfigSource; import org.keycloak.quarkus.runtime.configuration.test.AbstractConfigurationTest; @@ -45,6 +51,8 @@ public class PicocliTest extends AbstractConfigurationTest { final StringWriter out = new StringWriter(); SmallRyeConfig config; int exitCode = Integer.MAX_VALUE; + boolean reaug; + private Properties buildProps; String getErrString() { return normalize(err); @@ -76,10 +84,6 @@ public class PicocliTest extends AbstractConfigurationTest { this.exitCode = exitCode; } - protected int runReAugmentationIfNeeded(List cliArgs, CommandLine cmd, CommandLine currentCommand) { - throw new AssertionError("Should not reaugment"); - }; - @Override public void parseAndRun(List cliArgs) { config = createConfig(); @@ -93,7 +97,8 @@ public class PicocliTest extends AbstractConfigurationTest { @Override public void build() throws Throwable { - // skip + reaug = true; + this.buildProps = getNonPersistedBuildTimeOptions(); } }; @@ -255,4 +260,112 @@ public class PicocliTest extends AbstractConfigurationTest { 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); + } + + @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"); + + Environment.setRebuildCheck(); // will be reset by the system properties logic + NonRunningPicocli nonRunningPicocli = pseudoLaunch("start-dev", "--hostname=name", "--http-enabled=true"); + assertEquals(CommandLine.ExitCode.OK, nonRunningPicocli.exitCode); + assertTrue(nonRunningPicocli.reaug); + assertEquals("dev", nonRunningPicocli.buildProps.getProperty(org.keycloak.common.util.Environment.PROFILE));; + } + + /** + * Runs a fake build to setup the state of the persisted build properties + */ + private void build(String... args) { + if (Stream.of(args).anyMatch("start-dev"::equals)) { + Environment.setRebuildCheck(); // auto-build + } + NonRunningPicocli nonRunningPicocli = pseudoLaunch(args); + assertTrue(nonRunningPicocli.reaug); + assertEquals(CommandLine.ExitCode.OK, nonRunningPicocli.exitCode); + 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"); + + Environment.setRebuildCheck(); // will be reset by the system properties logic + NonRunningPicocli nonRunningPicocli = pseudoLaunch("--profile=dev", "export", "--file=file"); + 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); + assertThat(nonRunningPicocli.getOutString(), containsString("features= > features=docker")); + assertTrue(nonRunningPicocli.reaug); + } + } 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 71851f323a..5c353159b2 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 @@ -22,6 +22,8 @@ import io.quarkus.runtime.configuration.ConfigUtils; import io.smallrye.config.ConfigValue; import io.smallrye.config.SmallRyeConfig; import io.smallrye.config.SmallRyeConfigProviderResolver; +import io.smallrye.config.ConfigValue.ConfigValueBuilder; + import org.eclipse.microprofile.config.ConfigProvider; import org.eclipse.microprofile.config.spi.ConfigProviderResolver; import org.junit.After; @@ -30,6 +32,7 @@ import org.keycloak.quarkus.runtime.configuration.ConfigArgsConfigSource; import org.keycloak.quarkus.runtime.configuration.Configuration; import org.keycloak.quarkus.runtime.configuration.KeycloakConfigSourceProvider; import org.keycloak.quarkus.runtime.configuration.MicroProfileConfigProvider; +import org.keycloak.quarkus.runtime.configuration.PersistedConfigSource; import org.keycloak.quarkus.runtime.configuration.mappers.PropertyMappers; import java.lang.reflect.Field; @@ -113,6 +116,7 @@ public abstract class AbstractConfigurationTest { SmallRyeConfigProviderResolver.class.cast(ConfigProviderResolver.instance()).releaseConfig(ConfigProvider.getConfig()); PropertyMappers.reset(); ConfigArgsConfigSource.setCliArgs(); + PersistedConfigSource.getInstance().getConfigValueProperties().clear(); } protected Config.Scope initConfig(String... scope) { @@ -153,4 +157,9 @@ 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/runtime/src/test/java/org/keycloak/quarkus/runtime/configuration/test/ConfigRegExPatternMatchingTest.java b/quarkus/runtime/src/test/java/org/keycloak/quarkus/runtime/configuration/test/ConfigRegExPatternMatchingTest.java deleted file mode 100644 index 0b8d287888..0000000000 --- a/quarkus/runtime/src/test/java/org/keycloak/quarkus/runtime/configuration/test/ConfigRegExPatternMatchingTest.java +++ /dev/null @@ -1,24 +0,0 @@ -package org.keycloak.quarkus.runtime.configuration.test; - - -import org.junit.Test; -import org.keycloak.quarkus.runtime.configuration.QuarkusPropertiesConfigSource; - -import java.util.regex.Pattern; - -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; - -public class ConfigRegExPatternMatchingTest { - - @Test - public void quarkusPropertyMultipleDatasourcePatternTest(){ - Pattern p = Pattern.compile(QuarkusPropertiesConfigSource.QUARKUS_DATASOURCE_BUILDTIME_REGEX); - assertTrue(p.matcher("quarkus.datasource.user.jdbc.transactions").matches()); - assertTrue(p.matcher("quarkus.datasource.user-store.jdbc.enable-metrics").matches()); - assertTrue(p.matcher("quarkus.datasource.user12_store.db-kind").matches()); - assertTrue(p.matcher("quarkus.datasource.user12_-__--store.db-kind").matches()); - assertFalse(p.matcher("quarkus.datasource.user-store.db-username").matches()); - assertFalse(p.matcher("quarkus.datasource.user-store.db-kin").matches()); - } -} diff --git a/quarkus/runtime/src/test/java/org/keycloak/quarkus/runtime/configuration/test/ConfigurationTest.java b/quarkus/runtime/src/test/java/org/keycloak/quarkus/runtime/configuration/test/ConfigurationTest.java index dcb98fd112..2034375210 100644 --- a/quarkus/runtime/src/test/java/org/keycloak/quarkus/runtime/configuration/test/ConfigurationTest.java +++ b/quarkus/runtime/src/test/java/org/keycloak/quarkus/runtime/configuration/test/ConfigurationTest.java @@ -22,7 +22,6 @@ import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.keycloak.quarkus.runtime.Environment.isWindows; -import java.nio.file.FileSystem; import java.nio.file.FileSystems; import java.nio.file.Path; import java.util.Arrays; diff --git a/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/QuarkusPropertiesAutoBuildDistTest.java b/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/QuarkusPropertiesAutoBuildDistTest.java index 3e5144cbf5..a401c4cb28 100644 --- a/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/QuarkusPropertiesAutoBuildDistTest.java +++ b/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/QuarkusPropertiesAutoBuildDistTest.java @@ -22,6 +22,8 @@ import static org.hamcrest.CoreMatchers.not; import static org.hamcrest.MatcherAssert.assertThat; import java.util.function.Consumer; + +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.MethodOrderer.OrderAnnotation; import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; @@ -52,6 +54,7 @@ public class QuarkusPropertiesAutoBuildDistTest { @BeforeStartDistribution(EnableAdditionalConsoleHandler.class) @Launch({ "start" }) @Order(2) + @Disabled(value = "We don't properly differentiate between quarkus runtime and build time properties") void testQuarkusRuntimePropDoesNotTriggerReAug(LaunchResult result) { CLIResult cliResult = (CLIResult) result; cliResult.assertNoBuild(); @@ -63,6 +66,7 @@ public class QuarkusPropertiesAutoBuildDistTest { @BeforeStartDistribution(DisableAdditionalConsoleHandler.class) @Launch({ "start" }) @Order(3) + @Disabled(value = "We don't properly differentiate between quarkus runtime and build time properties") void testNoReAugAfterChangingRuntimeProperty(LaunchResult result) { CLIResult cliResult = (CLIResult) result; cliResult.assertNoBuild(); @@ -82,6 +86,7 @@ public class QuarkusPropertiesAutoBuildDistTest { @BeforeStartDistribution(ChangeAdditionalDatasourceUsername.class) @Launch({ "start" }) @Order(5) + @Disabled(value = "We don't properly differentiate between quarkus runtime and build time properties") void testNoReAugForAdditionalDatasourceRuntimeProperty(LaunchResult result) { CLIResult cliResult = (CLIResult) result; cliResult.assertNoBuild(); 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 70a2fda9c1..313d00a569 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 @@ -100,6 +100,7 @@ public class QuarkusPropertiesDistTest { @BeforeStartDistribution(UpdateConsoleHandlerFromQuarkusProps.class) @Launch({"start", "--http-enabled=true", "--hostname-strict=false"}) @Order(6) + @Disabled(value = "We don't properly differentiate between quarkus runtime and build time properties") void testRuntimePropFromQuarkusPropsIsAppliedWithoutRebuild(LaunchResult result) { CLIResult cliResult = (CLIResult) result; assertThat(cliResult.getOutput(), not(containsString("Keycloak is the best"))); 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 7929d6b3ce..54176fbabd 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 @@ -28,7 +28,9 @@ import org.keycloak.it.junit5.extension.CLIResult; import org.keycloak.it.junit5.extension.DistributionTest; import org.keycloak.it.junit5.extension.RawDistOnly; import org.keycloak.it.utils.KeycloakDistribution; +import org.keycloak.it.utils.RawKeycloakDistribution; +import java.io.File; import java.nio.file.Paths; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -86,5 +88,18 @@ public class StartDevCommandDistTest { assertTrue(cliResult.getOutput().contains("Listening on:")); cliResult.assertStartedDevMode(); } + + @Test + void testStartDevThenImportRebuild(KeycloakDistribution dist) throws Exception { + RawKeycloakDistribution rawDist = dist.unwrap(RawKeycloakDistribution.class); + CLIResult result = rawDist.run("start-dev"); + assertTrue(result.getErrorOutput().isEmpty(), result.getErrorOutput()); + + File target = new File("./target"); + + // feature change should trigger a build + result = rawDist.run("--profile=dev", "export", "--features=docker", "--dir=" + target.getAbsolutePath()); + result.assertMessage("Updating the configuration and installing your custom providers, if any. Please wait."); + } }