fix: consolidating logic dealing with persisted property handling (#34260)

closes: #34258

Signed-off-by: Steve Hawkins <shawkins@redhat.com>
This commit is contained in:
Steven Hawkins 2024-11-05 10:42:56 -05:00 committed by GitHub
parent bd1a5a1543
commit 927f110aef
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
21 changed files with 387 additions and 533 deletions

View file

@ -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.integration.HibernateOrmIntegrationRuntimeConfiguredBuildItem;
import io.quarkus.hibernate.orm.deployment.spi.AdditionalJpaModelBuildItem; import io.quarkus.hibernate.orm.deployment.spi.AdditionalJpaModelBuildItem;
import io.quarkus.resteasy.reactive.server.spi.MethodScannerBuildItem; import io.quarkus.resteasy.reactive.server.spi.MethodScannerBuildItem;
import io.quarkus.runtime.LaunchMode;
import io.quarkus.runtime.configuration.ConfigurationException; import io.quarkus.runtime.configuration.ConfigurationException;
import io.quarkus.vertx.http.deployment.HttpRootPathBuildItem; import io.quarkus.vertx.http.deployment.HttpRootPathBuildItem;
import io.quarkus.vertx.http.deployment.NonApplicationRootPathBuildItem; import io.quarkus.vertx.http.deployment.NonApplicationRootPathBuildItem;
import io.quarkus.vertx.http.deployment.RouteBuildItem; import io.quarkus.vertx.http.deployment.RouteBuildItem;
import io.smallrye.config.ConfigValue;
import org.eclipse.microprofile.health.Readiness; import org.eclipse.microprofile.health.Readiness;
import org.hibernate.cfg.AvailableSettings; import org.hibernate.cfg.AvailableSettings;
import org.hibernate.jpa.boot.spi.PersistenceUnitDescriptor; import org.hibernate.jpa.boot.spi.PersistenceUnitDescriptor;
@ -97,11 +96,11 @@ import org.keycloak.provider.ProviderManager;
import org.keycloak.provider.Spi; import org.keycloak.provider.Spi;
import org.keycloak.quarkus.runtime.Environment; import org.keycloak.quarkus.runtime.Environment;
import org.keycloak.quarkus.runtime.KeycloakRecorder; 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.Configuration;
import org.keycloak.quarkus.runtime.configuration.KeycloakConfigSourceProvider; import org.keycloak.quarkus.runtime.configuration.KeycloakConfigSourceProvider;
import org.keycloak.quarkus.runtime.configuration.MicroProfileConfigProvider; import org.keycloak.quarkus.runtime.configuration.MicroProfileConfigProvider;
import org.keycloak.quarkus.runtime.configuration.PersistedConfigSource; 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.PropertyMapper;
import org.keycloak.quarkus.runtime.configuration.mappers.PropertyMappers; import org.keycloak.quarkus.runtime.configuration.mappers.PropertyMappers;
import org.keycloak.quarkus.runtime.integration.resteasy.KeycloakHandlerChainCustomizer; import org.keycloak.quarkus.runtime.integration.resteasy.KeycloakHandlerChainCustomizer;
@ -130,7 +129,6 @@ import org.keycloak.vault.FilesPlainTextVaultProviderFactory;
import jakarta.persistence.Entity; import jakarta.persistence.Entity;
import jakarta.persistence.spi.PersistenceUnitTransactionType; import jakarta.persistence.spi.PersistenceUnitTransactionType;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.net.URL; 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.connections.jpa.util.JpaUtils.loadSpecificNamedQueries;
import static org.keycloak.quarkus.runtime.Environment.getCurrentOrCreateFeatureProfile; 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.Providers.getProviderManager;
import static org.keycloak.quarkus.runtime.configuration.Configuration.getOptionalKcValue; 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.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_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.quarkus.runtime.storage.database.jpa.QuarkusJpaConnectionProviderFactory.QUERY_PROPERTY_PREFIX;
import static org.keycloak.representations.provider.ScriptProviderDescriptor.AUTHENTICATORS; import static org.keycloak.representations.provider.ScriptProviderDescriptor.AUTHENTICATORS;
import static org.keycloak.representations.provider.ScriptProviderDescriptor.MAPPERS; import static org.keycloak.representations.provider.ScriptProviderDescriptor.MAPPERS;
@ -578,31 +572,7 @@ class KeycloakProcessor {
*/ */
@BuildStep(onlyIf = IsReAugmentation.class) @BuildStep(onlyIf = IsReAugmentation.class)
void persistBuildTimeProperties(BuildProducer<GeneratedResourceBuildItem> resources) { void persistBuildTimeProperties(BuildProducer<GeneratedResourceBuildItem> resources) {
Properties properties = new Properties(); Properties properties = Picocli.getNonPersistedBuildTimeOptions();
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));
try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) { try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
properties.store(outputStream, " Auto-generated, DO NOT change this file"); 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 * 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. * most of the JAX-RS resources, which are required to register Resteasy builtin providers.

View file

@ -17,8 +17,6 @@
package org.keycloak.quarkus.runtime; package org.keycloak.quarkus.runtime;
import static org.keycloak.quarkus.runtime.configuration.Configuration.getBuildTimeProperty;
import java.io.File; import java.io.File;
import java.io.FilenameFilter; import java.io.FilenameFilter;
import java.nio.file.Path; import java.nio.file.Path;
@ -36,6 +34,7 @@ import io.smallrye.config.SmallRyeConfig;
import org.apache.commons.lang3.SystemUtils; import org.apache.commons.lang3.SystemUtils;
import org.keycloak.common.Profile; import org.keycloak.common.Profile;
import org.keycloak.quarkus.runtime.cli.command.AbstractCommand; import org.keycloak.quarkus.runtime.cli.command.AbstractCommand;
import org.keycloak.quarkus.runtime.configuration.Configuration;
import org.keycloak.quarkus.runtime.configuration.PersistedConfigSource; import org.keycloak.quarkus.runtime.configuration.PersistedConfigSource;
public final class Environment { 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(); 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); 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) { if (profile == null) {
profile = defaultProfile; profile = Environment.PROD_PROFILE_VALUE;
} }
setProfile(profile);
return profile; return profile;
} }
@ -132,7 +128,7 @@ public final class Environment {
return true; 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(){ public static boolean isDevProfile(){
@ -221,6 +217,10 @@ public final class Environment {
return Boolean.getBoolean("kc.config.build-and-exit"); return Boolean.getBoolean("kc.config.build-and-exit");
} }
public static void setRebuildCheck() {
System.setProperty("kc.config.build-and-exit", "true");
}
public static boolean isRebuilt() { public static boolean isRebuilt() {
return Boolean.getBoolean("kc.config.built"); return Boolean.getBoolean("kc.config.built");
} }

View file

@ -19,12 +19,9 @@ package org.keycloak.quarkus.runtime;
import static org.keycloak.quarkus.runtime.Environment.getKeycloakModeFromProfile; import static org.keycloak.quarkus.runtime.Environment.getKeycloakModeFromProfile;
import static org.keycloak.quarkus.runtime.Environment.isDevProfile; 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.isNonServerMode;
import static org.keycloak.quarkus.runtime.Environment.isTestLaunchMode; 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.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.io.PrintWriter;
import java.util.ArrayList; import java.util.ArrayList;
@ -83,12 +80,8 @@ public class KeycloakMain implements QuarkusApplication {
cliArgs.add("-h"); cliArgs.add("-h");
} else if (isFastStart(cliArgs)) { // fast path for starting the server without bootstrapping CLI } else if (isFastStart(cliArgs)) { // fast path for starting the server without bootstrapping CLI
if (!wasBuildEverRun()) { Environment.updateProfile(true);
handleUsageError(Messages.optimizedUsedForFirstStartup()); if (Environment.isDevProfile()) {
return;
}
if (isDevProfileNotAllowed()) {
handleUsageError(Messages.devProfileNotAllowedError(Start.NAME)); handleUsageError(Messages.devProfileNotAllowedError(Start.NAME));
return; return;
} }
@ -156,7 +149,7 @@ public class KeycloakMain implements QuarkusApplication {
Quarkus.run(KeycloakMain.class, (exitCode, cause) -> { Quarkus.run(KeycloakMain.class, (exitCode, cause) -> {
if (cause != null) { if (cause != null) {
errorHandler.error(errStream, 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()); cause.getCause());
} }
@ -168,7 +161,7 @@ public class KeycloakMain implements QuarkusApplication {
}, args); }, args);
} catch (Throwable cause) { } catch (Throwable cause) {
errorHandler.error(errStream, 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()); cause.getCause());
System.exit(1); System.exit(1);
} }

View file

@ -18,8 +18,8 @@
package org.keycloak.quarkus.runtime.cli; package org.keycloak.quarkus.runtime.cli;
import static java.lang.String.format; import static java.lang.String.format;
import static java.util.Optional.ofNullable; import static org.keycloak.quarkus.runtime.Environment.getProviderFiles;
import static java.util.stream.StreamSupport.stream; import static org.keycloak.quarkus.runtime.Environment.isDevMode;
import static org.keycloak.quarkus.runtime.Environment.isRebuild; import static org.keycloak.quarkus.runtime.Environment.isRebuild;
import static org.keycloak.quarkus.runtime.Environment.isRebuildCheck; import static org.keycloak.quarkus.runtime.Environment.isRebuildCheck;
import static org.keycloak.quarkus.runtime.Environment.isRebuilt; 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.cli.command.AbstractStartCommand.OPTIMIZED_BUILD_OPTION_LONG;
import static org.keycloak.quarkus.runtime.configuration.ConfigArgsConfigSource.parseConfigArgs; 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.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.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.MicroProfileConfigProvider.NS_KEYCLOAK_PREFIX;
import static org.keycloak.quarkus.runtime.configuration.mappers.PropertyMappers.maskValue; 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 static picocli.CommandLine.Model.UsageMessageSpec.SECTION_KEY_COMMAND_LIST;
import java.io.File; import java.io.File;
@ -44,62 +37,59 @@ import java.io.PrintWriter;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.EnumMap; import java.util.EnumMap;
import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects;
import java.util.Optional; import java.util.Optional;
import java.util.Properties;
import java.util.Set; import java.util.Set;
import java.util.function.BiConsumer; import java.util.function.BiConsumer;
import java.util.function.Consumer; 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.common.profile.ProfileException;
import org.keycloak.config.DeprecatedMetadata; import org.keycloak.config.DeprecatedMetadata;
import org.keycloak.config.Option; import org.keycloak.config.Option;
import org.keycloak.config.OptionCategory; 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.AbstractCommand;
import org.keycloak.quarkus.runtime.cli.command.BootstrapAdmin;
import org.keycloak.quarkus.runtime.cli.command.Build; 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.Main;
import org.keycloak.quarkus.runtime.cli.command.ShowConfig; 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.StartDev;
import org.keycloak.quarkus.runtime.cli.command.Tools;
import org.keycloak.quarkus.runtime.configuration.ConfigArgsConfigSource; import org.keycloak.quarkus.runtime.configuration.ConfigArgsConfigSource;
import org.keycloak.quarkus.runtime.configuration.Configuration; import org.keycloak.quarkus.runtime.configuration.Configuration;
import org.keycloak.quarkus.runtime.configuration.DisabledMappersInterceptor; import org.keycloak.quarkus.runtime.configuration.DisabledMappersInterceptor;
import org.keycloak.quarkus.runtime.configuration.KcUnmatchedArgumentException; 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.PropertyMappingInterceptor;
import org.keycloak.quarkus.runtime.configuration.QuarkusPropertiesConfigSource; 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.configuration.mappers.PropertyMapper;
import org.keycloak.quarkus.runtime.Environment; import org.keycloak.quarkus.runtime.configuration.mappers.PropertyMappers;
import org.keycloak.quarkus.runtime.KeycloakMain;
import io.quarkus.bootstrap.runner.QuarkusEntryPoint; import io.quarkus.bootstrap.runner.QuarkusEntryPoint;
import io.quarkus.runtime.LaunchMode;
import io.smallrye.config.ConfigValue; import io.smallrye.config.ConfigValue;
import picocli.CommandLine; import picocli.CommandLine;
import picocli.CommandLine.ParameterException;
import picocli.CommandLine.ParseResult;
import picocli.CommandLine.DuplicateOptionAnnotationsException; import picocli.CommandLine.DuplicateOptionAnnotationsException;
import picocli.CommandLine.Help.Ansi; import picocli.CommandLine.Help.Ansi;
import picocli.CommandLine.Help.Ansi.Style; import picocli.CommandLine.Help.Ansi.Style;
import picocli.CommandLine.Help.ColorScheme; import picocli.CommandLine.Help.ColorScheme;
import picocli.CommandLine.IFactory; import picocli.CommandLine.IFactory;
import picocli.CommandLine.Model.ArgGroupSpec;
import picocli.CommandLine.Model.CommandSpec; import picocli.CommandLine.Model.CommandSpec;
import picocli.CommandLine.Model.ISetter; import picocli.CommandLine.Model.ISetter;
import picocli.CommandLine.Model.OptionSpec; import picocli.CommandLine.Model.OptionSpec;
import picocli.CommandLine.Model.ArgGroupSpec; import picocli.CommandLine.ParameterException;
import picocli.CommandLine.ParseResult;
public class Picocli { 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_PREFIX = "--";
public static final String ARG_SHORT_PREFIX = "-"; public static final String ARG_SHORT_PREFIX = "-";
public static final String NO_PARAM_LABEL = "none"; public static final String NO_PARAM_LABEL = "none";
@ -172,7 +162,13 @@ public class Picocli {
} }
if (currentSpec != null) { 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()) { if (isRebuildCheck()) {
@ -207,7 +203,7 @@ public class Picocli {
} }
} }
protected int runReAugmentationIfNeeded(List<String> cliArgs, CommandLine cmd, CommandLine currentCommand) { private int runReAugmentationIfNeeded(List<String> cliArgs, CommandLine cmd, CommandLine currentCommand) {
int exitCode = 0; int exitCode = 0;
if (currentCommand == null) { if (currentCommand == null) {
@ -220,14 +216,10 @@ public class Picocli {
return exitCode; return exitCode;
} }
if (currentCommandName.equals(StartDev.NAME)) { // TODO: ensure that the config has not yet been initialized
String profile = org.keycloak.common.util.Environment.getProfile(); // - there's currently no good way to do that directly on ConfigProviderResolver
initProfile(cliArgs, currentCommandName);
if (profile == null) {
// force the server image to be set with the dev profile
Environment.forceDevProfile();
}
}
if (requiresReAugmentation(currentCommand)) { if (requiresReAugmentation(currentCommand)) {
PropertyMappers.sanitizeDisabledMappers(); PropertyMappers.sanitizeDisabledMappers();
exitCode = runReAugmentation(cliArgs, cmd); exitCode = runReAugmentation(cliArgs, cmd);
@ -236,34 +228,42 @@ public class Picocli {
return exitCode; return exitCode;
} }
protected void initProfile(List<String> 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<String> cliArgs, String currentCommandName) { private static boolean shouldSkipRebuild(List<String> cliArgs, String currentCommandName) {
return cliArgs.contains("--help") return cliArgs.contains("--help")
|| cliArgs.contains("-h") || cliArgs.contains("-h")
|| cliArgs.contains("--help-all") || cliArgs.contains("--help-all")
|| currentCommandName.equals(Build.NAME) || currentCommandName.equals(Build.NAME)
|| currentCommandName.equals(ShowConfig.NAME) || currentCommandName.equals(ShowConfig.NAME)
|| currentCommandName.equals(BootstrapAdmin.NAME) || currentCommandName.equals(Completion.NAME);
|| currentCommandName.equals(Tools.NAME);
} }
private static boolean requiresReAugmentation(CommandLine cmdCommand) { private static boolean requiresReAugmentation(CommandLine cmdCommand) {
if (ConfigArgsConfigSource.getAllCliArgs().contains(Start.NAME) Map<String, String> rawPersistedProperties = Configuration.getRawPersistedProperties();
// run time dev mode is not set if (rawPersistedProperties.isEmpty()) {
&& !org.keycloak.common.util.Environment.isDevMode() return true; // no build yet
// 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;
} }
var current = getNonPersistedBuildTimeOptions();
if (hasConfigChanges(cmdCommand)) { // everything but the optimized value must match
if (!ConfigArgsConfigSource.getAllCliArgs().contains(StartDev.NAME) && "dev".equals(getConfig().getOptionalValue("kc.profile", String.class).orElse(null))) { String key = Configuration.KC_OPTIMIZED;
return false; Optional.ofNullable(rawPersistedProperties.get(key)).ifPresentOrElse(value -> current.put(key, value), () -> current.remove(key));
} return !rawPersistedProperties.equals(current);
return true;
}
return hasProviderChanges();
} }
/** /**
@ -294,7 +294,9 @@ public class Picocli {
private static int runReAugmentation(List<String> cliArgs, CommandLine cmd) { private static int runReAugmentation(List<String> cliArgs, CommandLine cmd) {
if(!isDevMode() && cmd != null) { if(!isDevMode() && cmd != null) {
cmd.getOut().println("Changes detected in configuration. Updating the server image."); cmd.getOut().println("Changes detected in configuration. Updating the server image.");
checkChangesInBuildOptionsDuringAutoBuild(); if (Configuration.isOptimized()) {
checkChangesInBuildOptionsDuringAutoBuild(cmd.getOut());
}
} }
List<String> configArgsList = new ArrayList<>(); List<String> configArgsList = new ArrayList<>();
@ -316,36 +318,8 @@ public class Picocli {
return exitCode; return exitCode;
} }
private static boolean hasProviderChanges() { private static boolean wasBuildEverRun() {
Map<String, String> persistedProps = PersistedConfigSource.getInstance().getProperties(); return !Configuration.getRawPersistedProperties().isEmpty();
Map<String, File> deployedProviders = Environment.getProviderFiles();
if (persistedProps.isEmpty()) {
return !deployedProviders.isEmpty();
}
Set<String> 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;
} }
/** /**
@ -356,6 +330,10 @@ public class Picocli {
* @param outWriter * @param outWriter
*/ */
public static void validateConfig(List<String> cliArgs, AbstractCommand abstractCommand, PrintWriter outWriter) { public static void validateConfig(List<String> 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()); IncludeOptions options = getIncludeOptions(cliArgs, abstractCommand, abstractCommand.getName());
if (!options.includeBuildTime && !options.includeRuntime) { if (!options.includeBuildTime && !options.includeRuntime) {
@ -391,8 +369,7 @@ public class Picocli {
ConfigValue configValue = Configuration.getConfigValue(mapper.getFrom()); ConfigValue configValue = Configuration.getConfigValue(mapper.getFrom());
String configValueStr = configValue.getValue(); String configValueStr = configValue.getValue();
// don't consider missing or anything below standard env properties if (configValueStr == null || !isUserModifiable(configValue)) {
if (configValueStr == null || configValue.getConfigSourceOrdinal() < 300) {
continue; 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<String> ignoredBuildTime, private static void checkSpiOptions(IncludeOptions options, final List<String> ignoredBuildTime,
final List<String> ignoredRunTime) { final List<String> ignoredRunTime) {
String kcSpiPrefix = NS_KEYCLOAK_PREFIX + "spi";
for (String key : Configuration.getConfig().getPropertyNames()) { for (String key : Configuration.getConfig().getPropertyNames()) {
if (!key.startsWith(kcSpiPrefix)) { if (!key.startsWith(PropertyMappers.KC_SPI_PREFIX)) {
continue; continue;
} }
boolean buildTimeOption = key.endsWith("-provider") || key.endsWith("-provider-default") || key.endsWith("-enabled"); boolean buildTimeOption = PropertyMappers.isSpiBuildTimeProperty(key);
ConfigValue configValue = Configuration.getConfigValue(key); ConfigValue configValue = Configuration.getConfigValue(key);
String configValueStr = configValue.getValue(); String configValueStr = configValue.getValue();
@ -558,124 +540,59 @@ public class Picocli {
String.join("\n", properties)), outWriter); String.join("\n", properties)), outWriter);
} }
private static boolean hasConfigChanges(CommandLine cmdCommand) { public static Properties getNonPersistedBuildTimeOptions() {
Optional<String> currentProfile = ofNullable(org.keycloak.common.util.Environment.getProfile()); Properties properties = new Properties();
Optional<String> persistedProfile = getBuildTimeProperty("kc.profile"); // TODO: could get only non-persistent property names
Configuration.getPropertyNames().forEach(name -> {
if (!persistedProfile.orElse("").equals(currentProfile.orElse(""))) { boolean quarkus = false;
return true; PropertyMapper<?> mapper = PropertyMappers.getMapper(name);
}
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 (mapper != null) {
if (!abstractCommand.getOptionCategories().contains(mapper.getCategory())) { if (!mapper.isBuildTime()) {
continue; return;
} }
name = mapper.getFrom();
if (properties.containsKey(name)) {
return;
} }
} 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;
}
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()));
} }
if (runtimeValue == null && isNotBlank(persistedValue)) { if (!Environment.isRebuildCheck()) {
PropertyMapper<?> mapper = PropertyMappers.getMapper(propertyName); // not auto-build (e.g.: start without optimized option) but a regular build to create an optimized server image
Configuration.markAsOptimized(properties);
if (mapper != null && persistedValue.equals(Option.getDefaultValueString(mapper.getDefaultValue().orElse(null)))) {
// same as default
continue;
} }
// probably because it was unset String profile = org.keycloak.common.util.Environment.getProfile();
return true; properties.put(org.keycloak.common.util.Environment.PROFILE, profile);
} properties.put(LaunchMode.current().getProfileKey(), profile);
// changes to a single property is enough to indicate changes to configuration return properties;
if (!persistedValue.equals(runtimeValue)) {
return true;
}
}
//check for defined quarkus raw build properties for UserStorageProvider extensions
if (QuarkusPropertiesConfigSource.getConfigurationFile() != null) {
Optional<ConfigSource> quarkusPropertiesConfigSource = getConfig().getConfigSource(QuarkusPropertiesConfigSource.NAME);
if (quarkusPropertiesConfigSource.isPresent()) {
Map<String, String> foundQuarkusBuildProperties = findSupportedRawQuarkusBuildProperties(quarkusPropertiesConfigSource.get().getProperties().entrySet());
//only check if buildProps are found in quarkus properties file.
if (!foundQuarkusBuildProperties.isEmpty()) {
Optional<ConfigSource> 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());
}
}
}
}
return false;
}
private static boolean hasAtLeastOneChangedBuildProperty(Map<String, String> foundQuarkusBuildProperties, Set<Map.Entry<String, String>> persistedEntries) {
for(Map.Entry<String, String> 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<String, String> findSupportedRawQuarkusBuildProperties(Set<Map.Entry<String, String>> entries) {
Pattern buildTimePattern = Pattern.compile(QuarkusPropertiesConfigSource.QUARKUS_DATASOURCE_BUILDTIME_REGEX);
Map<String, String> result = new HashMap<>();
for(Map.Entry<String, String> entry : entries) {
if (buildTimePattern.matcher(entry.getKey()).matches()) {
result.put(entry.getKey(), entry.getValue());
}
}
return result;
}
private static boolean isChangedValue(Map<String, String> foundQuarkusBuildProps, Map.Entry<String, String> persistedEntry) {
return !foundQuarkusBuildProps.get(persistedEntry.getKey()).equals(persistedEntry.getValue());
}
private static boolean isProviderKey(String key) {
return key.startsWith("kc.provider.file");
} }
public CommandLine createCommandLine(Consumer<CommandSpec> consumer) { public CommandLine createCommandLine(Consumer<CommandSpec> consumer) {
@ -740,12 +657,9 @@ public class Picocli {
} }
private static void addCommandOptions(List<String> cliArgs, CommandLine command) { private static void addCommandOptions(List<String> 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()); IncludeOptions options = getIncludeOptions(cliArgs, command.getCommand(), command.getCommandName());
// set current parsed command
Environment.setParsedCommand(ac);
if (!options.includeBuildTime && !options.includeRuntime) { if (!options.includeBuildTime && !options.includeRuntime) {
return; return;
} }
@ -938,39 +852,29 @@ public class Picocli {
return args; return args;
} }
private static void checkChangesInBuildOptionsDuringAutoBuild() { private static void checkChangesInBuildOptionsDuringAutoBuild(PrintWriter out) {
if (Configuration.isOptimized()) {
List<PropertyMapper<?>> buildOptions = stream(Configuration.getPropertyNames(true).spliterator(), false)
.sorted()
.map(PropertyMappers::getMapper)
.filter(Objects::nonNull).collect(Collectors.toList());
if (buildOptions.isEmpty()) {
return;
}
StringBuilder options = new StringBuilder(); StringBuilder options = new StringBuilder();
for (PropertyMapper<?> mapper : buildOptions) { var current = getNonPersistedBuildTimeOptions();
String newValue = ofNullable(getCurrentBuiltTimeProperty(mapper.getFrom())) var persisted = Configuration.getRawPersistedProperties();
.map(ConfigValue::getValue)
.orElse("<unset>");
String currentValue = getRawPersistedProperty(mapper.getFrom()).get();
if (newValue.equals(currentValue)) { // TODO: order is not well defined here
continue;
current.forEach((key, value) -> {
String persistedValue = persisted.get(key);
if (!value.equals(persistedValue)) {
optionChanged(options, (String)key, persistedValue, (String)value);
} }
});
String name = mapper.getOption().getKey(); persisted.forEach((key, value) -> {
if (current.get(key) == null) {
options.append("\n\t- ") optionChanged(options, key, value, null);
.append(name).append("=").append(currentValue)
.append(" > ")
.append(name).append("=").append(newValue);
} }
});
if (options.length() > 0) { if (options.length() > 0) {
System.out.println( out.println(
Ansi.AUTO.string( Ansi.AUTO.string(
new StringBuilder("@|bold,red ") new StringBuilder("@|bold,red ")
.append("The previous optimized build will be overridden with the following build options:") .append("The previous optimized build will be overridden with the following build options:")
@ -981,6 +885,19 @@ public class Picocli {
); );
} }
} }
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("<unset>")).append(" > ")
.append(key).append("=")
.append(Optional.ofNullable(newValue).orElse("<unset>"));
}
} }
public void start(CommandLine cmd) { public void start(CommandLine cmd) {

View file

@ -19,10 +19,6 @@ package org.keycloak.quarkus.runtime.cli.command;
import org.keycloak.config.OptionCategory; import org.keycloak.config.OptionCategory;
import org.keycloak.quarkus.runtime.Environment; 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.HostnameV2PropertyMappers;
import org.keycloak.quarkus.runtime.configuration.mappers.HttpPropertyMappers; import org.keycloak.quarkus.runtime.configuration.mappers.HttpPropertyMappers;
@ -46,10 +42,6 @@ public abstract class AbstractStartCommand extends AbstractCommand implements Ru
HostnameV2PropertyMappers.validateConfig(); HostnameV2PropertyMappers.validateConfig();
validateConfig(); validateConfig();
if (ConfigArgsConfigSource.getAllCliArgs().contains(OPTIMIZED_BUILD_OPTION_LONG) && !wasBuildEverRun()) {
executionError(spec.commandLine(), Messages.optimizedUsedForFirstStartup());
}
picocli.start(cmd); picocli.start(cmd);
} }

View file

@ -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.getHomePath;
import static org.keycloak.quarkus.runtime.Environment.isDevProfile; import static org.keycloak.quarkus.runtime.Environment.isDevProfile;
import static org.keycloak.quarkus.runtime.cli.Picocli.println; import static org.keycloak.quarkus.runtime.cli.Picocli.println;
import static org.keycloak.quarkus.runtime.configuration.ConfigArgsConfigSource.getAllCliArgs;
import io.quarkus.runtime.LaunchMode; import io.quarkus.runtime.LaunchMode;
import org.keycloak.config.OptionCategory; import org.keycloak.config.OptionCategory;
import org.keycloak.quarkus.runtime.Environment; import org.keycloak.quarkus.runtime.Environment;
import org.keycloak.quarkus.runtime.Messages; import org.keycloak.quarkus.runtime.Messages;
import org.keycloak.quarkus.runtime.configuration.Configuration; import org.keycloak.quarkus.runtime.configuration.Configuration;
import io.quarkus.bootstrap.runner.QuarkusEntryPoint;
import io.quarkus.bootstrap.runner.RunnerClassLoader; import io.quarkus.bootstrap.runner.RunnerClassLoader;
import io.smallrye.config.ConfigValue; import io.smallrye.config.ConfigValue;
@ -68,7 +67,10 @@ public final class Build extends AbstractCommand implements Runnable {
@Override @Override
public void run() { 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"); System.setProperty("quarkus.launch.rebuild", "true");
validateConfig(); validateConfig();
@ -94,7 +96,7 @@ public final class Build extends AbstractCommand implements Runnable {
private static void configureBuildClassLoader() { private static void configureBuildClassLoader() {
// ignored artifacts must be set prior to starting re-augmentation // 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) .map(ConfigValue::getValue)
.ifPresent(s -> System.setProperty(QUARKUS_REMOVED_ARTIFACTS_PROPERTY, s)); .ifPresent(s -> System.setProperty(QUARKUS_REMOVED_ARTIFACTS_PROPERTY, s));
} }
@ -110,9 +112,14 @@ public final class Build extends AbstractCommand implements Runnable {
return super.getOptionCategories(); return super.getOptionCategories();
} }
private void exitWithErrorIfDevProfileIsSetAndNotStartDev() { private void exitWithErrorIfDevProfileIsSet() {
if (Environment.isDevProfile() && !getAllCliArgs().contains(StartDev.NAME)) { if (Environment.isDevProfile()) {
executionError(spec.commandLine(), Messages.devProfileNotAllowedError(NAME)); 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));
}
} }
} }

View file

@ -20,7 +20,7 @@ package org.keycloak.quarkus.runtime.cli.command;
import picocli.AutoComplete; import picocli.AutoComplete;
import picocli.CommandLine.Command; 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}.", header = "Generate bash/zsh completion script for ${ROOT-COMMAND-NAME:-the root command of this command}.",
description = { description = {
"", "",
@ -29,4 +29,6 @@ import picocli.CommandLine.Command;
"", "",
" source <(${PARENT-COMMAND-FULL-NAME:-$PARENTCOMMAND} ${COMMAND-NAME})"}) " source <(${PARENT-COMMAND-FULL-NAME:-$PARENTCOMMAND} ${COMMAND-NAME})"})
public class Completion extends AutoComplete.GenerateCompletion { public class Completion extends AutoComplete.GenerateCompletion {
public static final String NAME = "completion";
} }

