Move all dist options to the new module

Co-authored-by: Pedro Igor <pigor.craveiro@gmail.com>
This commit is contained in:
andreaTP 2022-06-02 15:17:41 +01:00 committed by Pedro Igor
parent 71e7982a49
commit 3abcc699a1
35 changed files with 985 additions and 534 deletions

View File

@ -38,4 +38,11 @@
<maven.compiler.target>11</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-common</artifactId>
</dependency>
</dependencies>
</project>

View File

@ -5,9 +5,19 @@ import java.util.List;
public class AllOptions {
public final static List<Option<?>> ALL_OPTIONS = new ArrayList<>();
public static final List<Option<?>> ALL_OPTIONS = new ArrayList<>();
static {
ALL_OPTIONS.addAll(ClusteringOptions.ALL_OPTIONS);
ALL_OPTIONS.addAll(DatabaseOptions.ALL_OPTIONS);
ALL_OPTIONS.addAll(FeatureOptions.ALL_OPTIONS);
ALL_OPTIONS.addAll(HealthOptions.ALL_OPTIONS);
ALL_OPTIONS.addAll(HostnameOptions.ALL_OPTIONS);
ALL_OPTIONS.addAll(HttpOptions.ALL_OPTIONS);
ALL_OPTIONS.addAll(LoggingOptions.ALL_OPTIONS);
ALL_OPTIONS.addAll(MetricsOptions.ALL_OPTIONS);
ALL_OPTIONS.addAll(ProxyOptions.ALL_OPTIONS);
ALL_OPTIONS.addAll(TransactionOptions.ALL_OPTIONS);
ALL_OPTIONS.addAll(VaultOptions.ALL_OPTIONS);
}
}

View File

@ -0,0 +1,54 @@
package org.keycloak.config;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
public class ClusteringOptions {
public enum Mechanism {
ispn,
local
}
public static final Option CACHE = new OptionBuilder<>("cache", Mechanism.class)
.category(OptionCategory.CLUSTERING)
.description("Defines the cache mechanism for high-availability. "
+ "By default, a 'ispn' cache is used to create a cluster between multiple server nodes. "
+ "A 'local' cache disables clustering and is intended for development and testing purposes.")
.defaultValue(Mechanism.ispn)
.buildTime(true)
.build();
public enum Stack {
tcp,
udp,
kubernetes,
ec2,
azure,
google;
}
public static final Option CACHE_STACK = new OptionBuilder<>("cache-stack", Stack.class)
.category(OptionCategory.CLUSTERING)
.description("Define the default stack to use for cluster communication and node discovery. This option only takes effect "
+ "if 'cache' is set to 'ispn'. Default: udp.")
.buildTime(true)
.expectedValues(Stack.values())
.build();
public static final Option<File> CACHE_CONFIG_FILE = new OptionBuilder<>("cache-config-file", File.class)
.category(OptionCategory.CLUSTERING)
.description("Defines the file from which cache configuration should be loaded from. "
+ "The configuration file is relative to the 'conf/' directory.")
.buildTime(true)
.build();
public static final List<Option<?>> ALL_OPTIONS = new ArrayList<>();
static {
ALL_OPTIONS.add(CACHE);
ALL_OPTIONS.add(CACHE_STACK);
ALL_OPTIONS.add(CACHE_CONFIG_FILE);
}
}

View File

@ -0,0 +1,104 @@
package org.keycloak.config;
import org.keycloak.config.database.Database;
import java.util.ArrayList;
import java.util.List;
public class DatabaseOptions {
public static final Option<String> DB_DIALECT = new OptionBuilder<>("db-dialect", String.class)
.category(OptionCategory.DATABASE)
.runtimes(Option.Runtime.OPERATOR)
.buildTime(true)
.build();
public static final Option<String> DB_DRIVER = new OptionBuilder<>("db-driver", String.class)
.category(OptionCategory.DATABASE)
.runtimes(Option.Runtime.OPERATOR)
.defaultValue(Database.getDriver("dev-file", true).get())
.build();
public static final Option<Database.Vendor> DB = new OptionBuilder<>("db", Database.Vendor.class)
.category(OptionCategory.DATABASE)
.description("The database vendor. Possible values are: " + String.join(", ", Database.getAliases()))
.expectedStringValues(Database.getAliases())
.buildTime(true)
.build();
public static final Option<String> DB_URL = new OptionBuilder<>("db-url", String.class)
.category(OptionCategory.DATABASE)
.description("The full database JDBC URL. If not provided, a default URL is set based on the selected database vendor. " +
"For instance, if using 'postgres', the default JDBC URL would be 'jdbc:postgresql://localhost/keycloak'. ")
.build();
public static final Option<String> DB_URL_HOST = new OptionBuilder<>("db-url-host", String.class)
.category(OptionCategory.DATABASE)
.description("Sets the hostname of the default JDBC URL of the chosen vendor. If the `db-url` option is set, this option is ignored.")
.build();
public static final Option<String> DB_URL_DATABASE = new OptionBuilder<>("db-url-database", String.class)
.category(OptionCategory.DATABASE)
.description("Sets the database name of the default JDBC URL of the chosen vendor. If the `db-url` option is set, this option is ignored.")
.build();
public static final Option<Integer> DB_URL_PORT = new OptionBuilder<>("db-url-port", Integer.class)
.category(OptionCategory.DATABASE)
.description("Sets the port of the default JDBC URL of the chosen vendor. If the `db-url` option is set, this option is ignored.")
.build();
public static final Option<String> DB_URL_PROPERTIES = new OptionBuilder<>("db-url-properties", String.class)
.category(OptionCategory.DATABASE)
.description("Sets the properties of the default JDBC URL of the chosen vendor. If the `db-url` option is set, this option is ignored.")
.build();
public static final Option<String> DB_USERNAME = new OptionBuilder<>("db-username", String.class)
.category(OptionCategory.DATABASE)
.description("The username of the database user.")
.build();
public static final Option<String> DB_PASSWORD = new OptionBuilder<>("db-password", String.class)
.category(OptionCategory.DATABASE)
.description("The password of the database user.")
.build();
public static final Option<String> DB_SCHEMA = new OptionBuilder<>("db-schema", String.class)
.category(OptionCategory.DATABASE)
.description("The database schema to be used.")
.build();
public static final Option<Integer> DB_POOL_INITIAL_SIZE = new OptionBuilder<>("db-pool-initial-size", Integer.class)
.category(OptionCategory.DATABASE)
.description("The initial size of the connection pool.")
.build();
public static final Option<Integer> DB_POOL_MIN_SIZE = new OptionBuilder<>("db-pool-min-size", Integer.class)
.category(OptionCategory.DATABASE)
.description("The minimal size of the connection pool.")
.build();
public static final Option<Integer> DB_POOL_MAX_SIZE = new OptionBuilder<>("db-pool-max-size", Integer.class)
.category(OptionCategory.DATABASE)
.defaultValue(100)
.description("The maximum size of the connection pool.")
.build();
public static final List<Option<?>> ALL_OPTIONS = new ArrayList<>();
static {
ALL_OPTIONS.add(DB_DIALECT);
ALL_OPTIONS.add(DB_DRIVER);
ALL_OPTIONS.add(DB);
ALL_OPTIONS.add(DB_URL);
ALL_OPTIONS.add(DB_URL_HOST);
ALL_OPTIONS.add(DB_URL_DATABASE);
ALL_OPTIONS.add(DB_URL_PORT);
ALL_OPTIONS.add(DB_URL_PROPERTIES);
ALL_OPTIONS.add(DB_USERNAME);
ALL_OPTIONS.add(DB_PASSWORD);
ALL_OPTIONS.add(DB_SCHEMA);
ALL_OPTIONS.add(DB_POOL_INITIAL_SIZE);
ALL_OPTIONS.add(DB_POOL_MIN_SIZE);
ALL_OPTIONS.add(DB_POOL_MAX_SIZE);
}
}

View File

@ -0,0 +1,42 @@
package org.keycloak.config;
import org.keycloak.common.Profile;
import java.util.ArrayList;
import java.util.List;
public class FeatureOptions {
public static final Option FEATURES = new OptionBuilder("features", List.class, Profile.Feature.class)
.category(OptionCategory.FEATURE)
.description("Enables a set of one or more features.")
.expectedStringValues(getFeatureValues())
.buildTime(true)
.build();
public static final Option FEATURES_DISABLED = new OptionBuilder("features-disabled", List.class, Profile.Feature.class)
.category(OptionCategory.FEATURE)
.description("Disables a set of one or more features.")
.expectedStringValues(getFeatureValues())
.buildTime(true)
.build();
private static List<String> getFeatureValues() {
List<String> features = new ArrayList<>();
for (Profile.Feature value : Profile.Feature.values()) {
features.add(value.name().toLowerCase().replace('_', '-'));
}
features.add(Profile.Type.PREVIEW.name().toLowerCase());
return features;
}
public static final List<Option<?>> ALL_OPTIONS = new ArrayList<>();
static {
ALL_OPTIONS.add(FEATURES);
ALL_OPTIONS.add(FEATURES_DISABLED);
}
}

View File

@ -0,0 +1,21 @@
package org.keycloak.config;
import java.util.ArrayList;
import java.util.List;
public class HealthOptions {
public static final Option HEALTH_ENABLED = new OptionBuilder<>("health-enabled", Boolean.class)
.category(OptionCategory.HEALTH)
.description("If the server should expose health check endpoints. If enabled, health checks are available at the '/health', '/health/ready' and '/health/live' endpoints.")
.defaultValue(Boolean.FALSE)
.buildTime(true)
.expectedValues(Boolean.TRUE, Boolean.FALSE)
.build();
public static final List<Option<?>> ALL_OPTIONS = new ArrayList<>();
static {
ALL_OPTIONS.add(HEALTH_ENABLED);
}
}

View File

