task: refinements to propertymapping

closes: #32724

Signed-off-by: Steve Hawkins <shawkins@redhat.com>
This commit is contained in:
Steve Hawkins 2024-10-04 08:56:49 -04:00 committed by Pedro Igor
parent a276b3bb3d
commit 14e44f7d8c
14 changed files with 159 additions and 262 deletions

View file

@ -1,6 +1,5 @@
package org.keycloak.quarkus.runtime.configuration.mappers;
import static java.util.Optional.of;
import static org.keycloak.quarkus.runtime.configuration.Configuration.getOptionalKcValue;
import static org.keycloak.quarkus.runtime.configuration.mappers.PropertyMapper.fromOption;
@ -38,7 +37,14 @@ final class CachingPropertyMappers {
.paramLabel("stack")
.build(),
fromOption(CachingOptions.CACHE_CONFIG_FILE)
.mapFrom("cache")
.mapFrom(CachingOptions.CACHE, (value, context) -> {
if (CachingOptions.Mechanism.local.name().equals(value)) {
return "cache-local.xml";
} else if (CachingOptions.Mechanism.ispn.name().equals(value)) {
return "cache-ispn.xml";
} else
return null;
})
.to("kc.spi-connections-infinispan-quarkus-config-file")
.transformer(CachingPropertyMappers::resolveConfigFile)
.paramLabel("file")
@ -100,13 +106,7 @@ final class CachingPropertyMappers {
return getOptionalKcValue(CachingOptions.CACHE_REMOTE_HOST_PROPERTY).isPresent();
}
private static Optional<String> resolveConfigFile(Optional<String> value, ConfigSourceInterceptorContext context) {
if ("local".equals(value.get())) {
return of("cache-local.xml");
} else if ("ispn".equals(value.get())) {
return of("cache-ispn.xml");
}
private static String resolveConfigFile(String value, ConfigSourceInterceptorContext context) {
String pathPrefix;
String homeDir = Environment.getHomeDir();
@ -116,7 +116,7 @@ final class CachingPropertyMappers {
pathPrefix = homeDir + File.separator + "conf" + File.separator;
}
return of(pathPrefix + value.get());
return pathPrefix + value;
}
private static String getDefaultKeystorePathValue() {

View file

@ -5,8 +5,6 @@ import org.keycloak.config.ClassLoaderOptions;
import org.keycloak.quarkus.runtime.Environment;
import org.keycloak.quarkus.runtime.configuration.IgnoredArtifacts;
import java.util.Optional;
import static org.keycloak.config.ClassLoaderOptions.QUARKUS_REMOVED_ARTIFACTS_PROPERTY;
import static org.keycloak.quarkus.runtime.configuration.mappers.PropertyMapper.fromOption;
@ -23,9 +21,9 @@ final class ClassLoaderPropertyMappers {
};
}
private static Optional<String> resolveIgnoredArtifacts(Optional<String> value, ConfigSourceInterceptorContext context) {
private static String resolveIgnoredArtifacts(String value, ConfigSourceInterceptorContext context) {
if (Environment.isRebuildCheck() || Environment.isRebuild()) {
return Optional.of(String.join(",", IgnoredArtifacts.getDefaultIgnoredArtifacts()));
return String.join(",", IgnoredArtifacts.getDefaultIgnoredArtifacts());
}
return value;

View file

@ -6,7 +6,6 @@ import org.keycloak.config.ConfigKeystoreOptions;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Optional;
import static org.keycloak.quarkus.runtime.configuration.mappers.PropertyMapper.fromOption;
@ -38,7 +37,7 @@ final class ConfigKeystorePropertyMappers {
};
}
private static Optional<String> validatePath(Optional<String> option, ConfigSourceInterceptorContext context) {
private static String validatePath(String option, ConfigSourceInterceptorContext context) {
ConfigValue path = context.proceed(SMALLRYE_KEYSTORE_PATH);
boolean isPasswordDefined = context.proceed(SMALLRYE_KEYSTORE_PASSWORD) != null;
@ -55,10 +54,10 @@ final class ConfigKeystorePropertyMappers {
throw new IllegalArgumentException("config-keystore path does not exist: " + realPath);
}
return Optional.of(realPath.toUri().toString());
return realPath.toUri().toString();
}
private static Optional<String> validatePassword(Optional<String> option, ConfigSourceInterceptorContext context) {
private static String validatePassword(String option, ConfigSourceInterceptorContext context) {
boolean isPasswordDefined = context.proceed(SMALLRYE_KEYSTORE_PASSWORD).getValue() != null;
boolean isPathDefined = context.proceed(SMALLRYE_KEYSTORE_PATH) != null;

View file

@ -7,9 +7,6 @@ import org.keycloak.config.DatabaseOptions;
import org.keycloak.config.database.Database;
import org.keycloak.quarkus.runtime.configuration.Configuration;
import java.util.Optional;
import static java.util.Optional.of;
import static org.keycloak.quarkus.runtime.configuration.mappers.PropertyMapper.fromOption;
final class DatabasePropertyMappers {
@ -19,13 +16,11 @@ final class DatabasePropertyMappers {
public static PropertyMapper<?>[] getDatabasePropertyMappers() {
return new PropertyMapper[] {
fromOption(DatabaseOptions.DB_DIALECT)
.mapFrom("db")
.transformer(DatabasePropertyMappers::transformDialect)
.mapFrom(DatabaseOptions.DB, DatabasePropertyMappers::transformDialect)
.build(),
fromOption(DatabaseOptions.DB_DRIVER)
.mapFrom("db")
.mapFrom(DatabaseOptions.DB, DatabasePropertyMappers::getXaOrNonXaDriver)
.to("quarkus.datasource.jdbc.driver")
.transformer(DatabasePropertyMappers::getXaOrNonXaDriver)
.paramLabel("driver")
.build(),
fromOption(DatabaseOptions.DB)
@ -35,8 +30,7 @@ final class DatabasePropertyMappers {
.build(),
fromOption(DatabaseOptions.DB_URL)
.to("quarkus.datasource.jdbc.url")
.mapFrom("db")
.transformer(DatabasePropertyMappers::getDatabaseUrl)
.mapFrom(DatabaseOptions.DB, DatabasePropertyMappers::getDatabaseUrl)
.paramLabel("jdbc-url")
.build(),
fromOption(DatabaseOptions.DB_URL_HOST)
@ -84,44 +78,32 @@ final class DatabasePropertyMappers {
};
}
private static Optional<String> getDatabaseUrl(Optional<String> value, ConfigSourceInterceptorContext c) {
Optional<String> url = Database.getDefaultUrl(value.get());
if (url.isPresent()) {
return url;
}
return value;
private static String getDatabaseUrl(String value, ConfigSourceInterceptorContext c) {
return Database.getDefaultUrl(value).orElse(null);
}
private static Optional<String> getXaOrNonXaDriver(Optional<String> value, ConfigSourceInterceptorContext context) {
private static String getXaOrNonXaDriver(String value, ConfigSourceInterceptorContext context) {
ConfigValue xaEnabledConfigValue = context.proceed("kc.transaction-xa-enabled");
boolean isXaEnabled = xaEnabledConfigValue != null && Boolean.parseBoolean(xaEnabledConfigValue.getValue());
Optional<String> driver = Database.getDriver(value.get(), isXaEnabled);
return Database.getDriver(value, isXaEnabled).orElse(null);
}
if (driver.isPresent()) {
return driver;
private static String toDatabaseKind(String db, ConfigSourceInterceptorContext context) {
return Database.getDatabaseKind(db).orElse(null);
}
private static String resolveUsername(String value, ConfigSourceInterceptorContext context) {
if (isDevModeDatabase(context)) {
return "sa";
}
return value;
}
private static Optional<String> toDatabaseKind(Optional<String> db, ConfigSourceInterceptorContext context) {
return Database.getDatabaseKind(db.get());
}
private static Optional<String> resolveUsername(Optional<String> value, ConfigSourceInterceptorContext context) {
private static String resolvePassword(String value, ConfigSourceInterceptorContext context) {
if (isDevModeDatabase(context)) {
return of("sa");
}
return value;
}
private static Optional<String> resolvePassword(Optional<String> value, ConfigSourceInterceptorContext context) {
if (isDevModeDatabase(context)) {
return of("password");
return "password";
}
return value;
@ -132,20 +114,8 @@ final class DatabasePropertyMappers {
return Database.getDatabaseKind(db).get().equals(DatabaseKind.H2);
}
private static Optional<String> transformDialect(Optional<String> db, ConfigSourceInterceptorContext context) {
Optional<String> databaseKind = Database.getDatabaseKind(db.get());
if (databaseKind.isEmpty()) {
return db;
}
Optional<String> dialect = Database.getDialect(db.get());
if (dialect.isPresent()) {
return dialect;
}
return Database.getDialect("dev-file");
private static String transformDialect(String db, ConfigSourceInterceptorContext context) {
return Database.getDialect(db).orElse(null);
}
}

View file

@ -27,8 +27,6 @@ import org.keycloak.exportimport.UsersExportStrategy;
import org.keycloak.quarkus.runtime.cli.PropertyException;
import org.keycloak.quarkus.runtime.configuration.Configuration;
import java.util.Optional;
import static org.keycloak.exportimport.ExportImportConfig.PROVIDER;
import static org.keycloak.quarkus.runtime.configuration.Configuration.getOptionalValue;
import static org.keycloak.quarkus.runtime.configuration.Configuration.isBlank;
@ -117,10 +115,10 @@ public final class ExportPropertyMappers {
.isPresent();
}
private static Optional<String> transformExporter(Optional<String> option, ConfigSourceInterceptorContext context) {
private static String transformExporter(String option, ConfigSourceInterceptorContext context) {
ConfigValue exporter = context.proceed(EXPORTER_PROPERTY);
if (exporter != null) {
return Optional.of(exporter.getValue());
return exporter.getValue();
}
var file = Configuration.getOptionalValue("kc.spi-export-single-file-file").map(f -> SINGLE_FILE);
@ -131,7 +129,7 @@ public final class ExportPropertyMappers {
// Only one option can be specified
boolean xor = file.isPresent() ^ dir.isPresent();
return xor ? file.or(() -> dir) : Optional.empty();
return xor ? file.or(() -> dir).get() : null;
}
}

View file

@ -18,8 +18,6 @@ import java.nio.file.Paths;
import java.util.Optional;
import java.util.function.BiFunction;
import static java.util.Optional.empty;
import static java.util.Optional.of;
import static org.keycloak.quarkus.runtime.configuration.mappers.PropertyMapper.fromOption;
public final class HttpPropertyMappers {
@ -69,7 +67,7 @@ public final class HttpPropertyMappers {
fromOption(HttpOptions.HTTPS_CERTIFICATES_RELOAD_PERIOD)
.to("quarkus.http.ssl.certificate.reload-period")
// -1 means no reload
.transformer((value, context) -> "-1".equals(value.get()) ? null : value)
.transformer((value, context) -> "-1".equals(value) ? null : value)
.paramLabel("reload period")
.build(),
fromOption(HttpOptions.HTTPS_CERTIFICATE_FILE)
@ -94,8 +92,7 @@ public final class HttpPropertyMappers {
.build(),
fromOption(HttpOptions.HTTPS_KEY_STORE_TYPE)
.to("quarkus.http.ssl.certificate.key-store-file-type")
.mapFrom(SecurityOptions.FIPS_MODE.getKey())
.transformer(HttpPropertyMappers::resolveKeyStoreType)
.mapFrom(SecurityOptions.FIPS_MODE, HttpPropertyMappers::resolveKeyStoreType)
.paramLabel("type")
.build(),
fromOption(HttpOptions.HTTPS_TRUST_STORE_FILE)
@ -109,8 +106,7 @@ public final class HttpPropertyMappers {
.build(),
fromOption(HttpOptions.HTTPS_TRUST_STORE_TYPE)
.to("quarkus.http.ssl.certificate.trust-store-file-type")
.mapFrom(SecurityOptions.FIPS_MODE.getKey())
.transformer(HttpPropertyMappers::resolveKeyStoreType)
.mapFrom(SecurityOptions.FIPS_MODE, HttpPropertyMappers::resolveKeyStoreType)
.paramLabel("type")
.build(),
fromOption(HttpOptions.HTTP_MAX_QUEUED_REQUESTS)
@ -133,7 +129,7 @@ public final class HttpPropertyMappers {
}
public static void validateConfig() {
boolean enabled = isHttpEnabled(Configuration.getOptionalKcValue(HttpOptions.HTTP_ENABLED.getKey()));
boolean enabled = isHttpEnabled(Configuration.getOptionalKcValue(HttpOptions.HTTP_ENABLED.getKey()).orElse(null));
boolean trustStoreFile = Configuration.getOptionalKcValue(HttpOptions.HTTPS_TRUST_STORE_FILE.getKey()).isPresent();
boolean keyStoreFile = Configuration.getOptionalKcValue(HttpOptions.HTTPS_KEY_STORE_FILE.getKey()).isPresent();
@ -194,23 +190,19 @@ public final class HttpPropertyMappers {
}
}
private static BiFunction<Optional<String>, ConfigSourceInterceptorContext, Optional<String>> validatePath(String key) {
return (value, context) -> Environment.isWindows() ? value.filter(v -> v.equals(context.proceed(key).getValue())).map(p -> p.replace("\\", "/")) : value;
private static BiFunction<String, ConfigSourceInterceptorContext, String> validatePath(String key) {
return (value, context) -> Environment.isWindows() && value != null && value.equals(context.proceed(key).getValue()) ? value.replace("\\", "/") : value;
}
private static Optional<String> getHttpEnabledTransformer(Optional<String> value, ConfigSourceInterceptorContext context) {
return of(isHttpEnabled(value) ? "enabled" : "disabled");
private static String getHttpEnabledTransformer(String value, ConfigSourceInterceptorContext context) {
return isHttpEnabled(value) ? "enabled" : "disabled";
}
private static boolean isHttpEnabled(Optional<String> value) {
boolean enabled = Boolean.parseBoolean(value.get());
Optional<String> proxy = Configuration.getOptionalKcValue("proxy");
if (Environment.isDevMode() || Environment.isNonServerMode()
|| ("edge".equalsIgnoreCase(proxy.orElse("")))) {
enabled = true;
private static boolean isHttpEnabled(String value) {
if (Environment.isDevMode() || Environment.isNonServerMode()) {
return true;
}
return enabled;
return Boolean.parseBoolean(value);
}
private static File getDefaultKeystorePathValue() {
@ -227,24 +219,18 @@ public final class HttpPropertyMappers {
return null;
}
private static Optional<String> resolveKeyStoreType(Optional<String> value,
private static String resolveKeyStoreType(String value,
ConfigSourceInterceptorContext configSourceInterceptorContext) {
if (value.isPresent()) {
try {
if (FipsMode.valueOfOption(value.get()).equals(FipsMode.STRICT)) {
return of("BCFKS");
}
return empty();
} catch (IllegalArgumentException ignore) {
}
if (FipsMode.STRICT.toString().equals(value)) {
return "BCFKS";
}
return value;
return null;
}
private static Optional<String> resolveMaxThreads(Optional<String> value,
private static String resolveMaxThreads(String value,
ConfigSourceInterceptorContext configSourceInterceptorContext) {
if (value.isEmpty()) {
return of(String.valueOf(Math.max(MIN_MAX_THREADS, 4 * Runtime.getRuntime().availableProcessors())));
if (value == null) {
return String.valueOf(Math.max(MIN_MAX_THREADS, 4 * Runtime.getRuntime().availableProcessors()));
}
return value;
}

View file

@ -26,8 +26,6 @@ import org.keycloak.config.OptionCategory;
import org.keycloak.exportimport.Strategy;
import org.keycloak.quarkus.runtime.cli.PropertyException;
import java.util.Optional;
import static org.keycloak.exportimport.ExportImportConfig.PROVIDER;
import static org.keycloak.quarkus.runtime.configuration.Configuration.getOptionalValue;
import static org.keycloak.quarkus.runtime.configuration.mappers.PropertyMapper.fromOption;
@ -95,18 +93,18 @@ public final class ImportPropertyMappers {
.isPresent();
}
private static Optional<String> transformOverride(Optional<String> option, ConfigSourceInterceptorContext context) {
if (option.isPresent() && Boolean.parseBoolean(option.get())) {
return Optional.of(Strategy.OVERWRITE_EXISTING.name());
private static String transformOverride(String option, ConfigSourceInterceptorContext context) {
if (Boolean.parseBoolean(option)) {
return Strategy.OVERWRITE_EXISTING.name();
} else {
return Optional.of(Strategy.IGNORE_EXISTING.name());
return Strategy.IGNORE_EXISTING.name();
}
}
private static Optional<String> transformImporter(Optional<String> option, ConfigSourceInterceptorContext context) {
private static String transformImporter(String option, ConfigSourceInterceptorContext context) {
ConfigValue importer = context.proceed(IMPORTER_PROPERTY);
if (importer != null) {
return Optional.of(importer.getValue());
return importer.getValue();
}
var file = getOptionalValue("kc.spi-import-single-file-file").map(f -> SINGLE_FILE);
@ -117,7 +115,7 @@ public final class ImportPropertyMappers {
// Only one option can be specified
boolean xor = file.isPresent() ^ dir.isPresent();
return xor ? file.or(() -> dir) : Optional.empty();
return xor ? file.or(() -> dir).get() : null;
}
}

View file

@ -1,13 +1,11 @@
package org.keycloak.quarkus.runtime.configuration.mappers;
import static java.util.Optional.of;
import static org.keycloak.config.LoggingOptions.DEFAULT_LOG_FORMAT;
import static org.keycloak.quarkus.runtime.configuration.Configuration.isTrue;
import static org.keycloak.quarkus.runtime.configuration.mappers.PropertyMapper.fromOption;
import java.io.File;
import java.util.Locale;
import java.util.Optional;
import java.util.function.BiFunction;
import java.util.logging.Level;
import java.util.stream.Stream;
@ -17,9 +15,9 @@ import org.keycloak.config.LoggingOptions;
import org.keycloak.config.Option;
import org.keycloak.quarkus.runtime.Messages;
import org.keycloak.quarkus.runtime.cli.PropertyException;
import org.keycloak.quarkus.runtime.configuration.Configuration;
import io.smallrye.config.ConfigSourceInterceptorContext;
import org.keycloak.quarkus.runtime.configuration.Configuration;
public final class LoggingPropertyMappers {
@ -62,15 +60,13 @@ public final class LoggingPropertyMappers {
.to("quarkus.log.console.color")
.build(),
fromOption(LoggingOptions.LOG_CONSOLE_ENABLED)
.mapFrom("log")
.mapFrom(LoggingOptions.LOG, LoggingPropertyMappers.resolveLogHandler(LoggingOptions.DEFAULT_LOG_HANDLER.name()))
.to("quarkus.log.console.enable")
.transformer(LoggingPropertyMappers.resolveLogHandler(LoggingOptions.DEFAULT_LOG_HANDLER.name()))
.build(),
// File
fromOption(LoggingOptions.LOG_FILE_ENABLED)
.mapFrom("log")
.mapFrom(LoggingOptions.LOG, LoggingPropertyMappers.resolveLogHandler("file"))
.to("quarkus.log.file.enable")
.transformer(LoggingPropertyMappers.resolveLogHandler("file"))
.build(),
fromOption(LoggingOptions.LOG_FILE)
.isEnabled(LoggingPropertyMappers::isFileEnabled, FILE_ENABLED_MSG)
@ -108,9 +104,8 @@ public final class LoggingPropertyMappers {
.build(),
// Syslog
fromOption(LoggingOptions.LOG_SYSLOG_ENABLED)
.mapFrom("log")
.mapFrom(LoggingOptions.LOG, LoggingPropertyMappers.resolveLogHandler("syslog"))
.to("quarkus.log.syslog.enable")
.transformer(LoggingPropertyMappers.resolveLogHandler("syslog"))
.build(),
fromOption(LoggingOptions.LOG_SYSLOG_ENDPOINT)
.isEnabled(LoggingPropertyMappers::isSyslogEnabled, SYSLOG_ENABLED_MSG)
@ -175,18 +170,16 @@ public final class LoggingPropertyMappers {
return isTrue(LoggingOptions.LOG_SYSLOG_ENABLED);
}
private static BiFunction<Optional<String>, ConfigSourceInterceptorContext, Optional<String>> resolveLogHandler(String handler) {
return (parentValue, context) -> {
String handlers = parentValue.get();
private static BiFunction<String, ConfigSourceInterceptorContext, String> resolveLogHandler(String handler) {
return (handlers, context) -> {
String[] logHandlerValues = handlers.split(",");
return of(String.valueOf(Stream.of(logHandlerValues).anyMatch(handler::equals)));
return String.valueOf(Stream.of(logHandlerValues).anyMatch(handler::equals));
};
}
private static Optional<String> resolveFileLogLocation(Optional<String> value, ConfigSourceInterceptorContext configSourceInterceptorContext) {
return value.map(location -> location.endsWith(File.separator) ? location + LoggingOptions.DEFAULT_LOG_FILENAME : location);
private static String resolveFileLogLocation(String value, ConfigSourceInterceptorContext configSourceInterceptorContext) {
return value.endsWith(File.separator) ? value + LoggingOptions.DEFAULT_LOG_FILENAME : value;
}
private static Level toLevel(String categoryLevel) throws IllegalArgumentException {
@ -221,13 +214,13 @@ public final class LoggingPropertyMappers {
}
}
private static Optional<String> resolveLogLevel(Optional<String> value, ConfigSourceInterceptorContext configSourceInterceptorContext) {
Optional<String> rootLevel = of(LoggingOptions.DEFAULT_LOG_LEVEL.name());
private static String resolveLogLevel(String value, ConfigSourceInterceptorContext configSourceInterceptorContext) {
String rootLevel = LoggingOptions.DEFAULT_LOG_LEVEL.name();
for (String level : value.get().split(",")) {
for (String level : value.split(",")) {
var categoryLevel = validateLogLevel(level);
if (categoryLevel.category == null) {
rootLevel = of(categoryLevel.levelName);
rootLevel = categoryLevel.levelName;
} else {
setCategoryLevel(categoryLevel.category, categoryLevel.levelName);
}
@ -236,26 +229,23 @@ public final class LoggingPropertyMappers {
return rootLevel;
}
private static Optional<String> resolveLogOutput(Optional<String> value, ConfigSourceInterceptorContext context) {
if (value.get().equals(LoggingOptions.DEFAULT_CONSOLE_OUTPUT.name().toLowerCase(Locale.ROOT))) {
return of(Boolean.FALSE.toString());
}
return of(Boolean.TRUE.toString());
private static String resolveLogOutput(String value, ConfigSourceInterceptorContext context) {
boolean isDefault = LoggingOptions.DEFAULT_CONSOLE_OUTPUT.name().toLowerCase(Locale.ROOT).equals(value);
return Boolean.valueOf(!isDefault).toString();
}
/**
* Add tracing info to the log if the format is not explicitly set, and tracing and {@code includeTraceOption} options are enabled
*/
private static Optional<String> addTracingInfo(Optional<String> value, Option<Boolean> includeTraceOption) {
private static String addTracingInfo(String value, Option<Boolean> includeTraceOption) {
var isTracingEnabled = TracingPropertyMappers.isTracingEnabled();
var includeTrace = Configuration.isTrue(includeTraceOption);
var isChangedLogFormat = !DEFAULT_LOG_FORMAT.equals(value.get());
var isChangedLogFormat = !DEFAULT_LOG_FORMAT.equals(value);
if (!isTracingEnabled || !includeTrace || isChangedLogFormat) {
return value;
}
return Optional.of(LoggingOptions.DEFAULT_LOG_TRACING_FORMAT);
return LoggingOptions.DEFAULT_LOG_TRACING_FORMAT;
}
}

View file

@ -24,8 +24,6 @@ import org.keycloak.quarkus.runtime.Messages;
import org.keycloak.quarkus.runtime.cli.PropertyException;
import org.keycloak.quarkus.runtime.configuration.Configuration;
import java.util.Optional;
import static org.keycloak.config.ManagementOptions.LEGACY_OBSERVABILITY_INTERFACE;
import static org.keycloak.quarkus.runtime.configuration.Configuration.isTrue;
import static org.keycloak.quarkus.runtime.configuration.mappers.PropertyMapper.fromOption;
@ -44,7 +42,7 @@ public class ManagementPropertyMappers {
fromOption(ManagementOptions.LEGACY_OBSERVABILITY_INTERFACE)
.build(),
fromOption(ManagementOptions.HTTP_MANAGEMENT_RELATIVE_PATH)
.mapFrom(HttpOptions.HTTP_RELATIVE_PATH.getKey())
.mapFrom(HttpOptions.HTTP_RELATIVE_PATH)
.to("quarkus.management.root-path")
.paramLabel("path")
.build(),
@ -53,55 +51,54 @@ public class ManagementPropertyMappers {
.paramLabel("port")
.build(),
fromOption(ManagementOptions.HTTP_MANAGEMENT_HOST)
.mapFrom(HttpOptions.HTTP_HOST.getKey())
.mapFrom(HttpOptions.HTTP_HOST)
.to("quarkus.management.host")
.paramLabel("host")
.build(),
// HTTPS
fromOption(ManagementOptions.HTTPS_MANAGEMENT_CLIENT_AUTH)
.mapFrom(HttpOptions.HTTPS_CLIENT_AUTH.getKey())
.mapFrom(HttpOptions.HTTPS_CLIENT_AUTH)
.to("quarkus.management.ssl.client-auth")
.paramLabel("auth")
.build(),
fromOption(ManagementOptions.HTTPS_MANAGEMENT_CIPHER_SUITES)
.mapFrom(HttpOptions.HTTPS_CIPHER_SUITES.getKey())
.mapFrom(HttpOptions.HTTPS_CIPHER_SUITES)
.to("quarkus.management.ssl.cipher-suites")
.paramLabel("ciphers")
.build(),
fromOption(ManagementOptions.HTTPS_MANAGEMENT_PROTOCOLS)
.mapFrom(HttpOptions.HTTPS_PROTOCOLS.getKey())
.mapFrom(HttpOptions.HTTPS_PROTOCOLS)
.to("quarkus.management.ssl.protocols")
.paramLabel("protocols")
.build(),
fromOption(ManagementOptions.HTTPS_MANAGEMENT_CERTIFICATE_FILE)
.mapFrom(HttpOptions.HTTPS_CERTIFICATE_FILE.getKey())
.mapFrom(HttpOptions.HTTPS_CERTIFICATE_FILE)
.to("quarkus.management.ssl.certificate.files")
.validator(value -> validateTlsProperties())
.paramLabel("file")
.build(),
fromOption(ManagementOptions.HTTPS_MANAGEMENT_CERTIFICATE_KEY_FILE)
.mapFrom(HttpOptions.HTTPS_CERTIFICATE_KEY_FILE.getKey())
.mapFrom(HttpOptions.HTTPS_CERTIFICATE_KEY_FILE)
.to("quarkus.management.ssl.certificate.key-files")
.validator(value -> validateTlsProperties())
.paramLabel("file")
.build(),
fromOption(ManagementOptions.HTTPS_MANAGEMENT_KEY_STORE_FILE)
.mapFrom(HttpOptions.HTTPS_KEY_STORE_FILE.getKey())
.mapFrom(HttpOptions.HTTPS_KEY_STORE_FILE)
.to("quarkus.management.ssl.certificate.key-store-file")
.validator(value -> validateTlsProperties())
.paramLabel("file")
.build(),
fromOption(ManagementOptions.HTTPS_MANAGEMENT_KEY_STORE_PASSWORD)
.mapFrom(HttpOptions.HTTPS_KEY_STORE_PASSWORD.getKey())
.mapFrom(HttpOptions.HTTPS_KEY_STORE_PASSWORD)
.to("quarkus.management.ssl.certificate.key-store-password")
.validator(value -> validateTlsProperties())
.paramLabel("password")
.isMasked(true)
.build(),
fromOption(ManagementOptions.HTTPS_MANAGEMENT_KEY_STORE_TYPE)
.mapFrom(HttpOptions.HTTPS_KEY_STORE_TYPE.getKey())
.mapFrom(HttpOptions.HTTPS_KEY_STORE_TYPE)
.to("quarkus.management.ssl.certificate.key-store-file-type")
.transformer((value, config) -> value.or(() -> Configuration.getOptionalKcValue(HttpOptions.HTTPS_KEY_STORE_TYPE.getKey())))
.paramLabel("type")
.build(),
};
@ -115,8 +112,8 @@ public class ManagementPropertyMappers {
return isManagementOccupied;
}
private static Optional<String> managementEnabledTransformer() {
return Optional.of(Boolean.toString(isManagementEnabled()));
private static String managementEnabledTransformer() {
return Boolean.toString(isManagementEnabled());
}
public static boolean isManagementTlsEnabled() {

View file

@ -42,6 +42,7 @@ import org.keycloak.config.OptionCategory;
import org.keycloak.quarkus.runtime.cli.PropertyException;
import org.keycloak.quarkus.runtime.cli.ShortErrorMessageHandler;
import org.keycloak.quarkus.runtime.configuration.ConfigArgsConfigSource;
import org.keycloak.quarkus.runtime.configuration.Configuration;
import org.keycloak.quarkus.runtime.configuration.KcEnvConfigSource;
import org.keycloak.quarkus.runtime.configuration.KeycloakConfigSourceProvider;
import org.keycloak.quarkus.runtime.Environment;
@ -58,6 +59,7 @@ public class PropertyMapper<T> {
null,
null,
null,
null,
false,
null,
null) {
@ -71,8 +73,9 @@ public class PropertyMapper<T> {
private final String to;
private BooleanSupplier enabled;
private String enabledWhen;
private final BiFunction<Optional<String>, ConfigSourceInterceptorContext, Optional<String>> mapper;
private final BiFunction<String, ConfigSourceInterceptorContext, String> mapper;
private final String mapFrom;
private final BiFunction<String, ConfigSourceInterceptorContext, String> parentMapper;
private final boolean mask;
private final String paramLabel;
private final String envVarFormat;
@ -81,14 +84,15 @@ public class PropertyMapper<T> {
private final String description;
PropertyMapper(Option<T> option, String to, BooleanSupplier enabled, String enabledWhen,
BiFunction<Optional<String>, ConfigSourceInterceptorContext, Optional<String>> mapper,
String mapFrom, String paramLabel, boolean mask, BiConsumer<PropertyMapper<T>, ConfigValue> validator,
BiFunction<String, ConfigSourceInterceptorContext, String> mapper,
String mapFrom, BiFunction<String, ConfigSourceInterceptorContext, String> parentMapper,
String paramLabel, boolean mask, BiConsumer<PropertyMapper<T>, ConfigValue> validator,
String description) {
this.option = option;
this.to = to == null ? getFrom() : to;
this.enabled = enabled;
this.enabledWhen = enabledWhen;
this.mapper = mapper == null ? PropertyMapper::defaultTransformer : mapper;
this.mapper = mapper;
this.mapFrom = mapFrom;
this.paramLabel = paramLabel;
this.mask = mask;
@ -96,10 +100,7 @@ public class PropertyMapper<T> {
this.envVarFormat = toEnvVarFormat(getFrom());
this.validator = validator;
this.description = description;
}
private static Optional<String> defaultTransformer(Optional<String> value, ConfigSourceInterceptorContext context) {
return value;
this.parentMapper = parentMapper;
}
ConfigValue getConfigValue(ConfigSourceInterceptorContext context) {
@ -122,53 +123,25 @@ public class PropertyMapper<T> {
// try to obtain the value for the property we want to map first
ConfigValue config = convertValue(context.proceed(from));
if (config == null || config.getValue() == null) {
if (mapFrom != null) {
// if the property we want to map depends on another one, we use the value from the other property to call the mapper
String parentKey = MicroProfileConfigProvider.NS_KEYCLOAK_PREFIX + mapFrom;
ConfigValue parentValue = convertValue(context.proceed(parentKey));
boolean parentValue = false;
if (mapFrom != null && (config == null || config.getValue() == null)) {
// if the property we want to map depends on another one, we use the value from the other property to call the mapper
config = Configuration.getKcConfigValue(mapFrom);
parentValue = true;
}
if (parentValue == null) {
// parent value not explicitly set, try to resolve the default value set to the parent property
PropertyMapper<?> parentMapper = PropertyMappers.getMapper(parentKey);
if (parentMapper != null && parentMapper.getDefaultValue().isPresent()) {
parentValue = ConfigValue.builder().withValue(Option.getDefaultValueString(parentMapper.getDefaultValue().get())).build();
}
}
return transformValue(name, ofNullable(parentValue == null ? null : parentValue.getValue()), context, null);
}
ConfigValue defaultValue = transformValue(name, this.option.getDefaultValue().map(Option::getDefaultValueString), context, null);
if (defaultValue != null) {
return defaultValue;
}
// now tries any defaults from quarkus
ConfigValue current = context.proceed(name);
if (current != null) {
return transformValue(name, ofNullable(current.getValue()), context, current.getConfigSourceName());
}
return current;
if (config != null && config.getValue() != null) {
config = transformValue(name, config.getValue(), context, config.getConfigSourceName(), parentValue);
} else {
config = transformValue(name, this.option.getDefaultValue().map(Option::getDefaultValueString).orElse(null), context, null, false);
}
ConfigValue transformedValue = transformValue(name, ofNullable(config.getValue()), context, config.getConfigSourceName());
// we always fallback to the current value from the property we are mapping
if (transformedValue == null) {
return ConfigValue.builder()
.withName(name)
.withValue(null)
.withRawValue(config.getValue())
.withConfigSourceName(config.getConfigSourceName())
.build();
if (config != null) {
return config;
}
return transformedValue;
// now try any defaults from quarkus
return context.proceed(name);
}
public Option<T> getOption() {
@ -259,30 +232,22 @@ public class PropertyMapper<T> {
return option.getDeprecatedMetadata();
}
private ConfigValue transformValue(String name, Optional<String> value, ConfigSourceInterceptorContext context, String configSourceName) {
if (value == null) {
return null;
private ConfigValue transformValue(String name, String value, ConfigSourceInterceptorContext context, String configSourceName, boolean parentValue) {
String mappedValue = value;
var theMapper = parentValue ? this.parentMapper : this.mapper;
if (theMapper != null && (!name.equals(getFrom()) || parentValue)) {
mappedValue = theMapper.apply(value, context);
}
if (mapper == null || (mapFrom == null && name.equals(getFrom()))) {
// no mapper set or requesting a property that does not depend on other property, just return the value from the config source
return ConfigValue.builder()
.withName(name)
.withValue(value.orElse(null))
.withConfigSourceName(configSourceName)
.build();
}
Optional<String> mappedValue = mapper.apply(value, context);
if (mappedValue == null || mappedValue.isEmpty()) {
if (value == null && mappedValue == null) {
return null;
}
return ConfigValue.builder()
.withName(name)
.withValue(mappedValue.get())
.withRawValue(value.orElse(null))
.withValue(mappedValue)
.withRawValue(value)
.withConfigSourceName(configSourceName)
.build();
}
@ -299,8 +264,9 @@ public class PropertyMapper<T> {
private final Option<T> option;
private String to;
private BiFunction<Optional<String>, ConfigSourceInterceptorContext, Optional<String>> mapper;
private BiFunction<String, ConfigSourceInterceptorContext, String> mapper;
private String mapFrom = null;
private BiFunction<String, ConfigSourceInterceptorContext, String> parentMapper;
private boolean isMasked = false;
private BooleanSupplier isEnabled = () -> true;
private String enabledWhen = "";
@ -318,7 +284,12 @@ public class PropertyMapper<T> {
return this;
}
public Builder<T> transformer(BiFunction<Optional<String>, ConfigSourceInterceptorContext, Optional<String>> mapper) {
/**
* NOTE: This transformer will not apply to the mapFrom value. When using
* {@link #mapFrom} you generally need a transformer specifically for the parent
* value, see {@link #mapFrom(Option, BiFunction)}
*/
public Builder<T> transformer(BiFunction<String, ConfigSourceInterceptorContext, String> mapper) {
this.mapper = mapper;
return this;
}
@ -333,8 +304,9 @@ public class PropertyMapper<T> {
return this;
}
public Builder<T> mapFrom(String mapFrom) {
this.mapFrom = mapFrom;
public Builder<T> mapFrom(Option<?> mapFrom, BiFunction<String, ConfigSourceInterceptorContext, String> parentMapper) {
this.mapFrom = mapFrom.getKey();
this.parentMapper = parentMapper;
return this;
}
@ -404,7 +376,7 @@ public class PropertyMapper<T> {
if (paramLabel == null && Boolean.class.equals(option.getType())) {
paramLabel = Boolean.TRUE + "|" + Boolean.FALSE;
}
return new PropertyMapper<T>(option, to, isEnabled, enabledWhen, mapper, mapFrom, paramLabel, isMasked, validator, description);
return new PropertyMapper<T>(option, to, isEnabled, enabledWhen, mapper, mapFrom, parentMapper, paramLabel, isMasked, validator, description);
}
}

View file

@ -6,8 +6,6 @@ import org.keycloak.config.ProxyOptions;
import org.keycloak.quarkus.runtime.cli.PropertyException;
import org.keycloak.quarkus.runtime.configuration.Configuration;
import java.util.Optional;
import static org.keycloak.quarkus.runtime.configuration.mappers.PropertyMapper.fromOption;
final class ProxyPropertyMappers {
@ -26,18 +24,15 @@ final class ProxyPropertyMappers {
.build(),
fromOption(ProxyOptions.PROXY_FORWARDED_HOST)
.to("quarkus.http.proxy.enable-forwarded-host")
.mapFrom("proxy-headers")
.transformer((v, c) -> proxyEnabled(null, v, c))
.mapFrom(ProxyOptions.PROXY_HEADERS, (v, c) -> proxyEnabled(null, v, c))
.build(),
fromOption(ProxyOptions.PROXY_FORWARDED_HEADER_ENABLED)
.to("quarkus.http.proxy.allow-forwarded")
.mapFrom("proxy-headers")
.transformer((v, c) -> proxyEnabled(ProxyOptions.Headers.forwarded, v, c))
.mapFrom(ProxyOptions.PROXY_HEADERS, (v, c) -> proxyEnabled(ProxyOptions.Headers.forwarded, v, c))
.build(),
fromOption(ProxyOptions.PROXY_X_FORWARDED_HEADER_ENABLED)
.to("quarkus.http.proxy.allow-x-forwarded")
.mapFrom("proxy-headers")
.transformer((v, c) -> proxyEnabled(ProxyOptions.Headers.xforwarded, v, c))
.mapFrom(ProxyOptions.PROXY_HEADERS, (v, c) -> proxyEnabled(ProxyOptions.Headers.xforwarded, v, c))
.build(),
fromOption(ProxyOptions.PROXY_TRUSTED_ADDRESSES)
.to("quarkus.http.proxy.trusted-proxies")
@ -57,18 +52,18 @@ final class ProxyPropertyMappers {
}
}
private static Optional<String> proxyEnabled(ProxyOptions.Headers testHeader, Optional<String> value, ConfigSourceInterceptorContext context) {
private static String proxyEnabled(ProxyOptions.Headers testHeader, String value, ConfigSourceInterceptorContext context) {
boolean enabled = false;
if (value.isPresent()) { // proxy-headers explicitly configured
if (value != null) { // proxy-headers explicitly configured
if (testHeader != null) {
enabled = ProxyOptions.Headers.valueOf(value.get()).equals(testHeader);
enabled = ProxyOptions.Headers.valueOf(value).equals(testHeader);
} else {
enabled = true;
}
}
return Optional.of(String.valueOf(enabled));
return String.valueOf(enabled);
}
}

View file

@ -1,10 +1,7 @@
package org.keycloak.quarkus.runtime.configuration.mappers;
import static java.util.Optional.of;
import static org.keycloak.quarkus.runtime.configuration.mappers.PropertyMapper.fromOption;
import java.util.Optional;
import org.keycloak.common.Profile;
import org.keycloak.common.Profile.Feature;
import org.keycloak.common.crypto.FipsMode;
@ -25,15 +22,15 @@ final class SecurityPropertyMappers {
};
}
private static Optional<String> resolveFipsMode(Optional<String> value, ConfigSourceInterceptorContext context) {
if (value.isEmpty()) {
private static String resolveFipsMode(String value, ConfigSourceInterceptorContext context) {
if (value == null) {
if (Profile.isFeatureEnabled(Feature.FIPS)) {
return of(FipsMode.NON_STRICT.toString());
return FipsMode.NON_STRICT.toString();
}
return of(FipsMode.DISABLED.toString());
return FipsMode.DISABLED.toString();
}
return of(FipsMode.valueOfOption(value.get()).toString());
return value;
}
}

View file

@ -4,11 +4,8 @@ import io.smallrye.config.ConfigSourceInterceptorContext;
import org.keycloak.config.TransactionOptions;
import static java.util.Optional.of;
import static org.keycloak.quarkus.runtime.configuration.mappers.PropertyMapper.fromOption;
import java.util.Optional;
public class TransactionPropertyMappers {
private static final String QUARKUS_TXPROP_TARGET = "quarkus.datasource.jdbc.transactions";
@ -24,13 +21,13 @@ public class TransactionPropertyMappers {
};
}
private static Optional<String> getQuarkusTransactionsValue(Optional<String> txValue, ConfigSourceInterceptorContext context) {
boolean isXaEnabled = Boolean.parseBoolean(txValue.get());
private static String getQuarkusTransactionsValue(String txValue, ConfigSourceInterceptorContext context) {
boolean isXaEnabled = Boolean.parseBoolean(txValue);
if (isXaEnabled) {
return of("xa");
return "xa";
}
return of("enabled");
return "enabled";
}
}

View file

@ -358,7 +358,7 @@ public class ConfigurationTest extends AbstractConfigurationTest {
// If explicitly set, then it is always used regardless of the profile
System.clearProperty(org.keycloak.common.util.Environment.PROFILE);
ConfigArgsConfigSource.setCliArgs("--cache=cluster-foo.xml");
ConfigArgsConfigSource.setCliArgs("--cache-config-file=cluster-foo.xml");
Assert.assertEquals("cluster-foo.xml", initConfig("connectionsInfinispan", "quarkus").get("configFile"));
System.setProperty(org.keycloak.common.util.Environment.PROFILE, "dev");
@ -449,7 +449,7 @@ public class ConfigurationTest extends AbstractConfigurationTest {
assertEquals("false", createConfig().getConfigValue("kc.hostname-strict").getValue());
Environment.setProfile("prod");
assertEquals("true", createConfig().getConfigValue("kc.hostname-strict").getValue());
assertEquals("true", createConfig().getConfigValue("kc.spi-hostname-v2-hostname-strict").getValue());
}
@Test