View file

@ -17,17 +17,13 @@
package org.keycloak.quarkus.runtime.cli.command; 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.getConfigValue;
import static org.keycloak.quarkus.runtime.configuration.Configuration.getPropertyNames; 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 static org.keycloak.quarkus.runtime.configuration.mappers.PropertyMappers.maskValue;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Optional;
import java.util.Set; import java.util.Set;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.StreamSupport; import java.util.stream.StreamSupport;
@ -60,24 +56,19 @@ public final class ShowConfig extends AbstractCommand implements Runnable {
@Override @Override
public void run() { public void run() {
System.setProperty("kc.show.config", filter); String profile = Environment.updateProfile(true);
String configArgs = System.getProperty("kc.show.config");
String profile = Optional.ofNullable(getCurrentOrPersistedProfile()).orElse(Environment.PROD_PROFILE_VALUE);
setProfile(profile);
Map<String, Set<String>> properties = getPropertiesByGroup(); Map<String, Set<String>> properties = getPropertiesByGroup();
printRunTimeConfig(properties, profile); printRunTimeConfig(properties, profile);
if (configArgs.equalsIgnoreCase("all")) { if (filter.equalsIgnoreCase("all")) {
spec.commandLine().getOut().println("Quarkus Configuration:"); spec.commandLine().getOut().println("Quarkus Configuration:");
properties.get(MicroProfileConfigProvider.NS_QUARKUS).stream().sorted() properties.get(MicroProfileConfigProvider.NS_QUARKUS).stream().sorted()
.forEachOrdered(this::printProperty); .forEachOrdered(this::printProperty);
} }
if (!Boolean.getBoolean("kc.show.config.runtime")) {
Quarkus.asyncExit(0); Quarkus.asyncExit(0);
} }
}
private void printRunTimeConfig(Map<String, Set<String>> properties, String profile) { private void printRunTimeConfig(Map<String, Set<String>> properties, String profile) {
Set<String> uniqueNames = new HashSet<>(); Set<String> uniqueNames = new HashSet<>();
@ -119,13 +110,9 @@ public final class ShowConfig extends AbstractCommand implements Runnable {
PropertyMapper<?> mapper = PropertyMappers.getMapper(property); PropertyMapper<?> mapper = PropertyMappers.getMapper(property);
if (mapper == null) { if (mapper == null && configValue.getSourceName().equals("SysPropConfigSource") && !allowedSystemPropertyKeys.contains(property)) {
if (configValue.getSourceName().equals("SysPropConfigSource") && !allowedSystemPropertyKeys.contains(property)) {
return; // most system properties are internally used, and not relevant during show-config return; // most system properties are internally used, and not relevant during show-config
} }
} else if (mapper.isRunTime()) {
value = getRuntimeProperty(property).orElse(value);
}
value = maskValue(configValue.getName(), value, configValue.getConfigSourceName()); value = maskValue(configValue.getName(), value, configValue.getConfigSourceName());

View file

@ -17,9 +17,7 @@
package org.keycloak.quarkus.runtime.cli.command; 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.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.Environment;
import org.keycloak.quarkus.runtime.Messages; import org.keycloak.quarkus.runtime.Messages;
@ -27,8 +25,6 @@ import org.keycloak.quarkus.runtime.Messages;
import picocli.CommandLine; import picocli.CommandLine;
import picocli.CommandLine.Command; import picocli.CommandLine.Command;
import java.util.Optional;
@Command(name = Start.NAME, @Command(name = Start.NAME,
header = "Start the server.", header = "Start the server.",
description = { description = {
@ -52,24 +48,12 @@ public final class Start extends AbstractStartCommand implements Runnable {
@Override @Override
protected void doBeforeRun() { protected void doBeforeRun() {
devProfileNotAllowedError(); Environment.updateProfile(true);
} if (Environment.isDevProfile()) {
private void devProfileNotAllowedError() {
if (isDevProfileNotAllowed()) {
executionError(spec.commandLine(), Messages.devProfileNotAllowedError(NAME)); executionError(spec.commandLine(), Messages.devProfileNotAllowedError(NAME));
} }
} }
public static boolean isDevProfileNotAllowed() {
Optional<String> currentProfile = Optional.ofNullable(org.keycloak.common.util.Environment.getProfile());
Optional<String> persistedProfile = getRawPersistedProperty("kc.profile");
setProfile(currentProfile.orElse(persistedProfile.orElse("prod")));
return Environment.isDevProfile();
}
@Override @Override
public boolean includeRuntime() { public boolean includeRuntime() {
return true; return true;

View file

@ -17,7 +17,6 @@
package org.keycloak.quarkus.runtime.configuration; 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 static org.keycloak.quarkus.runtime.cli.Picocli.ARG_PREFIX;
import java.util.Map; import java.util.Map;
@ -28,7 +27,6 @@ import io.smallrye.config.ConfigValue;
import io.smallrye.config.SmallRyeConfig; import io.smallrye.config.SmallRyeConfig;
import org.eclipse.microprofile.config.spi.ConfigProviderResolver; import org.eclipse.microprofile.config.spi.ConfigProviderResolver;
import org.eclipse.microprofile.config.spi.ConfigSource;
import org.keycloak.config.Option; import org.keycloak.config.Option;
import org.keycloak.quarkus.runtime.configuration.mappers.PropertyMapper; import org.keycloak.quarkus.runtime.configuration.mappers.PropertyMapper;
import org.keycloak.quarkus.runtime.configuration.mappers.PropertyMappers; 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 char OPTION_PART_SEPARATOR_CHAR = '-';
public static final String OPTION_PART_SEPARATOR = String.valueOf(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() { private Configuration() {
@ -79,34 +77,9 @@ public final class Configuration {
return (SmallRyeConfig) ConfigProviderResolver.instance().getConfig(); return (SmallRyeConfig) ConfigProviderResolver.instance().getConfig();
} }
public static Optional<String> getBuildTimeProperty(String name) { /**
Optional<String> value = getRawPersistedProperty(name); * Raw persisted keycloak properties will match the resolved value of what was originally specified by the user
*/
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;
}
public static Optional<String> getRawPersistedProperty(String name) { public static Optional<String> getRawPersistedProperty(String name) {
return Optional.ofNullable(PersistedConfigSource.getInstance().getValue(name)); return Optional.ofNullable(PersistedConfigSource.getInstance().getValue(name));
} }
@ -170,26 +143,6 @@ public final class Configuration {
return mapper.getTo() == null ? mapper.getFrom() : mapper.getTo(); return mapper.getTo() == null ? mapper.getFrom() : mapper.getTo();
} }
public static Optional<String> 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) { public static String toEnvVarFormat(String key) {
return replaceNonAlphanumericByUnderscores(key).toUpperCase(); return replaceNonAlphanumericByUnderscores(key).toUpperCase();
} }
@ -233,16 +186,6 @@ public final class Configuration {
return sb.toString(); 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() { public static boolean isOptimized() {
return Configuration.getRawPersistedProperty(KC_OPTIMIZED).isPresent(); return Configuration.getRawPersistedProperty(KC_OPTIMIZED).isPresent();
} }
@ -251,7 +194,7 @@ public final class Configuration {
properties.put(Configuration.KC_OPTIMIZED, Boolean.TRUE.toString()); 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)); return PersistedConfigSource.getInstance().runWithDisabled(() -> getConfigValue(name));
} }
} }

View file

@ -40,7 +40,6 @@ import io.smallrye.config.AbstractLocationConfigSourceLoader;
import io.smallrye.config.PropertiesConfigSource; import io.smallrye.config.PropertiesConfigSource;
import io.smallrye.config.common.utils.ConfigSourceUtil; 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.Configuration.getMappedPropertyName;
import static org.keycloak.quarkus.runtime.configuration.MicroProfileConfigProvider.NS_KEYCLOAK; import static org.keycloak.quarkus.runtime.configuration.MicroProfileConfigProvider.NS_KEYCLOAK;
import static org.keycloak.quarkus.runtime.configuration.MicroProfileConfigProvider.NS_KEYCLOAK_PREFIX; import static org.keycloak.quarkus.runtime.configuration.MicroProfileConfigProvider.NS_KEYCLOAK_PREFIX;
@ -51,6 +50,8 @@ import static org.keycloak.quarkus.runtime.configuration.MicroProfileConfigProvi
*/ */
public class KeycloakPropertiesConfigSource extends AbstractLocationConfigSourceLoader { public class KeycloakPropertiesConfigSource extends AbstractLocationConfigSourceLoader {
public static final int PROPERTIES_FILE_ORDINAL = 475;
private static final Pattern DOT_SPLIT = Pattern.compile("\\."); private static final Pattern DOT_SPLIT = Pattern.compile("\\.");
private static final String KEYCLOAK_CONFIG_FILE_ENV = "KC_CONFIG_FILE"; private static final String KEYCLOAK_CONFIG_FILE_ENV = "KC_CONFIG_FILE";
private static final String KEYCLOAK_CONF_FILE = "keycloak.conf"; private static final String KEYCLOAK_CONF_FILE = "keycloak.conf";
@ -124,7 +125,7 @@ public class KeycloakPropertiesConfigSource extends AbstractLocationConfigSource
} }
public List<ConfigSource> getConfigSources(final ClassLoader classLoader, Path configFile) { public List<ConfigSource> getConfigSources(final ClassLoader classLoader, Path configFile) {
return loadConfigSources(configFile.toUri().toString(), 450, classLoader); return loadConfigSources(configFile.toUri().toString(), PROPERTIES_FILE_ORDINAL, classLoader);
} }
@Override @Override

View file

@ -17,8 +17,6 @@
package org.keycloak.quarkus.runtime.configuration; 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 static org.keycloak.quarkus.runtime.configuration.MicroProfileConfigProvider.NS_QUARKUS;
import java.io.File; import java.io.File;
@ -36,7 +34,6 @@ import org.eclipse.microprofile.config.spi.ConfigSourceProvider;
import org.keycloak.quarkus.runtime.Environment; import org.keycloak.quarkus.runtime.Environment;
import io.smallrye.config.AbstractLocationConfigSourceLoader; import io.smallrye.config.AbstractLocationConfigSourceLoader;
import io.smallrye.config.ConfigValue;
import io.smallrye.config.PropertiesConfigSource; import io.smallrye.config.PropertiesConfigSource;
import io.smallrye.config.common.utils.ConfigSourceUtil; 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 { public final class QuarkusPropertiesConfigSource extends AbstractLocationConfigSourceLoader implements ConfigSourceProvider {
private static final String FILE_NAME = "quarkus.properties"; 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"; 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() { public static Path getConfigurationFile() {
String homeDir = Environment.getHomeDir(); String homeDir = Environment.getHomeDir();
@ -77,6 +59,8 @@ public final class QuarkusPropertiesConfigSource extends AbstractLocationConfigS
return null; return null;
} }
private boolean loadingFile;
@Override @Override
protected String[] getFileExtensions() { protected String[] getFileExtensions() {
return new String[] { "properties" }; return new String[] { "properties" };
@ -84,10 +68,11 @@ public final class QuarkusPropertiesConfigSource extends AbstractLocationConfigS
@Override @Override
protected ConfigSource loadConfigSource(URL url, int ordinal) throws IOException { 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 @Override
public String getName() { public String getName() {
return NAME; return name;
} }
@Override @Override
@ -102,7 +87,7 @@ public final class QuarkusPropertiesConfigSource extends AbstractLocationConfigS
} }
@Override @Override
public List<ConfigSource> getConfigSources(final ClassLoader classLoader) { public synchronized List<ConfigSource> getConfigSources(final ClassLoader classLoader) {
List<ConfigSource> configSources = new ArrayList<>(); List<ConfigSource> configSources = new ArrayList<>();
configSources.addAll(loadConfigSources("META-INF/services/" + FILE_NAME, 450, classLoader)); configSources.addAll(loadConfigSources("META-INF/services/" + FILE_NAME, 450, classLoader));
@ -110,7 +95,12 @@ public final class QuarkusPropertiesConfigSource extends AbstractLocationConfigS
Path configFile = getConfigurationFile(); Path configFile = getConfigurationFile();
if (configFile != null) { 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; return configSources;

View file

@ -34,6 +34,7 @@ import java.util.stream.Stream;
import io.smallrye.config.ConfigSourceInterceptorContext; import io.smallrye.config.ConfigSourceInterceptorContext;
import io.smallrye.config.ConfigValue; import io.smallrye.config.ConfigValue;
import io.smallrye.config.ConfigValue.ConfigValueBuilder;
import org.keycloak.config.DeprecatedMetadata; import org.keycloak.config.DeprecatedMetadata;
import org.keycloak.config.Option; import org.keycloak.config.Option;
@ -131,9 +132,12 @@ public class PropertyMapper<T> {
} }
if (config != null && config.getValue() != null) { if (config != null && config.getValue() != null) {
config = transformValue(name, config.getValue(), context, config.getConfigSourceName(), parentValue); config = transformValue(name, config, context, parentValue);
} else { } 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) { if (config != null) {
@ -232,24 +236,27 @@ public class PropertyMapper<T> {
return option.getDeprecatedMetadata(); 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; String mappedValue = value;
boolean mapped = false;
var theMapper = parentValue ? this.parentMapper : this.mapper; var theMapper = parentValue ? this.parentMapper : this.mapper;
if (theMapper != null && (!name.equals(getFrom()) || parentValue)) { if (theMapper != null && (!name.equals(getFrom()) || parentValue)) {
mappedValue = theMapper.apply(value, context); mappedValue = theMapper.apply(value, context);
mapped = true;
} }
if (value == null && mappedValue == null) { if (value == null && mappedValue == null) {
return null; return null;
} }
return ConfigValue.builder() if (!mapped && name.equals(configValue.getName())) {
.withName(name) return configValue;
.withValue(mappedValue) }
.withRawValue(value)
.withConfigSourceName(configSourceName) // by unsetting the ordinal this will not be seen as directly modified by the user
.build(); return configValue.from().withValue(mappedValue).withRawValue(value).withConfigSourceOrdinal(0).build();
} }
private ConfigValue convertValue(ConfigValue configValue) { private ConfigValue convertValue(ConfigValue configValue) {

View file

@ -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.AbstractCommand;
import org.keycloak.quarkus.runtime.cli.command.Build; import org.keycloak.quarkus.runtime.cli.command.Build;
import org.keycloak.quarkus.runtime.cli.command.ShowConfig; 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.DisabledMappersInterceptor;
import org.keycloak.quarkus.runtime.configuration.PersistedConfigSource; import org.keycloak.quarkus.runtime.configuration.PersistedConfigSource;
@ -78,32 +77,10 @@ public final class PropertyMappers {
return getMapperOrDefault(name, PropertyMapper.IDENTITY).getConfigValue(name, context); 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) { public static boolean isSpiBuildTimeProperty(String name) {
return name.startsWith(KC_SPI_PREFIX) && (name.endsWith("-provider") || name.endsWith("-enabled") || name.endsWith("-provider-default")); 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<OptionCategory, List<PropertyMapper<?>>> getRuntimeMappers() { public static Map<OptionCategory, List<PropertyMapper<?>>> getRuntimeMappers() {
return MAPPERS.getRuntimeMappers(); return MAPPERS.getRuntimeMappers();
} }

View file

@ -21,13 +21,19 @@ import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.not; import static org.hamcrest.Matchers.not;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull; import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import java.io.PrintWriter; import java.io.PrintWriter;
import java.io.StringWriter; import java.io.StringWriter;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.stream.Stream;
import org.junit.Test; import org.junit.Test;
import org.keycloak.quarkus.runtime.Environment;
import org.keycloak.quarkus.runtime.cli.Picocli; import org.keycloak.quarkus.runtime.cli.Picocli;
import org.keycloak.quarkus.runtime.configuration.ConfigArgsConfigSource; import org.keycloak.quarkus.runtime.configuration.ConfigArgsConfigSource;
import org.keycloak.quarkus.runtime.configuration.test.AbstractConfigurationTest; import org.keycloak.quarkus.runtime.configuration.test.AbstractConfigurationTest;
@ -45,6 +51,8 @@ public class PicocliTest extends AbstractConfigurationTest {
final StringWriter out = new StringWriter(); final StringWriter out = new StringWriter();
SmallRyeConfig config; SmallRyeConfig config;
int exitCode = Integer.MAX_VALUE; int exitCode = Integer.MAX_VALUE;
boolean reaug;
private Properties buildProps;
String getErrString() { String getErrString() {
return normalize(err); return normalize(err);
@ -76,10 +84,6 @@ public class PicocliTest extends AbstractConfigurationTest {
this.exitCode = exitCode; this.exitCode = exitCode;
} }
protected int runReAugmentationIfNeeded(List<String> cliArgs, CommandLine cmd, CommandLine currentCommand) {
throw new AssertionError("Should not reaugment");
};
@Override @Override
public void parseAndRun(List<String> cliArgs) { public void parseAndRun(List<String> cliArgs) {
config = createConfig(); config = createConfig();
@ -93,7 +97,8 @@ public class PicocliTest extends AbstractConfigurationTest {
@Override @Override
public void build() throws Throwable { 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")); 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=<unset> > features=docker"));
assertTrue(nonRunningPicocli.reaug);
}
} }

View file

@ -22,6 +22,8 @@ import io.quarkus.runtime.configuration.ConfigUtils;
import io.smallrye.config.ConfigValue; import io.smallrye.config.ConfigValue;
import io.smallrye.config.SmallRyeConfig; import io.smallrye.config.SmallRyeConfig;
import io.smallrye.config.SmallRyeConfigProviderResolver; import io.smallrye.config.SmallRyeConfigProviderResolver;
import io.smallrye.config.ConfigValue.ConfigValueBuilder;
import org.eclipse.microprofile.config.ConfigProvider; import org.eclipse.microprofile.config.ConfigProvider;
import org.eclipse.microprofile.config.spi.ConfigProviderResolver; import org.eclipse.microprofile.config.spi.ConfigProviderResolver;
import org.junit.After; 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.Configuration;
import org.keycloak.quarkus.runtime.configuration.KeycloakConfigSourceProvider; import org.keycloak.quarkus.runtime.configuration.KeycloakConfigSourceProvider;
import org.keycloak.quarkus.runtime.configuration.MicroProfileConfigProvider; import org.keycloak.quarkus.runtime.configuration.MicroProfileConfigProvider;
import org.keycloak.quarkus.runtime.configuration.PersistedConfigSource;
import org.keycloak.quarkus.runtime.configuration.mappers.PropertyMappers; import org.keycloak.quarkus.runtime.configuration.mappers.PropertyMappers;
import java.lang.reflect.Field; import java.lang.reflect.Field;
@ -113,6 +116,7 @@ public abstract class AbstractConfigurationTest {
SmallRyeConfigProviderResolver.class.cast(ConfigProviderResolver.instance()).releaseConfig(ConfigProvider.getConfig()); SmallRyeConfigProviderResolver.class.cast(ConfigProviderResolver.instance()).releaseConfig(ConfigProvider.getConfig());
PropertyMappers.reset(); PropertyMappers.reset();
ConfigArgsConfigSource.setCliArgs(); ConfigArgsConfigSource.setCliArgs();
PersistedConfigSource.getInstance().getConfigValueProperties().clear();
} }
protected Config.Scope initConfig(String... scope) { protected Config.Scope initConfig(String... scope) {
@ -153,4 +157,9 @@ public abstract class AbstractConfigurationTest {
protected void assertExternalConfig(Map<String, String> expectedValues) { protected void assertExternalConfig(Map<String, String> expectedValues) {
expectedValues.forEach(this::assertExternalConfig); expectedValues.forEach(this::assertExternalConfig);
} }
protected static void addPersistedConfigValues(Map<String, String> values) {
var configValueProps = PersistedConfigSource.getInstance().getConfigValueProperties();
values.forEach((k, v) -> configValueProps.put(k, new ConfigValueBuilder().withName(k).withValue(v).build()));
}
} }

View file

@ -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());
}
}

View file

@ -22,7 +22,6 @@ import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import static org.keycloak.quarkus.runtime.Environment.isWindows; import static org.keycloak.quarkus.runtime.Environment.isWindows;
import java.nio.file.FileSystem;
import java.nio.file.FileSystems; import java.nio.file.FileSystems;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.Arrays; import java.util.Arrays;

View file

@ -22,6 +22,8 @@ import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.MatcherAssert.assertThat;
import java.util.function.Consumer; import java.util.function.Consumer;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.MethodOrderer.OrderAnnotation; import org.junit.jupiter.api.MethodOrderer.OrderAnnotation;
import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
@ -52,6 +54,7 @@ public class QuarkusPropertiesAutoBuildDistTest {
@BeforeStartDistribution(EnableAdditionalConsoleHandler.class) @BeforeStartDistribution(EnableAdditionalConsoleHandler.class)
@Launch({ "start" }) @Launch({ "start" })
@Order(2) @Order(2)
@Disabled(value = "We don't properly differentiate between quarkus runtime and build time properties")
void testQuarkusRuntimePropDoesNotTriggerReAug(LaunchResult result) { void testQuarkusRuntimePropDoesNotTriggerReAug(LaunchResult result) {
CLIResult cliResult = (CLIResult) result; CLIResult cliResult = (CLIResult) result;
cliResult.assertNoBuild(); cliResult.assertNoBuild();
@ -63,6 +66,7 @@ public class QuarkusPropertiesAutoBuildDistTest {
@BeforeStartDistribution(DisableAdditionalConsoleHandler.class) @BeforeStartDistribution(DisableAdditionalConsoleHandler.class)
@Launch({ "start" }) @Launch({ "start" })
@Order(3) @Order(3)
@Disabled(value = "We don't properly differentiate between quarkus runtime and build time properties")
void testNoReAugAfterChangingRuntimeProperty(LaunchResult result) { void testNoReAugAfterChangingRuntimeProperty(LaunchResult result) {
CLIResult cliResult = (CLIResult) result; CLIResult cliResult = (CLIResult) result;
cliResult.assertNoBuild(); cliResult.assertNoBuild();
@ -82,6 +86,7 @@ public class QuarkusPropertiesAutoBuildDistTest {
@BeforeStartDistribution(ChangeAdditionalDatasourceUsername.class) @BeforeStartDistribution(ChangeAdditionalDatasourceUsername.class)
@Launch({ "start" }) @Launch({ "start" })
@Order(5) @Order(5)
@Disabled(value = "We don't properly differentiate between quarkus runtime and build time properties")
void testNoReAugForAdditionalDatasourceRuntimeProperty(LaunchResult result) { void testNoReAugForAdditionalDatasourceRuntimeProperty(LaunchResult result) {
CLIResult cliResult = (CLIResult) result; CLIResult cliResult = (CLIResult) result;
cliResult.assertNoBuild(); cliResult.assertNoBuild();

View file

@ -100,6 +100,7 @@ public class QuarkusPropertiesDistTest {
@BeforeStartDistribution(UpdateConsoleHandlerFromQuarkusProps.class) @BeforeStartDistribution(UpdateConsoleHandlerFromQuarkusProps.class)
@Launch({"start", "--http-enabled=true", "--hostname-strict=false"}) @Launch({"start", "--http-enabled=true", "--hostname-strict=false"})
@Order(6) @Order(6)
@Disabled(value = "We don't properly differentiate between quarkus runtime and build time properties")
void testRuntimePropFromQuarkusPropsIsAppliedWithoutRebuild(LaunchResult result) { void testRuntimePropFromQuarkusPropsIsAppliedWithoutRebuild(LaunchResult result) {
CLIResult cliResult = (CLIResult) result; CLIResult cliResult = (CLIResult) result;
assertThat(cliResult.getOutput(), not(containsString("Keycloak is the best"))); assertThat(cliResult.getOutput(), not(containsString("Keycloak is the best")));

View file

@ -28,7 +28,9 @@ import org.keycloak.it.junit5.extension.CLIResult;
import org.keycloak.it.junit5.extension.DistributionTest; import org.keycloak.it.junit5.extension.DistributionTest;
import org.keycloak.it.junit5.extension.RawDistOnly; import org.keycloak.it.junit5.extension.RawDistOnly;
import org.keycloak.it.utils.KeycloakDistribution; import org.keycloak.it.utils.KeycloakDistribution;
import org.keycloak.it.utils.RawKeycloakDistribution;
import java.io.File;
import java.nio.file.Paths; import java.nio.file.Paths;
import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.assertTrue;
@ -87,4 +89,17 @@ public class StartDevCommandDistTest {
cliResult.assertStartedDevMode(); 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.");
}
} }