@ -0,0 +1,57 @@
package org.keycloak.config;
import java.util.ArrayList;
import java.util.List;
public class HostnameOptions {
public static final Option HOSTNAME = new OptionBuilder<>("hostname", String.class)
.category(OptionCategory.HOSTNAME)
.description("Hostname for the Keycloak server.")
.build();
public static final Option HOSTNAME_ADMIN = new OptionBuilder<>("hostname-admin", String.class)
.category(OptionCategory.HOSTNAME)
.description("The hostname for accessing the administration console. Use this option if you are exposing the administration console using a hostname other than the value set to the 'hostname' option.")
.build();
public static final Option HOSTNAME_STRICT = new OptionBuilder<>("hostname-strict", Boolean.class)
.category(OptionCategory.HOSTNAME)
.description("Disables dynamically resolving the hostname from request headers. Should always be set to true in production, unless proxy verifies the Host header.")
.defaultValue(Boolean.TRUE)
.build();
public static final Option HOSTNAME_STRICT_HTTPS = new OptionBuilder<>("hostname-strict-https", Boolean.class)
.category(OptionCategory.HOSTNAME)
.description("Forces URLs to use HTTPS. Only needed if proxy does not properly set the X-Forwarded-Proto header.")
.runtimes(Option.Runtime.OPERATOR)
.defaultValue(Boolean.TRUE)
.build();
public static final Option HOSTNAME_STRICT_BACKCHANNEL = new OptionBuilder<>("hostname-strict-backchannel", Boolean.class)
.category(OptionCategory.HOSTNAME)
.description("By default backchannel URLs are dynamically resolved from request headers to allow internal and external applications. If all applications use the public URL this option should be enabled.")
.build();
public static final Option HOSTNAME_PATH = new OptionBuilder<>("hostname-path", String.class)
.category(OptionCategory.HOSTNAME)
.description("This should be set if proxy uses a different context-path for Keycloak.")
.build();
public static final Option HOSTNAME_PORT = new OptionBuilder<>("hostname-port", Integer.class)
.category(OptionCategory.HOSTNAME)
.description("The port used by the proxy when exposing the hostname. Set this option if the proxy uses a port other than the default HTTP and HTTPS ports.")
.defaultValue(-1)
.build();
public static final List<Option<?>> ALL_OPTIONS = new ArrayList<>();
static {
ALL_OPTIONS.add(HOSTNAME);
ALL_OPTIONS.add(HOSTNAME_STRICT);
ALL_OPTIONS.add(HOSTNAME_STRICT_HTTPS);
ALL_OPTIONS.add(HOSTNAME_STRICT_BACKCHANNEL);
ALL_OPTIONS.add(HOSTNAME_PATH);
ALL_OPTIONS.add(HOSTNAME_PORT);
}
}

View File

@ -1,20 +1,128 @@
package org.keycloak.config;
import java.io.File;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
public class HttpOptions {
public final static Option httpPort = new OptionBuilder<Integer>("http-port", Integer.class)
.description("The used HTTP port.")
public static final Option<Boolean> HTTP_ENABLED = new OptionBuilder<>("http-enabled", Boolean.class)
.category(OptionCategory.HTTP)
.description("Enables the HTTP listener.")
.defaultValue(Boolean.FALSE)
.expectedValues(Boolean.TRUE, Boolean.FALSE)
.build();
public static final Option HTTP_HOST = new OptionBuilder<>("http-host", String.class)
.category(OptionCategory.HTTP)
.description("The used HTTP Host.")
.defaultValue("0.0.0.0")
.build();
public static final Option HTTP_RELATIVE_PATH = new OptionBuilder<>("http-relative-path", String.class)
.category(OptionCategory.HTTP)
.description("Set the path relative to '/' for serving resources.")
.defaultValue("/")
.buildTime(true)
.build();
public static final Option HTTP_PORT = new OptionBuilder<>("http-port", Integer.class)
.category(OptionCategory.HTTP)
.description("The used HTTP port.")
.defaultValue(8080)
.build();
public final static List<Option<?>> ALL_OPTIONS = new ArrayList<>();
public static final Option HTTPS_PORT = new OptionBuilder<>("https-port", Integer.class)
.category(OptionCategory.HTTP)
.description("The used HTTPS port.")
.defaultValue(8443)
.build();
public enum ClientAuth {
none,
request,
required
}
public static final Option HTTPS_CLIENT_AUTH = new OptionBuilder<>("https-client-auth", ClientAuth.class)
.category(OptionCategory.HTTP)
.description("Configures the server to require/request client authentication. Possible Values: none, request, required.")
.defaultValue(ClientAuth.none)
.expectedValues(ClientAuth.values())
.build();
public static final Option HTTPS_CIPHER_SUITES = new OptionBuilder<>("https-cipher-suites", String.class)
.category(OptionCategory.HTTP)
.description("The cipher suites to use. If none is given, a reasonable default is selected.")
.build();
public static final Option HTTPS_PROTOCOLS = new OptionBuilder<>("https-protocols", String.class)
.category(OptionCategory.HTTP)
.description("The list of protocols to explicitly enable.")
.defaultValue("TLSv1.3")
.build();
public static final Option HTTPS_CERTIFICATE_FILE = new OptionBuilder<>("https-certificate-file", File.class)
.category(OptionCategory.HTTP)
.description("The file path to a server certificate or certificate chain in PEM format.")
.build();
public static final Option HTTPS_CERTIFICATE_KEY_FILE = new OptionBuilder<>("https-certificate-key-file", File.class)
.category(OptionCategory.HTTP)
.description("The file path to a private key in PEM format.")
.build();
public static final Option HTTPS_KEY_STORE_FILE = new OptionBuilder<>("https-key-store-file", File.class)
.category(OptionCategory.HTTP)
.description("The key store which holds the certificate information instead of specifying separate files.")
.build();
public static final Option HTTPS_KEY_STORE_PASSWORD = new OptionBuilder<>("https-key-store-password", String.class)
.category(OptionCategory.HTTP)
.description("The password of the key store file.")
.defaultValue("password")
.build();
public static final Option HTTPS_KEY_STORE_TYPE = new OptionBuilder<>("https-key-store-type", String.class)
.category(OptionCategory.HTTP)
.description("The type of the key store file. " +
"If not given, the type is automatically detected based on the file name.")
.build();
public static final Option HTTPS_TRUST_STORE_FILE = new OptionBuilder<>("https-trust-store-file", File.class)
.category(OptionCategory.HTTP)
.description("The trust store which holds the certificate information of the certificates to trust.")
.build();
public static final Option HTTPS_TRUST_STORE_PASSWORD = new OptionBuilder<>("https-trust-store-password", String.class)
.category(OptionCategory.HTTP)
.description("The password of the trust store file.")
.build();
public static final Option HTTPS_TRUST_STORE_TYPE = new OptionBuilder<>("https-trust-store-type", File.class)
.category(OptionCategory.HTTP)
.description("The type of the trust store file. " +
"If not given, the type is automatically detected based on the file name.")
.build();
public static final List<Option<?>> ALL_OPTIONS = new ArrayList<>();
static {
ALL_OPTIONS.add(httpPort);
ALL_OPTIONS.add(HTTP_ENABLED);
ALL_OPTIONS.add(HTTP_HOST);
ALL_OPTIONS.add(HTTP_RELATIVE_PATH);
ALL_OPTIONS.add(HTTP_PORT);
ALL_OPTIONS.add(HTTPS_PORT);
ALL_OPTIONS.add(HTTPS_CLIENT_AUTH);
ALL_OPTIONS.add(HTTPS_CIPHER_SUITES);
ALL_OPTIONS.add(HTTPS_PROTOCOLS);
ALL_OPTIONS.add(HTTPS_CERTIFICATE_FILE);
ALL_OPTIONS.add(HTTPS_CERTIFICATE_KEY_FILE);
ALL_OPTIONS.add(HTTPS_KEY_STORE_FILE);
ALL_OPTIONS.add(HTTPS_KEY_STORE_PASSWORD);
ALL_OPTIONS.add(HTTPS_KEY_STORE_TYPE);
ALL_OPTIONS.add(HTTPS_TRUST_STORE_FILE);
ALL_OPTIONS.add(HTTPS_TRUST_STORE_PASSWORD);
ALL_OPTIONS.add(HTTPS_TRUST_STORE_TYPE);
}
}

View File

@ -0,0 +1,116 @@
package org.keycloak.config;
import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.stream.Collectors;
public class LoggingOptions {
public static final Handler DEFAULT_LOG_HANDLER = Handler.console;
public static final Level DEFAULT_LOG_LEVEL = Level.INFO;
public static final Output DEFAULT_CONSOLE_OUTPUT = Output.DEFAULT;
public static final String DEFAULT_LOG_FILENAME = "keycloak.log";
public static final String DEFAULT_LOG_PATH = "data" + File.separator + "log" + File.separator + DEFAULT_LOG_FILENAME;
public enum Handler {
console,
file;
}
public static final Option log = new OptionBuilder("log", List.class, Handler.class)
.category(OptionCategory.LOGGING)
.description("Enable one or more log handlers in a comma-separated list. Available log handlers are: " + Arrays.stream(Handler.values()).limit(2).map(h -> h.toString()).collect(Collectors.joining(",")))
.defaultValue(DEFAULT_LOG_HANDLER)
.expectedValues(Handler.values())
.build();
public enum Level {
OFF,
FATAL,
ERROR,
WARN,
INFO,
DEBUG,
TRACE,
ALL;
@Override
public String toString() {
return super.toString().toLowerCase(Locale.ROOT);
}
}
public static final Option<Level> LOG_LEVEL = new OptionBuilder<>("log-level", Level.class)
.category(OptionCategory.LOGGING)
.defaultValue(DEFAULT_LOG_LEVEL)
.description("The log level of the root category or a comma-separated list of individual categories and their levels. For the root category, you don't need to specify a category.")
.build();
public enum Output {
DEFAULT,
JSON;
@Override
public String toString() {
return super.toString().toLowerCase(Locale.ROOT);
}
}
public static final Option LOG_CONSOLE_OUTPUT = new OptionBuilder<>("log-console-output", Output.class)
.category(OptionCategory.LOGGING)
.defaultValue(DEFAULT_CONSOLE_OUTPUT)
.description("Set the log output to JSON or default (plain) unstructured logging.")
.expectedValues(Output.values())
.build();
public static final Option LOG_CONSOLE_FORMAT = new OptionBuilder<>("log-console-format", String.class)
.category(OptionCategory.LOGGING)
.description("The format of unstructured console log entries. If the format has spaces in it, escape the value using \"<format>\".")
.defaultValue("%d{yyyy-MM-dd HH:mm:ss,SSS} %-5p [%c] (%t) %s%e%n")
.build();
public static final Option LOG_CONSOLE_COLOR = new OptionBuilder<>("log-console-color", Boolean.class)
.category(OptionCategory.LOGGING)
.description("Enable or disable colors when logging to console.")
.defaultValue(Boolean.FALSE) // :-(
.build();
public static final Option<Boolean> LOG_CONSOLE_ENABLED = new OptionBuilder<>("log-console-enabled", Boolean.class)
.category(OptionCategory.LOGGING)
.runtimes(Collections.emptySet())
.build();
public static final Option LOG_FILE_ENABLED = new OptionBuilder<>("log-file-enabled", Boolean.class)
.category(OptionCategory.LOGGING)
.runtimes(Collections.emptySet())
.build();
public static final Option<File> LOG_FILE = new OptionBuilder<>("log-file", File.class)
.category(OptionCategory.LOGGING)
.description("Set the log file path and filename.")
.defaultValue(new File(DEFAULT_LOG_PATH))
.build();
public static final Option LOG_FILE_FORMAT = new OptionBuilder<>("log-file-format", String.class)
.category(OptionCategory.LOGGING)
.description("Set a format specific to file log entries.")
.defaultValue("%d{yyyy-MM-dd HH:mm:ss,SSS} %-5p [%c] (%t) %s%e%n")
.build();
public static final List<Option<?>> ALL_OPTIONS = new ArrayList<>();
static {
ALL_OPTIONS.add(log);
ALL_OPTIONS.add(LOG_LEVEL);
ALL_OPTIONS.add(LOG_CONSOLE_OUTPUT);
ALL_OPTIONS.add(LOG_CONSOLE_FORMAT);
ALL_OPTIONS.add(LOG_CONSOLE_COLOR);
ALL_OPTIONS.add(LOG_CONSOLE_ENABLED);
ALL_OPTIONS.add(LOG_FILE_ENABLED);
ALL_OPTIONS.add(LOG_FILE);
ALL_OPTIONS.add(LOG_FILE_FORMAT);
}
}

View File

@ -0,0 +1,21 @@
package org.keycloak.config;
import java.util.ArrayList;
import java.util.List;
public class MetricsOptions {
public static final Option METRICS_ENABLED = new OptionBuilder<>("metrics-enabled", Boolean.class)
.category(OptionCategory.METRICS)
.description("If the server should expose metrics. If enabled, metrics are available at the '/metrics' endpoint.")
.buildTime(true)
.defaultValue(Boolean.FALSE)
.expectedValues(Boolean.TRUE, Boolean.FALSE)
.build();
public static final List<Option<?>> ALL_OPTIONS = new ArrayList<>();
static {
ALL_OPTIONS.add(METRICS_ENABLED);
}
}

View File

@ -0,0 +1,19 @@
package org.keycloak.config;
import java.util.List;
import java.util.Optional;
import java.util.Set;
public class MultiOption<T> extends Option<T> {
private final Class auxiliaryType;
public MultiOption(Class type, Class auxiliaryType, String key, OptionCategory category, Set supportedRuntimes, boolean buildTime, String description, Optional defaultValue, List expectedValues) {
super(type, key, category, supportedRuntimes, buildTime, description, defaultValue, expectedValues);
this.auxiliaryType = auxiliaryType;
}
public Class<?> getAuxiliaryType() {
return auxiliaryType;
}
}

View File

@ -19,9 +19,9 @@ public class Option<T> {
private final boolean buildTime;
private final String description;
private final Optional<T> defaultValue;
private final List<T> expectedValues;
private final List<String> expectedValues;
public Option(Class<T> type, String key, OptionCategory category, Set<Runtime> supportedRuntimes, boolean buildTime, String description, Optional<T> defaultValue, List<T> expectedValues) {
public Option(Class<T> type, String key, OptionCategory category, Set<Runtime> supportedRuntimes, boolean buildTime, String description, Optional<T> defaultValue, List<String> expectedValues) {
this.type = type;
this.key = key;
this.category = category;
@ -58,8 +58,21 @@ public class Option<T> {
return defaultValue;
}
public List<T> getExpectedValues() {
public List<String> getExpectedValues() {
return expectedValues;
}
public Option<T> withRuntimeSpecificDefault(T defaultValue) {
return new Option<T>(
this.type,
this.key,
this.category,
this.supportedRuntimes,
this.buildTime,
this.description,
Optional.ofNullable(defaultValue),
this.expectedValues
);
}
}

View File

@ -2,31 +2,50 @@ package org.keycloak.config;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
public class OptionBuilder<T> {
private Class<T> type;
private String key;
private final Class<T> type;
private final Class<T> auxiliaryType;
private final String key;
private OptionCategory category;
private Set<Option.Runtime> supportedRuntimes;
private boolean build;
private String description;
private Optional<T> defaultValue;
private List<T> expectedValues;
private List<String> expectedValues;
public OptionBuilder(String key, Class<T> type) {
this.type = type;
this.auxiliaryType = null;
this.key = key;
category = OptionCategory.GENERAL;
supportedRuntimes = Arrays.stream(Option.Runtime.values()).collect(Collectors.toSet());
build = false;
description = "";
defaultValue = Optional.empty();
description = null;
defaultValue = Boolean.class.equals(type) ? Optional.of((T) Boolean.FALSE) : Optional.empty();
expectedValues = new ArrayList<>();
if (Boolean.class.equals(type)) {
expectedStringValues(Boolean.TRUE.toString(), Boolean.FALSE.toString());
}
}
public OptionBuilder(String key, Class<T> type, Class<T> auxiliaryType) {
this.type = type;
this.auxiliaryType = auxiliaryType;
this.key = key;
category = OptionCategory.GENERAL;
supportedRuntimes = Arrays.stream(Option.Runtime.values()).collect(Collectors.toSet());
build = false;
description = null;
defaultValue = Boolean.class.equals(type) ? Optional.of((T) Boolean.FALSE) : Optional.empty();
expectedValues = new ArrayList<>();
if (Boolean.class.equals(type)) {
expectedStringValues(Boolean.TRUE.toString(), Boolean.FALSE.toString());
}
}
public OptionBuilder<T> category(OptionCategory category) {
@ -66,20 +85,37 @@ public class OptionBuilder<T> {
return this;
}
public OptionBuilder<T> expectedValues(List<T> expected) {
public OptionBuilder<T> expectedStringValues(List<String> expected) {
this.expectedValues.clear();
this.expectedValues.addAll(expected);
return this;
}
public OptionBuilder<T> expectedValues(T ... expected) {
public OptionBuilder<T> expectedStringValues(String ... expected) {
this.expectedValues.clear();
this.expectedValues.addAll(Arrays.asList(expected));
return this;
}
public OptionBuilder<T> expectedValues(List<T> expected) {
this.expectedValues.clear();
this.expectedValues.addAll(expected.stream().map(v -> v.toString()).collect(Collectors.toList()));
return this;
}
public OptionBuilder<T> expectedValues(T ... expected) {
this.expectedValues.clear();
this.expectedValues.addAll(Arrays.asList(expected).stream().map(v -> v.toString()).collect(Collectors.toList()));
return this;
}
public Option<T> build() {
return new Option<T>(type, key, category, supportedRuntimes, build, description, defaultValue, expectedValues);
if (auxiliaryType != null) {
return new MultiOption<T>(type, auxiliaryType, key, category, supportedRuntimes, build, description, defaultValue, expectedValues);
} else {
return new Option<T>(type, key, category, supportedRuntimes, build, description, defaultValue, expectedValues);
}
}
}

View File

@ -0,0 +1,36 @@
package org.keycloak.config;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class ProxyOptions {
public enum Mode {
none,
edge,
reencrypt,
passthrough;
}
public static final Option<Mode> proxy = new OptionBuilder<>("proxy", Mode.class)
.category(OptionCategory.PROXY)
.description("The proxy address forwarding mode if the server is behind a reverse proxy. " +
"Possible values are: " + String.join(",", Arrays.stream(Mode.values()).skip(1).map(m -> m.name()).collect(Collectors.joining(","))))
.defaultValue(Mode.none)
.expectedValues(Mode.values())
.build();
public static final Option<Boolean> proxyForwardedHost = new OptionBuilder<>("proxy-forwarded-host", Boolean.class)
.category(OptionCategory.PROXY)
.defaultValue(Boolean.FALSE)
.build();
public static final List<Option<?>> ALL_OPTIONS = new ArrayList<>();
static {
ALL_OPTIONS.add(proxy);
ALL_OPTIONS.add(proxyForwardedHost);
}
}

View File

@ -0,0 +1,21 @@
package org.keycloak.config;
import java.util.ArrayList;
import java.util.List;
public class TransactionOptions {
public static final Option<Boolean> TRANSACTION_XA_ENABLED = new OptionBuilder<>("transaction-xa-enabled", Boolean.class)
.category(OptionCategory.TRANSACTION)
.description("Manually override the transaction type. Transaction type XA and the appropriate driver is used by default.")
.buildTime(true)
.defaultValue(Boolean.TRUE)
.expectedValues(Boolean.TRUE, Boolean.FALSE)
.build();
public static final List<Option<?>> ALL_OPTIONS = new ArrayList<>();
static {
ALL_OPTIONS.add(TRANSACTION_XA_ENABLED);
}
}

View File

@ -0,0 +1,56 @@
package org.keycloak.config;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
public class VaultOptions {
public enum Provider {
file,
hashicorp;
}
public static final Option VAULT = new OptionBuilder<>("vault", Provider.class)
.category(OptionCategory.VAULT)
.description("Enables a vault provider.")
.buildTime(true)
.expectedValues(Provider.values())
.build();
public static final Option VAULT_DIR = new OptionBuilder<>("vault-dir", File.class)
.category(OptionCategory.VAULT)
.description("If set, secrets can be obtained by reading the content of files within the given directory.")
.build();
public static final Option VAULT_UNMAPPED = new OptionBuilder<>("vault-", String.class)
.category(OptionCategory.VAULT)
.description("Maps any vault option to their corresponding properties in quarkus-vault extension.")
.runtimes()
.buildTime(true)
.build();
public static final Option VAULT_URL = new OptionBuilder<>("vault-url", String.class)
.category(OptionCategory.VAULT)
.description("The vault server url.")
.runtimes()
.buildTime(true)
.build();
public static final Option VAULT_KV_PATHS = new OptionBuilder("vault-kv-paths", Map.class, String.class)
.category(OptionCategory.VAULT)
.description("A set of one or more key/value paths that should be used when looking up secrets.")
.runtimes()
.build();
public static final List<Option<?>> ALL_OPTIONS = new ArrayList<>();
static {
ALL_OPTIONS.add(VAULT);
ALL_OPTIONS.add(VAULT_DIR);
ALL_OPTIONS.add(VAULT_UNMAPPED);
ALL_OPTIONS.add(VAULT_URL);
ALL_OPTIONS.add(VAULT_KV_PATHS);
}
}

View File

@ -15,17 +15,18 @@
* limitations under the License.
*/
package org.keycloak.quarkus.runtime.storage.database;
import static java.util.Arrays.asList;
package org.keycloak.config.database;
import java.io.File;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import java.util.function.Function;
import static java.util.Arrays.asList;
public final class Database {
private static final Map<String, Vendor> DATABASES = new HashMap<>();
@ -96,7 +97,7 @@ public final class Database {
return DATABASES.keySet().stream().sorted().toArray(String[]::new);
}
private enum Vendor {
public enum Vendor {
H2("h2",
"org.h2.jdbcx.JdbcDataSource",
"org.h2.Driver",
@ -187,5 +188,10 @@ public final class Database {
public boolean isOfKind(String dbKind) {
return databaseKind.equals(dbKind);
}
@Override
public String toString() {
return databaseKind.toLowerCase(Locale.ROOT);
}
}
}

View File

@ -111,7 +111,7 @@
<!-- Keycloak -->
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-common</artifactId>
<artifactId>keycloak-config-api</artifactId>
</dependency>
<!-- Test -->

View File

@ -43,9 +43,11 @@ public class ServerConfigGen {
field.addSingleMemberAnnotation(
ANNOTATION_JSON_PROPERTY,
new StringLiteralExpr(o.getKey()));
field.addSingleMemberAnnotation(
ANNOTATION_JSON_PROPERTY_DESCRIPTION,
new StringLiteralExpr(StringEscapeUtils.escapeJava(o.getDescription())));
if (o.getDescription() != null) {
field.addSingleMemberAnnotation(
ANNOTATION_JSON_PROPERTY_DESCRIPTION,
new StringLiteralExpr(StringEscapeUtils.escapeJava(o.getDescription())));
}
field.createGetter();
field.createSetter();
}

View File

@ -81,7 +81,7 @@ class LiquibaseProcessor {
private void filterImplementations(Class<?> types, String dbKind, Set<ClassInfo> classes) {
if (Database.class.equals(types)) {
// removes unsupported databases
classes.removeIf(classInfo -> !org.keycloak.quarkus.runtime.storage.database.Database.isLiquibaseDatabaseSupported(classInfo.name().toString(), dbKind));
classes.removeIf(classInfo -> !org.keycloak.config.database.Database.isLiquibaseDatabaseSupported(classInfo.name().toString(), dbKind));
}
}
}

View File

@ -47,6 +47,7 @@ import java.util.regex.Pattern;
import java.util.stream.Collectors;
import org.eclipse.microprofile.config.spi.ConfigSource;
import org.keycloak.config.MultiOption;
import org.keycloak.config.OptionCategory;
import org.keycloak.quarkus.runtime.cli.command.Build;
import org.keycloak.quarkus.runtime.cli.command.ImportRealmMixin;
@ -420,6 +421,9 @@ public final class Picocli {
if (mapper.getType() != null) {
optBuilder.type(mapper.getType());
if (mapper.getOption() instanceof MultiOption) {
optBuilder.auxiliaryTypes(((MultiOption<?>) mapper.getOption()).getAuxiliaryType());
}
} else {
optBuilder.type(String.class);
}

View File

@ -1,13 +1,12 @@
package org.keycloak.quarkus.runtime.configuration.mappers;
import java.util.Arrays;
import java.util.function.BiFunction;
import org.keycloak.config.OptionCategory;
import org.keycloak.config.ClusteringOptions;
import org.keycloak.quarkus.runtime.Environment;
import io.smallrye.config.ConfigSourceInterceptorContext;
import static org.keycloak.quarkus.runtime.configuration.mappers.PropertyMapper.fromOption;
final class ClusteringPropertyMappers {
private ClusteringPropertyMappers() {
@ -15,56 +14,38 @@ final class ClusteringPropertyMappers {
public static PropertyMapper[] getClusteringPropertyMappers() {
return new PropertyMapper[] {
builder().from("cache")
.defaultValue("ispn")
.description("Defines the cache mechanism for high-availability. "
+ "By default, a 'ispn' cache is used to create a cluster between multiple server nodes. "
+ "A 'local' cache disables clustering and is intended for development and testing purposes.")
fromOption(ClusteringOptions.CACHE)
.paramLabel("type")
.isBuildTimeProperty(true)
.expectedValues("local", "ispn")
.build(),
builder().from("cache-stack")
fromOption(ClusteringOptions.CACHE_STACK)
.to("kc.spi-connections-infinispan-quarkus-stack")
.description("Define the default stack to use for cluster communication and node discovery. This option only takes effect "
+ "if 'cache' is set to 'ispn'. Default: udp.")
.paramLabel("stack")
.isBuildTimeProperty(true)
.expectedValues(Arrays.asList("tcp", "udp", "kubernetes", "ec2", "azure", "google"))
.build(),
builder().from("cache-config-file")
fromOption(ClusteringOptions.CACHE_CONFIG_FILE)
.mapFrom("cache")
.to("kc.spi-connections-infinispan-quarkus-config-file")
.description("Defines the file from which cache configuration should be loaded from. "
+ "The configuration file is relative to the 'conf/' directory.")
.transformer(new BiFunction<String, ConfigSourceInterceptorContext, String>() {
@Override
public String apply(String value, ConfigSourceInterceptorContext context) {
if ("local".equals(value)) {
return "cache-local.xml";
} else if ("ispn".equals(value)) {
return "cache-ispn.xml";
}
String pathPrefix;
String homeDir = Environment.getHomeDir();
if (homeDir == null) {
pathPrefix = "";
} else {
pathPrefix = homeDir + "/conf/";
}
return pathPrefix + value;
}
})
.transformer(ClusteringPropertyMappers::resolveConfigFile)
.paramLabel("file")
.isBuildTimeProperty(true)
.build()
};
}
private static PropertyMapper.Builder builder() {
return PropertyMapper.builder(OptionCategory.CLUSTERING);
private static String resolveConfigFile(String value, ConfigSourceInterceptorContext context) {
if ("local".equals(value)) {
return "cache-local.xml";
} else if ("ispn".equals(value)) {
return "cache-ispn.xml";
}
String pathPrefix;
String homeDir = Environment.getHomeDir();
if (homeDir == null) {
pathPrefix = "";
} else {
pathPrefix = homeDir + "/conf/";
}
return pathPrefix + value;
}
}

View File

@ -3,14 +3,14 @@ package org.keycloak.quarkus.runtime.configuration.mappers;
import io.quarkus.datasource.common.runtime.DatabaseKind;
import io.smallrye.config.ConfigSourceInterceptorContext;
import io.smallrye.config.ConfigValue;
import org.keycloak.config.OptionCategory;
import org.keycloak.quarkus.runtime.storage.database.Database;
import org.keycloak.config.DatabaseOptions;
import org.keycloak.config.database.Database;
import java.util.Optional;
import java.util.function.BiFunction;
import static java.util.Arrays.asList;
import static org.keycloak.quarkus.runtime.Messages.invalidDatabaseVendor;
import static org.keycloak.quarkus.runtime.configuration.mappers.PropertyMapper.fromOption;
import static org.keycloak.quarkus.runtime.integration.QuarkusPlatform.addInitializationException;
final class DatabasePropertyMappers {
@ -19,119 +19,99 @@ final class DatabasePropertyMappers {
public static PropertyMapper[] getDatabasePropertyMappers() {
return new PropertyMapper[] {
builder().from("db-dialect")
fromOption(DatabaseOptions.DB_DIALECT)
.mapFrom("db")
.to("quarkus.hibernate-orm.dialect")
.isBuildTimeProperty(true)
.transformer(DatabasePropertyMappers::transformDialect)
.hidden(true)
.build(),
builder().from("db-driver")
fromOption(DatabaseOptions.DB_DRIVER)
.mapFrom("db")
.defaultValue(Database.getDriver("dev-file", true).get())
.to("quarkus.datasource.jdbc.driver")
.transformer(DatabasePropertyMappers::getXaOrNonXaDriver)
.hidden(true)
.transformer(DatabasePropertyMappers.getXaOrNonXaDriver())
.build(),
builder().from("db").
to("quarkus.datasource.db-kind")
.isBuildTimeProperty(true)
.transformer(toDatabaseKind())
.description("The database vendor. Possible values are: " + String.join(", ", Database.getAliases()))
fromOption(DatabaseOptions.DB)
.to("quarkus.datasource.db-kind")
.transformer(DatabasePropertyMappers::toDatabaseKind)
.paramLabel("vendor")
.expectedValues(asList(Database.getAliases()))
.build(),
builder().from("db-url")
fromOption(DatabaseOptions.DB_URL)
.to("quarkus.datasource.jdbc.url")
.mapFrom("db")
.transformer((value, context) -> Database.getDefaultUrl(value).orElse(value))
.description("The full database JDBC URL. If not provided, a default URL is set based on the selected database vendor. " +
"For instance, if using 'postgres', the default JDBC URL would be 'jdbc:postgresql://localhost/keycloak'. ")
.transformer(DatabasePropertyMappers.getDatabaseUrl())
.paramLabel("jdbc-url")
.build(),
builder().from("db-url-host")
fromOption(DatabaseOptions.DB_URL_HOST)
.to("kc.db-url-host")
.description("Sets the hostname of the default JDBC URL of the chosen vendor. If the `db-url` option is set, this option is ignored.")
.paramLabel("hostname")
.build(),
builder().from("db-url-database")
fromOption(DatabaseOptions.DB_URL_DATABASE)
.to("kc.db-url-database")
.description("Sets the database name of the default JDBC URL of the chosen vendor. If the `db-url` option is set, this option is ignored.")
.paramLabel("dbname")
.build(),
builder().from("db-url-port")
fromOption(DatabaseOptions.DB_URL_PORT)
.to("kc.db-url-port")
.description("Sets the port of the default JDBC URL of the chosen vendor. If the `db-url` option is set, this option is ignored.")
.paramLabel("port")
.build(),
builder().from("db-url-properties")
fromOption(DatabaseOptions.DB_URL_PROPERTIES)
.to("kc.db-url-properties")
.description("Sets the properties of the default JDBC URL of the chosen vendor. If the `db-url` option is set, this option is ignored.")
.paramLabel("properties")
.build(),
builder().from("db-username")
fromOption(DatabaseOptions.DB_USERNAME)
.to("quarkus.datasource.username")
.mapFrom("db")
.transformer(DatabasePropertyMappers::resolveUsername)
.description("The username of the database user.")
.paramLabel("username")
.build(),
builder().from("db-password")
fromOption(DatabaseOptions.DB_PASSWORD)
.to("quarkus.datasource.password")
.mapFrom("db")
.transformer(DatabasePropertyMappers::resolvePassword)
.description("The password of the database user.")
.paramLabel("password")
.isMasked(true)
.build(),
builder().from("db-schema")
fromOption(DatabaseOptions.DB_SCHEMA)
.to("quarkus.hibernate-orm.database.default-schema")
.description("The database schema to be used.")
.paramLabel("schema")
.build(),
builder().from("db-pool-initial-size")
fromOption(DatabaseOptions.DB_POOL_INITIAL_SIZE)
.to("quarkus.datasource.jdbc.initial-size")
.description("The initial size of the connection pool.")
.paramLabel("size")
.build(),
builder().from("db-pool-min-size")
fromOption(DatabaseOptions.DB_POOL_MIN_SIZE)
.to("quarkus.datasource.jdbc.min-size")
.description("The minimal size of the connection pool.")
.paramLabel("size")
.build(),
builder().from("db-pool-max-size")
fromOption(DatabaseOptions.DB_POOL_MAX_SIZE)
.to("quarkus.datasource.jdbc.max-size")
.defaultValue(String.valueOf(100))
.description("The maximum size of the connection pool.")
.paramLabel("size")
.build()
};
}
private static String getXaOrNonXaDriver(String db, ConfigSourceInterceptorContext context) {
ConfigValue xaEnabledConfigValue = context.proceed("kc.transaction-xa-enabled");
boolean isXaEnabled = xaEnabledConfigValue == null || Boolean.parseBoolean(xaEnabledConfigValue.getValue());
return Database.getDriver(db, isXaEnabled).orElse(db);
private static BiFunction<String, ConfigSourceInterceptorContext, String> getDatabaseUrl() {
return (s, c) -> Database.getDefaultUrl(s).orElse(s);
}
private static BiFunction<String, ConfigSourceInterceptorContext, String> toDatabaseKind() {
return (db, context) -> {
Optional<String> databaseKind = Database.getDatabaseKind(db);
private static BiFunction<String, ConfigSourceInterceptorContext, String> getXaOrNonXaDriver() {
return (String db, ConfigSourceInterceptorContext context) -> {
ConfigValue xaEnabledConfigValue = context.proceed("kc.transaction-xa-enabled");
if (databaseKind.isPresent()) {
return databaseKind.get();
}
boolean isXaEnabled = xaEnabledConfigValue == null || Boolean.parseBoolean(xaEnabledConfigValue.getValue());
addInitializationException(invalidDatabaseVendor(db, Database.getAliases()));
return "h2";
return Database.getDriver(db, isXaEnabled).orElse(db);
};
}
private static <T> PropertyMapper.Builder<T> builder() {
return PropertyMapper.builder(OptionCategory.DATABASE);
private static String toDatabaseKind(String db, ConfigSourceInterceptorContext context) {
Optional<String> databaseKind = Database.getDatabaseKind(db);
if (databaseKind.isPresent()) {
return databaseKind.get();
}
addInitializationException(invalidDatabaseVendor(db, Database.getAliases()));
return "h2";
}
private static String resolveUsername(String value, ConfigSourceInterceptorContext context) {

View File

@ -1,9 +1,8 @@
package org.keycloak.quarkus.runtime.configuration.mappers;
import java.util.ArrayList;
import java.util.List;
import org.keycloak.common.Profile;
import org.keycloak.config.OptionCategory;
import org.keycloak.config.FeatureOptions;
import static org.keycloak.quarkus.runtime.configuration.mappers.PropertyMapper.fromOption;
final class FeaturePropertyMappers {
@ -12,34 +11,13 @@ final class FeaturePropertyMappers {
public static PropertyMapper[] getMappers() {
return new PropertyMapper[] {
builder()
.from("features")
.description("Enables a set of one or more features.")
.expectedValues(getFeatureValues())
fromOption(FeatureOptions.FEATURES)
.paramLabel("feature")
.build(),
builder()
.from("features-disabled")
.expectedValues(getFeatureValues())
fromOption(FeatureOptions.FEATURES_DISABLED)
.paramLabel("feature")
.description("Disables a set of one or more features.")
.build()
};
}
private static List<String> getFeatureValues() {
List<String> features = new ArrayList<>();
for (Profile.Feature value : Profile.Feature.values()) {
features.add(value.name().toLowerCase().replace('_', '-'));
}
features.add(Profile.Type.PREVIEW.name().toLowerCase());
return features;
}
private static PropertyMapper.Builder builder() {
return PropertyMapper.builder(OptionCategory.FEATURE).isBuildTimeProperty(true);
}
}

View File

@ -1,8 +1,8 @@
package org.keycloak.quarkus.runtime.configuration.mappers;
import org.keycloak.config.OptionCategory;
import org.keycloak.config.HealthOptions;
import java.util.Arrays;
import static org.keycloak.quarkus.runtime.configuration.mappers.PropertyMapper.fromOption;
final class HealthPropertyMappers {
@ -11,18 +11,11 @@ final class HealthPropertyMappers {
public static PropertyMapper[] getHealthPropertyMappers() {
return new PropertyMapper[] {
builder().from("health-enabled")
fromOption(HealthOptions.HEALTH_ENABLED)
.to("quarkus.datasource.health.enabled")
.isBuildTimeProperty(true)
.defaultValue(Boolean.FALSE.toString())
.description("If the server should expose health check endpoints. If enabled, health checks are available at the '/health', '/health/ready' and '/health/live' endpoints.")
.paramLabel(Boolean.TRUE + "|" + Boolean.FALSE)
.expectedValues(Arrays.asList(Boolean.TRUE.toString(), Boolean.FALSE.toString()))
.build()
};
}
private static PropertyMapper.Builder builder() {
return PropertyMapper.builder(OptionCategory.HEALTH);
}
}

View File

@ -1,7 +1,8 @@
package org.keycloak.quarkus.runtime.configuration.mappers;
import org.keycloak.config.HostnameOptions;
import org.keycloak.config.OptionCategory;
import static org.keycloak.quarkus.runtime.configuration.mappers.PropertyMapper.fromOption;
final class HostnamePropertyMappers {
@ -9,49 +10,32 @@ final class HostnamePropertyMappers {
public static PropertyMapper[] getHostnamePropertyMappers() {
return new PropertyMapper[] {
builder().from("hostname")
fromOption(HostnameOptions.HOSTNAME)
.to("kc.spi-hostname-default-hostname")
.description("Hostname for the Keycloak server.")
.paramLabel("hostname")
.build(),
builder().from("hostname-admin")
fromOption(HostnameOptions.HOSTNAME_ADMIN)
.to("kc.spi-hostname-default-admin")
.description("The hostname for accessing the administration console. Use this option if you are exposing the administration console using a hostname other than the value set to the 'hostname' option.")
.paramLabel("hostname")
.build(),
builder().from("hostname-strict")
fromOption(HostnameOptions.HOSTNAME_STRICT)
.to("kc.spi-hostname-default-strict")
.description("Disables dynamically resolving the hostname from request headers. Should always be set to true in production, unless proxy verifies the Host header.")
.type(Boolean.class)
.defaultValue(Boolean.TRUE.toString())
.build(),
builder().from("hostname-strict-https")
fromOption(HostnameOptions.HOSTNAME_STRICT_HTTPS)
.to("kc.spi-hostname-default-strict-https")
.description("Forces URLs to use HTTPS. Only needed if proxy does not properly set the X-Forwarded-Proto header.")
.hidden(true)
.defaultValue(Boolean.TRUE.toString())
.type(Boolean.class)
.build(),
builder().from("hostname-strict-backchannel")
fromOption(HostnameOptions.HOSTNAME_STRICT_BACKCHANNEL)
.to("kc.spi-hostname-default-strict-backchannel")
.description("By default backchannel URLs are dynamically resolved from request headers to allow internal and external applications. If all applications use the public URL this option should be enabled.")
.type(Boolean.class)
.build(),
builder().from("hostname-path")
fromOption(HostnameOptions.HOSTNAME_PATH)
.to("kc.spi-hostname-default-path")
.description("This should be set if proxy uses a different context-path for Keycloak.")
.paramLabel("path")
.build(),
builder().from("hostname-port")
fromOption(HostnameOptions.HOSTNAME_PORT)
.to("kc.spi-hostname-default-hostname-port")
.defaultValue("-1")
.description("The port used by the proxy when exposing the hostname. Set this option if the proxy uses a port other than the default HTTP and HTTPS ports.")
.paramLabel("port")
.build()
};
}
private static PropertyMapper.Builder builder() {
return PropertyMapper.builder(OptionCategory.HOSTNAME);
}
}

View File

@ -3,15 +3,12 @@ package org.keycloak.quarkus.runtime.configuration.mappers;
import io.smallrye.config.ConfigSourceInterceptorContext;
import io.smallrye.config.ConfigValue;
import org.keycloak.config.HttpOptions;
import org.keycloak.config.Option;
import org.keycloak.config.OptionCategory;
import org.keycloak.quarkus.runtime.Environment;
import org.keycloak.quarkus.runtime.Messages;
import org.keycloak.quarkus.runtime.configuration.MicroProfileConfigProvider;
import java.io.File;
import java.nio.file.Paths;
import java.util.Arrays;
import static org.keycloak.quarkus.runtime.configuration.mappers.PropertyMapper.fromOption;
import static org.keycloak.quarkus.runtime.configuration.mappers.PropertyMappers.getMapper;
@ -23,103 +20,74 @@ final class HttpPropertyMappers {
public static PropertyMapper[] getHttpPropertyMappers() {
return new PropertyMapper[] {
builder().from("http-enabled")
fromOption(HttpOptions.HTTP_ENABLED)
.to("quarkus.http.insecure-requests")
.defaultValue(Boolean.FALSE.toString())
.transformer(HttpPropertyMappers::getHttpEnabledTransformer)
.description("Enables the HTTP listener.")
.paramLabel(Boolean.TRUE + "|" + Boolean.FALSE)
.expectedValues(Arrays.asList(Boolean.TRUE.toString(), Boolean.FALSE.toString()))
.build(),
builder().from("http-host")
fromOption(HttpOptions.HTTP_HOST)
.to("quarkus.http.host")
.defaultValue("0.0.0.0")
.description("The used HTTP Host.")
.paramLabel("host")
.build(),
builder().from("http-relative-path")
fromOption(HttpOptions.HTTP_RELATIVE_PATH)
.to("quarkus.http.root-path")
.defaultValue("/")
.description("Set the path relative to '/' for serving resources.")
.paramLabel("path")
.isBuildTimeProperty(true)
.build(),
fromOption(HttpOptions.httpPort)
fromOption(HttpOptions.HTTP_PORT)
.to("quarkus.http.port")
.paramLabel("port")
.build(),
builder().from("https-port")
fromOption(HttpOptions.HTTPS_PORT)
.to("quarkus.http.ssl-port")
.defaultValue(String.valueOf(8443))
.description("The used HTTPS port.")
.paramLabel("port")
.build(),
builder().from("https-client-auth")
fromOption(HttpOptions.HTTPS_CLIENT_AUTH)
.to("quarkus.http.ssl.client-auth")
.defaultValue("none")
.description("Configures the server to require/request client authentication. Possible Values: none, request, required.")
.paramLabel("auth")
.expectedValues(Arrays.asList("none", "request", "required"))
.build(),
builder().from("https-cipher-suites")
fromOption(HttpOptions.HTTPS_CIPHER_SUITES)
.to("quarkus.http.ssl.cipher-suites")
.description("The cipher suites to use. If none is given, a reasonable default is selected.")
.paramLabel("ciphers")
.build(),
builder().from("https-protocols")
fromOption(HttpOptions.HTTPS_PROTOCOLS)
.to("quarkus.http.ssl.protocols")
.description("The list of protocols to explicitly enable.")
.paramLabel("protocols")
.defaultValue("TLSv1.3")
.build(),
builder().from("https-certificate-file")
fromOption(HttpOptions.HTTPS_CERTIFICATE_FILE)
.to("quarkus.http.ssl.certificate.file")
.description("The file path to a server certificate or certificate chain in PEM format.")
.paramLabel("file")
.build(),
builder().from("https-certificate-key-file")
fromOption(HttpOptions.HTTPS_CERTIFICATE_KEY_FILE)
.to("quarkus.http.ssl.certificate.key-file")
.description("The file path to a private key in PEM format.")
.paramLabel("file")
.build(),
builder().from("https-key-store-file")
fromOption(HttpOptions.HTTPS_KEY_STORE_FILE
.withRuntimeSpecificDefault(getDefaultKeystorePathValue()))
.to("quarkus.http.ssl.certificate.key-store-file")
.defaultValue(getDefaultKeystorePathValue())
.description("The key store which holds the certificate information instead of specifying separate files.")
.paramLabel("file")
.build(),
builder().from("https-key-store-password")
fromOption(HttpOptions.HTTPS_KEY_STORE_PASSWORD)
.to("quarkus.http.ssl.certificate.key-store-password")
.description("The password of the key store file.")
.defaultValue("password")
.paramLabel("password")
.isMasked(true)
.build(),
builder().from("https-key-store-type")
fromOption(HttpOptions.HTTPS_KEY_STORE_TYPE)
.to("quarkus.http.ssl.certificate.key-store-file-type")
.description("The type of the key store file. " +
"If not given, the type is automatically detected based on the file name.")
.paramLabel("type")
.build(),
builder().from("https-trust-store-file")
fromOption(HttpOptions.HTTPS_TRUST_STORE_FILE)
.to("quarkus.http.ssl.certificate.trust-store-file")
.description("The trust store which holds the certificate information of the certificates to trust.")
.paramLabel("file")
.build(),
builder().from("https-trust-store-password")
fromOption(HttpOptions.HTTPS_TRUST_STORE_PASSWORD)
.to("quarkus.http.ssl.certificate.trust-store-password")
.description("The password of the trust store file.")
.paramLabel("password")
.isMasked(true)
.build(),
builder().from("https-trust-store-type")
fromOption(HttpOptions.HTTPS_TRUST_STORE_TYPE)
.to("quarkus.http.ssl.certificate.trust-store-file-type")
.defaultValue(getDefaultKeystorePathValue())
.description("The type of the trust store file. " +
"If not given, the type is automatically detected based on the file name.")
.paramLabel("type")
.build()
};
}
@ -161,6 +129,5 @@ final class HttpPropertyMappers {
return null;
}
private static <T> PropertyMapper.Builder<T> builder() { return PropertyMapper.<T> builder(OptionCategory.HTTP); }
}

View File

@ -1,132 +1,72 @@
package org.keycloak.quarkus.runtime.configuration.mappers;
import static org.keycloak.quarkus.runtime.configuration.mappers.PropertyMapper.fromOption;
import static org.keycloak.quarkus.runtime.integration.QuarkusPlatform.addInitializationException;
import java.io.File;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
import java.util.function.BiFunction;
import java.util.logging.Level;
import java.util.stream.Collectors;
import org.jboss.logmanager.LogContext;
import org.keycloak.config.OptionCategory;
import org.keycloak.config.LoggingOptions;
import org.keycloak.quarkus.runtime.Messages;
import io.smallrye.config.ConfigSourceInterceptorContext;
public final class LoggingPropertyMappers {
private static final String DEFAULT_LOG_LEVEL = "info";
private static final String DEFAULT_LOG_HANDLER = "console";
private static final String DEFAULT_LOG_FILENAME = "keycloak.log";
public static final String DEFAULT_LOG_PATH = "data" + File.separator + "log" + File.separator + DEFAULT_LOG_FILENAME;
private static final List<String> AVAILABLE_LOG_HANDLERS = List.of(DEFAULT_LOG_HANDLER,"file");
private static final String DEFAULT_CONSOLE_OUTPUT = "default";
private LoggingPropertyMappers(){}
public static PropertyMapper[] getMappers() {
return new PropertyMapper[] {
builder().from("log")
.defaultValue(DEFAULT_LOG_HANDLER)
.description("Enable one or more log handlers in a comma-separated list. Available log handlers are: " + String.join(",", AVAILABLE_LOG_HANDLERS))
fromOption(LoggingOptions.log)
.paramLabel("<handler>")
.expectedValues("console","file","console,file","file,console")
.build(),
builder().from("log-level")
.to("quarkus.log.level")
.transformer(new BiFunction<String, ConfigSourceInterceptorContext, String>() {
@Override
public String apply(String value, ConfigSourceInterceptorContext configSourceInterceptorContext) {
String rootLevel = DEFAULT_LOG_LEVEL;
for (String level : value.split(",")) {
String[] parts = level.split(":");
String category = null;
String categoryLevel;
if (parts.length == 1) {
categoryLevel = parts[0];
} else if (parts.length == 2) {
category = parts[0];
categoryLevel = parts[1];
} else {
addInitializationException(Messages.invalidLogCategoryFormat(level));
return rootLevel;
}
Level levelType;
try {
levelType = toLevel(categoryLevel);
} catch (IllegalArgumentException iae) {
addInitializationException(Messages.invalidLogLevel(categoryLevel));
return rootLevel;
}
if (category == null) {
rootLevel = levelType.getName();
} else {
setCategoryLevel(category, levelType.getName());
}
}
return rootLevel;
}
})
.defaultValue(DEFAULT_LOG_LEVEL)
.description("The log level of the root category or a comma-separated list of individual categories and their levels. For the root category, you don't need to specify a category.")
.paramLabel("category:level")
.build(),
builder().from("log-console-output")
fromOption(LoggingOptions.LOG_CONSOLE_OUTPUT)
.to("quarkus.log.console.json")
.defaultValue(DEFAULT_CONSOLE_OUTPUT)
.description("Set the log output to JSON or default (plain) unstructured logging.")
.paramLabel("default|json")
.expectedValues(DEFAULT_CONSOLE_OUTPUT,"json")
.transformer((value, context) -> {
if(value.equals(DEFAULT_CONSOLE_OUTPUT)) {
if(value.equals(LoggingOptions.DEFAULT_CONSOLE_OUTPUT.name().toLowerCase(Locale.ROOT))) {
return Boolean.FALSE.toString();
}
return Boolean.TRUE.toString();
})
.build(),
builder().from("log-console-format")
fromOption(LoggingOptions.LOG_CONSOLE_FORMAT)
.to("quarkus.log.console.format")
.defaultValue("%d{yyyy-MM-dd HH:mm:ss,SSS} %-5p [%c] (%t) %s%e%n")
.description("The format of unstructured console log entries. If the format has spaces in it, escape the value using \"<format>\".")
.paramLabel("format")
.build(),
builder().from("log-console-color")
fromOption(LoggingOptions.LOG_CONSOLE_COLOR)
.to("quarkus.log.console.color")
.defaultValue(Boolean.FALSE.toString())
.description("Enable or disable colors when logging to console.")
.paramLabel(Boolean.TRUE + "|" + Boolean.FALSE)
.build(),
builder().from("log-console-enabled")
fromOption(LoggingOptions.LOG_CONSOLE_ENABLED)
.mapFrom("log")
.to("quarkus.log.console.enable")
.hidden(true)
.transformer(resolveLogHandler(DEFAULT_LOG_HANDLER))
.transformer(LoggingPropertyMappers.resolveLogHandler(LoggingOptions.DEFAULT_LOG_HANDLER.name()))
.build(),
builder().from("log-file-enabled")
fromOption(LoggingOptions.LOG_FILE_ENABLED)
.mapFrom("log")
.to("quarkus.log.file.enable")
.hidden(true)
.transformer(resolveLogHandler("file"))
.transformer(LoggingPropertyMappers.resolveLogHandler("file"))
.build(),
builder().from("log-file")
fromOption(LoggingOptions.LOG_FILE)
.to("quarkus.log.file.path")
.defaultValue(DEFAULT_LOG_PATH)
.description("Set the log file path and filename.")
.paramLabel("<path>/<file-name>.log")
.transformer(LoggingPropertyMappers::resolveFileLogLocation)
.build(),
builder().from("log-file-format")
fromOption(LoggingOptions.LOG_FILE_FORMAT)
.to("quarkus.log.file.format")
.defaultValue("%d{yyyy-MM-dd HH:mm:ss,SSS} %-5p [%c] (%t) %s%e%n")
.description("Set a format specific to file log entries.")
.paramLabel("<format>")
.build(),
fromOption(LoggingOptions.LOG_LEVEL)
.to("quarkus.log.level")
.transformer(LoggingPropertyMappers::resolveLogLevel)
.paramLabel("category:level")
.build()
};
}
@ -135,7 +75,7 @@ public final class LoggingPropertyMappers {
return (parentValue, context) -> {
//we want to fall back to console to not have nothing shown up when wrong values are set.
String consoleDependantErrorResult = handler.equals(DEFAULT_LOG_HANDLER) ? Boolean.TRUE.toString() : Boolean.FALSE.toString();
String consoleDependantErrorResult = handler.equals(LoggingOptions.DEFAULT_LOG_HANDLER.name()) ? Boolean.TRUE.toString() : Boolean.FALSE.toString();
if(parentValue.isBlank()) {
addInitializationException(Messages.emptyValueForKey("log"));
@ -143,9 +83,10 @@ public final class LoggingPropertyMappers {
}
String[] logHandlerValues = parentValue.split(",");
List<String> availableLogHandlers = Arrays.stream(LoggingOptions.Handler.values()).map(h -> h.name()).collect(Collectors.toList());
if (!AVAILABLE_LOG_HANDLERS.containsAll(List.of(logHandlerValues))) {
addInitializationException(Messages.notRecognizedValueInList("log", parentValue, String.join(",", AVAILABLE_LOG_HANDLERS)));
if (!availableLogHandlers.containsAll(List.of(logHandlerValues))) {
addInitializationException(Messages.notRecognizedValueInList("log", parentValue, String.join(",", availableLogHandlers)));
return consoleDependantErrorResult;
}
@ -160,9 +101,8 @@ public final class LoggingPropertyMappers {
}
private static String resolveFileLogLocation(String value, ConfigSourceInterceptorContext configSourceInterceptorContext) {
if (value.endsWith(File.separator))
{
return value + DEFAULT_LOG_FILENAME;
if (value.endsWith(File.separator)) {
return value + LoggingOptions.DEFAULT_LOG_FILENAME;
}
return value;
@ -176,7 +116,40 @@ public final class LoggingPropertyMappers {
LogContext.getLogContext().getLogger(category).setLevel(toLevel(level));
}
private static <T> PropertyMapper.Builder<T> builder() {
return PropertyMapper.builder(OptionCategory.LOGGING);
private static String resolveLogLevel(String value, ConfigSourceInterceptorContext configSourceInterceptorContext) {
String rootLevel = LoggingOptions.DEFAULT_LOG_LEVEL.name();
for (String level : value.split(",")) {
String[] parts = level.split(":");
String category = null;
String categoryLevel;
if (parts.length == 1) {
categoryLevel = parts[0];
} else if (parts.length == 2) {
category = parts[0];
categoryLevel = parts[1];
} else {
addInitializationException(Messages.invalidLogCategoryFormat(level));
return rootLevel;
}
Level levelType;
try {
levelType = toLevel(categoryLevel);
} catch (IllegalArgumentException iae) {
addInitializationException(Messages.invalidLogLevel(categoryLevel));
return rootLevel;
}
if (category == null) {
rootLevel = levelType.getName();
} else {
setCategoryLevel(category, levelType.getName());
}
}
return rootLevel;
}
}

View File

@ -1,8 +1,8 @@
package org.keycloak.quarkus.runtime.configuration.mappers;
import org.keycloak.config.OptionCategory;
import org.keycloak.config.MetricsOptions;
import java.util.Arrays;
import static org.keycloak.quarkus.runtime.configuration.mappers.PropertyMapper.fromOption;
final class MetricsPropertyMappers {
@ -11,18 +11,11 @@ final class MetricsPropertyMappers {
public static PropertyMapper[] getMetricsPropertyMappers() {
return new PropertyMapper[] {
builder().from("metrics-enabled")
fromOption(MetricsOptions.METRICS_ENABLED)
.to("quarkus.datasource.metrics.enabled")
.isBuildTimeProperty(true)
.defaultValue(Boolean.FALSE.toString())
.description("If the server should expose metrics. If enabled, metrics are available at the '/metrics' endpoint.")
.paramLabel(Boolean.TRUE + "|" + Boolean.FALSE)
.expectedValues(Arrays.asList(Boolean.TRUE.toString(), Boolean.FALSE.toString()))
.build()
};
}
private static PropertyMapper.Builder builder() {
return PropertyMapper.builder(OptionCategory.METRICS);
}
}

View File

@ -22,13 +22,8 @@ import static org.keycloak.quarkus.runtime.configuration.Configuration.OPTION_PA
import static org.keycloak.quarkus.runtime.configuration.Configuration.toCliFormat;
import static org.keycloak.quarkus.runtime.configuration.Configuration.toEnvVarFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.function.BiFunction;
import java.util.stream.Collectors;
@ -42,8 +37,13 @@ import org.keycloak.quarkus.runtime.configuration.MicroProfileConfigProvider;
public class PropertyMapper<T> {
static PropertyMapper IDENTITY = new PropertyMapper(String.class, null, null, Optional.empty(), null, null,
false,null, null, false,Collections.emptyList(),null, true) {
static PropertyMapper IDENTITY = new PropertyMapper(
new OptionBuilder<String>(null, String.class).build(),
null,
null,
null,
null,
false) {
@Override
public ConfigValue getConfigValue(String name, ConfigSourceInterceptorContext context) {
return context.proceed(name);
@ -55,43 +55,20 @@ public class PropertyMapper<T> {
private final BiFunction<String, ConfigSourceInterceptorContext, String> mapper;
private final String mapFrom;
private final boolean mask;
private final List<T> expectedValues;
private final String paramLabel;
private final String envVarFormat;
private String cliFormat;
// Backward compatible constructor
PropertyMapper(Class<T> type, String from, String to, Optional<T> defaultValue, BiFunction<String, ConfigSourceInterceptorContext, String> mapper,
String mapFrom, boolean buildTime, String description, String paramLabel, boolean mask, List<T> expectedValues,
OptionCategory category, boolean hidden) {
Set<Option.Runtime> runtimes = new HashSet<>();
if (!hidden) {
runtimes.add(Option.Runtime.QUARKUS);
}
this.option = new OptionBuilder<T>(MicroProfileConfigProvider.NS_KEYCLOAK_PREFIX + from, type)
.buildTime(buildTime)
.category(category != null ? category : OptionCategory.GENERAL)
.defaultValue(defaultValue)
.description(description)
.expectedValues(expectedValues)
.runtimes(runtimes)
.build();
this.to = to == null ? option.getKey() : to;
PropertyMapper(Option<T> option, String to, BiFunction<String, ConfigSourceInterceptorContext, String> mapper,
String mapFrom, String paramLabel, boolean mask) {
this.option = option;
this.to = to == null ? getFrom() : to;
this.mapper = mapper == null ? PropertyMapper::defaultTransformer : mapper;
this.mapFrom = mapFrom;
this.paramLabel = paramLabel;
this.mask = mask;
this.expectedValues = expectedValues == null ? Collections.emptyList() : expectedValues;
this.cliFormat = toCliFormat(from);
this.envVarFormat = toEnvVarFormat(option.getKey());
}
public static PropertyMapper.Builder builder(String fromProp, String toProp) {
return new PropertyMapper.Builder(fromProp, toProp);
}
public static PropertyMapper.Builder builder(OptionCategory category) {
return new PropertyMapper.Builder(category);
this.cliFormat = toCliFormat(option.getKey());
this.envVarFormat = toEnvVarFormat(getFrom());
}
private static String defaultTransformer(String value, ConfigSourceInterceptorContext context) {
@ -103,7 +80,7 @@ public class PropertyMapper<T> {
}
ConfigValue getConfigValue(String name, ConfigSourceInterceptorContext context) {
String from = this.option.getKey();
String from = getFrom();
if (to != null && to.endsWith(OPTION_PART_SEPARATOR)) {
// in case mapping is based on prefixes instead of full property names
@ -166,16 +143,18 @@ public class PropertyMapper<T> {
return value;
}
public Option<T> getOption() { return this.option; }
public Class<T> getType() { return this.option.getType(); }
public String getFrom() {
return this.option.getKey();
return MicroProfileConfigProvider.NS_KEYCLOAK_PREFIX + this.option.getKey();
}
public String getDescription() { return this.option.getDescription(); }
public List<T> getExpectedValues() {
return expectedValues;
public List<String> getExpectedValues() {
return this.option.getExpectedValues().stream().map(v -> v.toString()).collect(Collectors.toList());
}
public Optional<T> getDefaultValue() {return this.option.getDefaultValue(); }
@ -236,32 +215,15 @@ public class PropertyMapper<T> {
public static class Builder<T> {
private Class<T> type;
private String from;
private final Option<T> option;
private String to;
private T defaultValue;
private BiFunction<String, ConfigSourceInterceptorContext, String> mapper;
private String description;
private String mapFrom = null;
private List<T> expectedValues = new ArrayList<>();
private boolean isBuildTimeProperty = false;
private boolean isMasked = false;
private OptionCategory category = OptionCategory.GENERAL;
private String paramLabel;
private boolean hidden;
public Builder(OptionCategory category) {
this.category = category;
}
public Builder(String fromProp, String toProp) {
this.from = fromProp;
this.to = toProp;
}
public Builder<T> from(String from) {
this.from = from;
return this;
public Builder(Option<T> option) {
this.option = option;
}
public Builder<T> to(String to) {
@ -269,22 +231,11 @@ public class PropertyMapper<T> {
return this;
}
public Builder<T> defaultValue(T defaultValue) {
this.defaultValue = defaultValue;
return this;
}
public Builder<T> transformer(BiFunction<String, ConfigSourceInterceptorContext, String> mapper) {
this.mapper = mapper;
return this;
}
public Builder<T> description(String description) {
this.description = description;
return this;
}
public Builder<T> paramLabel(String label) {
this.paramLabel = label;
return this;
@ -295,63 +246,21 @@ public class PropertyMapper<T> {
return this;
}
public Builder<T> expectedValues(List<T> expectedValues) {
this.expectedValues = new ArrayList<>(expectedValues);
return this;
}
public Builder expectedValues(T... expectedValues) {
this.expectedValues = new ArrayList<>(Arrays.asList(expectedValues));
return this;
}
public Builder<T> isBuildTimeProperty(boolean isBuildTime) {
this.isBuildTimeProperty = isBuildTime;
return this;
}
public Builder<T> isMasked(boolean isMasked) {
this.isMasked = isMasked;
return this;
}
public Builder<T> category(OptionCategory category) {
this.category = category;
return this;
}
public Builder<T> type(Class<T> type) {
if (Boolean.class.equals(type)) {
expectedValues((T) Boolean.TRUE.toString(), (T) Boolean.FALSE.toString());
paramLabel(defaultValue == null ? "true|false" : defaultValue.toString());
defaultValue(defaultValue == null ? (T) Boolean.FALSE : defaultValue);
}
this.type = type;
return this;
}
public Builder<T> hidden(boolean hidden) {
this.hidden = hidden;
return this;
}
public PropertyMapper<T> build() {
return new PropertyMapper<T>(type, from, to, Optional.ofNullable(defaultValue), mapper, mapFrom, isBuildTimeProperty, description, paramLabel,
isMasked, expectedValues, category, hidden);
if (paramLabel == null && Boolean.class.equals(option.getType())) {
paramLabel = Boolean.TRUE + "|" + Boolean.FALSE;
}
return new PropertyMapper<T>(option, to, mapper, mapFrom, paramLabel, isMasked);
}
}
public static <T> PropertyMapper.Builder<T> fromOption(Option<T> opt) {
Builder<T> builder = PropertyMapper.builder(opt.getCategory())
.type(opt.getType())
.from(opt.getKey())
.hidden(!opt.getSupportedRuntimes().contains(Option.Runtime.QUARKUS))
.description(opt.getDescription())
.isBuildTimeProperty(opt.isBuildTime())
.expectedValues(opt.getExpectedValues());
if (opt.getDefaultValue().isPresent()) {
builder.defaultValue(opt.getDefaultValue().get());
}
return builder;
return new PropertyMapper.Builder<>(opt);
}
}

View File

@ -1,12 +1,9 @@
package org.keycloak.quarkus.runtime.configuration.mappers;
import io.quarkus.runtime.QuarkusApplication;
import io.quarkus.runtime.configuration.ConfigurationRuntimeConfig;
import io.smallrye.config.ConfigSourceInterceptorContext;
import io.smallrye.config.ConfigValue;
import org.keycloak.quarkus.runtime.Environment;
import org.keycloak.quarkus.runtime.configuration.ConfigArgsConfigSource;
import org.keycloak.quarkus.runtime.configuration.MicroProfileConfigProvider;
import java.util.Collection;
import java.util.HashMap;
@ -173,4 +170,5 @@ public final class PropertyMappers {
return super.put(key, value);
}
}
}

View File

@ -2,62 +2,49 @@ package org.keycloak.quarkus.runtime.configuration.mappers;
import io.smallrye.config.ConfigSourceInterceptorContext;
import java.util.Arrays;
import java.util.function.BiFunction;
import static org.keycloak.quarkus.runtime.configuration.mappers.PropertyMapper.fromOption;
import static org.keycloak.quarkus.runtime.integration.QuarkusPlatform.addInitializationException;
import org.keycloak.config.OptionCategory;
import org.keycloak.config.ProxyOptions;
import org.keycloak.quarkus.runtime.Messages;
final class ProxyPropertyMappers {
private static final String[] possibleProxyValues = {"edge", "reencrypt", "passthrough"};
private ProxyPropertyMappers(){}
public static PropertyMapper[] getProxyPropertyMappers() {
return new PropertyMapper[] {
builder().from("proxy")
fromOption(ProxyOptions.proxy)
.to("quarkus.http.proxy.proxy-address-forwarding")
.defaultValue("none")
.transformer(getValidProxyModeValue())
.expectedValues(Arrays.asList(possibleProxyValues))
.description("The proxy address forwarding mode if the server is behind a reverse proxy. " +
"Possible values are: " + String.join(",",possibleProxyValues))
.transformer(ProxyPropertyMappers::getValidProxyModeValue)
.paramLabel("mode")
.category(OptionCategory.PROXY)
.build(),
builder().to("quarkus.http.proxy.enable-forwarded-host")
fromOption(ProxyOptions.proxyForwardedHost)
.to("quarkus.http.proxy.enable-forwarded-host")
.mapFrom("proxy")
.defaultValue("false")
.transformer(ProxyPropertyMappers::resolveEnableForwardedHost)
.category(OptionCategory.PROXY)
.transformer(ProxyPropertyMappers::getResolveEnableForwardedHost)
.build()
};
}
private static BiFunction<String, ConfigSourceInterceptorContext, String> getValidProxyModeValue() {
return (mode, context) -> {
switch (mode) {
case "none":
return "false";
case "edge":
case "reencrypt":
case "passthrough":
return "true";
default:
addInitializationException(Messages.invalidProxyMode(mode));
return "false";
}
};
private static String getValidProxyModeValue(String mode, ConfigSourceInterceptorContext context) {
switch (mode) {
case "none":
return "false";
case "edge":
case "reencrypt":
case "passthrough":
return "true";
default:
addInitializationException(Messages.invalidProxyMode(mode));
return "false";
}
}
private static String resolveEnableForwardedHost(String proxy, ConfigSourceInterceptorContext context) {
private static String getResolveEnableForwardedHost(String proxy, ConfigSourceInterceptorContext context) {
return String.valueOf(!"none".equals(proxy));
}
private static <T> PropertyMapper.Builder<T> builder() {
return PropertyMapper.builder(OptionCategory.PROXY);
}
}

View File

@ -1,9 +1,9 @@
package org.keycloak.quarkus.runtime.configuration.mappers;
import io.smallrye.config.ConfigSourceInterceptorContext;
import org.keycloak.config.OptionCategory;
import org.keycloak.config.TransactionOptions;
import java.util.Arrays;
import static org.keycloak.quarkus.runtime.configuration.mappers.PropertyMapper.fromOption;
public class TransactionPropertyMappers {
@ -11,15 +11,11 @@ public class TransactionPropertyMappers {
public static PropertyMapper[] getTransactionPropertyMappers() {
return new PropertyMapper[] {
builder().from("transaction-xa-enabled")
fromOption(TransactionOptions.TRANSACTION_XA_ENABLED)
.to("quarkus.datasource.jdbc.transactions")
.defaultValue(Boolean.TRUE.toString())
.description("Manually override the transaction type. Transaction type XA and the appropriate driver is used by default.")
.paramLabel(Boolean.TRUE + "|" + Boolean.FALSE)
.expectedValues(Arrays.asList(Boolean.TRUE.toString(), Boolean.FALSE.toString()))
.isBuildTimeProperty(true)
.transformer(TransactionPropertyMappers::getQuarkusTransactionsValue)
.build(),
.build()
};
}
@ -33,8 +29,4 @@ public class TransactionPropertyMappers {
return "enabled";
}
private static <T> PropertyMapper.Builder<T> builder() {
return PropertyMapper.builder(OptionCategory.TRANSACTION);
}
}

View File

@ -1,6 +1,8 @@
package org.keycloak.quarkus.runtime.configuration.mappers;
import org.keycloak.config.OptionCategory;
import org.keycloak.config.VaultOptions;
import static org.keycloak.quarkus.runtime.configuration.mappers.PropertyMapper.fromOption;
final class VaultPropertyMappers {
@ -9,45 +11,25 @@ final class VaultPropertyMappers {
public static PropertyMapper[] getVaultPropertyMappers() {
return new PropertyMapper[] {
builder()
.from("vault")
.description("Enables a vault provider.")
.expectedValues("file", "hashicorp")
fromOption(VaultOptions.VAULT)
.paramLabel("provider")
.isBuildTimeProperty(true)
.build(),
builder()
.from("vault-dir")
fromOption(VaultOptions.VAULT_DIR)
.to("kc.spi-vault-file-dir")
.description("If set, secrets can be obtained by reading the content of files within the given directory.")
.paramLabel("dir")
.build(),
builder()
.from("vault-")
fromOption(VaultOptions.VAULT_UNMAPPED)
.to("quarkus.vault.")
.description("Maps any vault option to their corresponding properties in quarkus-vault extension.")
.hidden(true)
.isBuildTimeProperty(true)
.build(),
builder()
.from("vault-url")
fromOption(VaultOptions.VAULT_URL)
.to("quarkus.vault.url")
.description("The vault server url.")
.paramLabel("paths")
.hidden(true)
.isBuildTimeProperty(true)
.build(),
builder()
.from("vault-kv-paths")
fromOption(VaultOptions.VAULT_KV_PATHS)
.to("kc.spi-vault-hashicorp-paths")
.description("A set of one or more key/value paths that should be used when looking up secrets.")
.paramLabel("paths")
.hidden(true)
.build()
};
}
private static PropertyMapper.Builder builder() {
return PropertyMapper.builder(OptionCategory.VAULT);
}
}

View File

@ -24,6 +24,7 @@ import com.fasterxml.jackson.core.JsonProcessingException;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.EnabledOnOs;
import org.junit.jupiter.api.condition.OS;
import org.keycloak.config.LoggingOptions;
import org.keycloak.it.junit5.extension.CLIResult;
import org.keycloak.it.junit5.extension.DistributionTest;
import org.keycloak.it.junit5.extension.RawDistOnly;
@ -142,7 +143,7 @@ public class LoggingDistTest {
@EnabledOnOs(value = { OS.LINUX, OS.MAC }, disabledReason = "different shell escaping behaviour on Windows.")
@Launch({ "start-dev", "--log=console,file"})
void testKeycloakLogFileCreated(RawDistRootPath path) {
Path logFilePath = Paths.get(path.getDistRootPath() + File.separator + LoggingPropertyMappers.DEFAULT_LOG_PATH);
Path logFilePath = Paths.get(path.getDistRootPath() + File.separator + LoggingOptions.DEFAULT_LOG_PATH);
File logFile = new File(logFilePath.toString());
assertTrue(logFile.isFile(), "Log file does not exist!");
}
@ -151,7 +152,7 @@ public class LoggingDistTest {
@EnabledOnOs(value = { OS.WINDOWS }, disabledReason = "different shell escaping behaviour on Windows.")
@Launch({ "start-dev", "--log=\"console,file\""})
void testWinKeycloakLogFileCreated(RawDistRootPath path) {
Path logFilePath = Paths.get(path.getDistRootPath() + File.separator + LoggingPropertyMappers.DEFAULT_LOG_PATH);
Path logFilePath = Paths.get(path.getDistRootPath() + File.separator + LoggingOptions.DEFAULT_LOG_PATH);
File logFile = new File(logFilePath.toString());
assertTrue(logFile.isFile(), "Log file does not exist!");
}
@ -160,7 +161,7 @@ public class LoggingDistTest {
@EnabledOnOs(value = { OS.LINUX, OS.MAC }, disabledReason = "different shell escaping behaviour on Windows.")
@Launch({ "start-dev", "--log=console,file", "--log-file-format=\"%d{HH:mm:ss} %-5p [%c{1.}] (%t) %s%e%n\""})
void testFileLoggingHasDifferentFormat(RawDistRootPath path) throws IOException {
Path logFilePath = Paths.get(path.getDistRootPath() + File.separator + LoggingPropertyMappers.DEFAULT_LOG_PATH);
Path logFilePath = Paths.get(path.getDistRootPath() + File.separator + LoggingOptions.DEFAULT_LOG_PATH);
File logFile = new File(logFilePath.toString());
String data = FileUtils.readFileToString(logFile, Charset.defaultCharset());