Conditionally enable and disable CLI options (#25333)
* Conditionally enable and disable CLI options Closes #13113 Signed-off-by: Martin Bartoš <mabartos@redhat.com> * Support for duplicates in config Signed-off-by: Martin Bartoš <mabartos@redhat.com> * Fix rendering config options in docs Fixes #26515 Signed-off-by: Martin Bartoš <mabartos@redhat.com> * Reorder OptionsDistTest Signed-off-by: Martin Bartoš <mabartos@redhat.com> --------- Signed-off-by: Martin Bartoš <mabartos@redhat.com>
This commit is contained in:
parent
40385061f7
commit
e4aa1b5f95
45 changed files with 1250 additions and 489 deletions
|
@ -215,7 +215,7 @@ public class Profile {
|
||||||
|
|
||||||
private static final Logger logger = Logger.getLogger(Profile.class);
|
private static final Logger logger = Logger.getLogger(Profile.class);
|
||||||
|
|
||||||
private static Profile CURRENT;
|
private static volatile Profile CURRENT;
|
||||||
|
|
||||||
private final ProfileName profileName;
|
private final ProfileName profileName;
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<#import "/templates/options.adoc" as opts>
|
<#import "/templates/options.adoc" as opts>
|
||||||
|
|
||||||
<#macro guide title summary priority=999 includedOptions="" preview="" tileVisible="true" previewDiscussionLink="">
|
<#macro guide title summary priority=999 deniedCategories="" includedOptions="" preview="" tileVisible="true" previewDiscussionLink="">
|
||||||
:guide-id: ${id}
|
:guide-id: ${id}
|
||||||
:guide-title: ${title}
|
:guide-title: ${title}
|
||||||
:guide-summary: ${summary}
|
:guide-summary: ${summary}
|
||||||
|
@ -28,6 +28,6 @@ endif::[]
|
||||||
<#if includedOptions?has_content>
|
<#if includedOptions?has_content>
|
||||||
== Relevant options
|
== Relevant options
|
||||||
|
|
||||||
<@opts.list options=ctx.options.getOptions(includedOptions) anchor=false></@opts.list>
|
<@opts.list options=ctx.options.getOptions(includedOptions, deniedCategories) anchor=false></@opts.list>
|
||||||
</#if>
|
</#if>
|
||||||
</#macro>
|
</#macro>
|
|
@ -23,6 +23,10 @@
|
||||||
*Env:* `${option.keyEnv}`
|
*Env:* `${option.keyEnv}`
|
||||||
--
|
--
|
||||||
|
|
||||||
|
<#if option.enabledWhen?has_content>
|
||||||
|
${option.enabledWhen!}
|
||||||
|
</#if>
|
||||||
|
|
||||||
<#if option.deprecated?has_content>
|
<#if option.deprecated?has_content>
|
||||||
<#-- Either mark the whole option as deprecated, or just selected values -->
|
<#-- Either mark the whole option as deprecated, or just selected values -->
|
||||||
<#if !option.deprecated.deprecatedValues?has_content>
|
<#if !option.deprecated.deprecatedValues?has_content>
|
||||||
|
|
|
@ -15,40 +15,54 @@ import org.keycloak.provider.Spi;
|
||||||
import org.keycloak.quarkus.runtime.Providers;
|
import org.keycloak.quarkus.runtime.Providers;
|
||||||
import org.keycloak.quarkus.runtime.configuration.Configuration;
|
import org.keycloak.quarkus.runtime.configuration.Configuration;
|
||||||
import org.keycloak.quarkus.runtime.configuration.mappers.PropertyMappers;
|
import org.keycloak.quarkus.runtime.configuration.mappers.PropertyMappers;
|
||||||
|
import org.keycloak.utils.StringUtil;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
|
import java.util.EnumMap;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.TreeSet;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import java.util.stream.StreamSupport;
|
import java.util.stream.StreamSupport;
|
||||||
|
|
||||||
public class Options {
|
public class Options {
|
||||||
|
|
||||||
private final Map<String, Option> options;
|
private final Map<OptionCategory, Set<Option>> options;
|
||||||
private final Map<String, Map<String, List<Option>>> providerOptions = new LinkedHashMap<>();
|
private final Map<String, Map<String, List<Option>>> providerOptions = new LinkedHashMap<>();
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
public Options() {
|
public Options() {
|
||||||
options = PropertyMappers.getMappers().stream()
|
this.options = new EnumMap<>(OptionCategory.class);
|
||||||
|
PropertyMappers.getMappers().stream()
|
||||||
.filter(m -> !m.isHidden())
|
.filter(m -> !m.isHidden())
|
||||||
.filter(propertyMapper -> Objects.nonNull(propertyMapper.getDescription()))
|
.filter(propertyMapper -> Objects.nonNull(propertyMapper.getDescription()))
|
||||||
.map(m -> new Option(m.getFrom(), m.getCategory(), m.isBuildTime(), null, m.getDescription(), (String) m.getDefaultValue().map(Object::toString).orElse(null), m.getExpectedValues(), (DeprecatedMetadata) m.getDeprecatedMetadata().orElse(null)))
|
.map(m -> new Option(m.getFrom(),
|
||||||
.sorted(Comparator.comparing(Option::getKey))
|
m.getCategory(),
|
||||||
.collect(Collectors.toMap(Option::getKey, o -> o, (o1, o2) -> o1, LinkedHashMap::new)); // Need to ignore duplicate keys??
|
m.isBuildTime(),
|
||||||
|
null,
|
||||||
|
m.getDescription(),
|
||||||
|
m.getDefaultValue().map(Object::toString).orElse(null),
|
||||||
|
m.getExpectedValues(),
|
||||||
|
m.getEnabledWhen().orElse(""),
|
||||||
|
m.getDeprecatedMetadata().orElse(null)))
|
||||||
|
.forEach(o -> options.computeIfAbsent(o.category, k -> new TreeSet<>(Comparator.comparing(Option::getKey))).add(o));
|
||||||
|
|
||||||
ProviderManager providerManager = Providers.getProviderManager(Thread.currentThread().getContextClassLoader());
|
ProviderManager providerManager = Providers.getProviderManager(Thread.currentThread().getContextClassLoader());
|
||||||
|
|
||||||
options.forEach((s, option) -> {
|
options.values()
|
||||||
option.description = option.description.replaceAll("'([^ ]*)'", "`$1`");
|
.stream()
|
||||||
});
|
.flatMap(Collection::stream)
|
||||||
|
.forEach(option -> option.description = option.description.replaceAll("'([^ ]*)'", "`$1`"));
|
||||||
|
|
||||||
for (Spi loadSpi : providerManager.loadSpis().stream().sorted(Comparator.comparing(Spi::getName)).collect(Collectors.toList())) {
|
for (Spi loadSpi : providerManager.loadSpis().stream().sorted(Comparator.comparing(Spi::getName)).toList()) {
|
||||||
for (ProviderFactory providerFactory : providerManager.load(loadSpi).stream().sorted(Comparator.comparing(ProviderFactory::getId)).collect(Collectors.toList())) {
|
for (ProviderFactory<?> providerFactory : providerManager.load(loadSpi).stream().sorted(Comparator.comparing(ProviderFactory::getId)).toList()) {
|
||||||
List<ProviderConfigProperty> configMetadata = providerFactory.getConfigMetadata();
|
List<ProviderConfigProperty> configMetadata = providerFactory.getConfigMetadata();
|
||||||
|
|
||||||
if (configMetadata == null) {
|
if (configMetadata == null) {
|
||||||
|
@ -62,6 +76,7 @@ public class Options {
|
||||||
m.getHelpText(),
|
m.getHelpText(),
|
||||||
m.getDefaultValue() == null ? null : m.getDefaultValue().toString(),
|
m.getDefaultValue() == null ? null : m.getDefaultValue().toString(),
|
||||||
m.getOptions() == null ? Collections.emptyList() : m.getOptions(),
|
m.getOptions() == null ? Collections.emptyList() : m.getOptions(),
|
||||||
|
"",
|
||||||
null))
|
null))
|
||||||
.sorted(Comparator.comparing(Option::getKey)).collect(Collectors.toList());
|
.sorted(Comparator.comparing(Option::getKey)).collect(Collectors.toList());
|
||||||
|
|
||||||
|
@ -88,39 +103,84 @@ public class Options {
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
public Collection<Option> getValues() {
|
|
||||||
return options.values();
|
|
||||||
}
|
|
||||||
|
|
||||||
public Collection<Option> getValues(OptionCategory category) {
|
public Collection<Option> getValues(OptionCategory category) {
|
||||||
return options.values().stream().filter(o -> o.category.equals(category)).collect(Collectors.toList());
|
return options.getOrDefault(category, Collections.emptySet());
|
||||||
}
|
}
|
||||||
|
|
||||||
public Option getOption(String key) {
|
public Option getOption(String key) {
|
||||||
return options.get(key);
|
Set<Option> foundOptions = options.values().stream().flatMap(Collection::stream).filter(f -> f.getKey().equals(key)).collect(Collectors.toSet());
|
||||||
|
if (foundOptions.size() > 1) {
|
||||||
|
final var categories = foundOptions.stream().map(f -> f.category).map(OptionCategory::getHeading).collect(Collectors.joining(","));
|
||||||
|
throw new IllegalArgumentException(String.format("Ambiguous options '%s' with categories: %s\n", key, categories));
|
||||||
|
}
|
||||||
|
return foundOptions.iterator().next();
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<Option> getOptions(String includeOptions) {
|
/**
|
||||||
|
* Get denied categories for guide options
|
||||||
|
* <p>
|
||||||
|
* Used in cases when multiple options can be found under the same name
|
||||||
|
* By providing 'deniedCategories' parameter, we will not search for the option in these categories
|
||||||
|
* <p>
|
||||||
|
* f.e. when we specify {@code includedOptions="hostname"}, we should provide also {@code deniedCategories="hostname_v2"}
|
||||||
|
* In that case, we will use the option from the old hostname provider
|
||||||
|
*
|
||||||
|
* @return denied categories, otherwise an empty set
|
||||||
|
*/
|
||||||
|
public Set<OptionCategory> getDeniedCategories(String deniedCategories) {
|
||||||
|
return Optional.ofNullable(deniedCategories)
|
||||||
|
.filter(StringUtil::isNotBlank)
|
||||||
|
.map(f -> f.split(" "))
|
||||||
|
.map(Arrays::asList)
|
||||||
|
.map(f -> f.stream()
|
||||||
|
.map(g -> {
|
||||||
|
try {
|
||||||
|
return OptionCategory.valueOf(g.toUpperCase());
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
throw new IllegalArgumentException("You have specified wrong category name in the 'deniedCategories' property", e);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect(Collectors.toSet()))
|
||||||
|
.orElseGet(Collections::emptySet);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Option> getOptions(String includeOptions, String deniedCategories) {
|
||||||
final String r = includeOptions.replaceAll("\\.", "\\\\.").replaceAll("\\*", ".*").replace(' ', '|');
|
final String r = includeOptions.replaceAll("\\.", "\\\\.").replaceAll("\\*", ".*").replace(' ', '|');
|
||||||
return this.options.values().stream().filter(o -> o.getKey().matches(r)).collect(Collectors.toList());
|
final Set<OptionCategory> denied = getDeniedCategories(deniedCategories);
|
||||||
|
|
||||||
|
return options.values()
|
||||||
|
.stream()
|
||||||
|
.flatMap(Collection::stream)
|
||||||
|
.filter(f -> !denied.contains(f.category))
|
||||||
|
.filter(f -> f.getKey().matches(r))
|
||||||
|
.toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Map<String, Map<String, List<Option>>> getProviderOptions() {
|
public Map<String, Map<String, List<Option>>> getProviderOptions() {
|
||||||
return providerOptions;
|
return providerOptions;
|
||||||
}
|
}
|
||||||
|
|
||||||
public class Option {
|
public static class Option {
|
||||||
|
|
||||||
private String key;
|
private final String key;
|
||||||
private OptionCategory category;
|
private final OptionCategory category;
|
||||||
private boolean build;
|
private final boolean build;
|
||||||
private String type;
|
private final String type;
|
||||||
private String description;
|
private String description;
|
||||||
private String defaultValue;
|
private final String defaultValue;
|
||||||
private List<String> expectedValues;
|
private List<String> expectedValues;
|
||||||
private DeprecatedMetadata deprecated;
|
private final String enabledWhen;
|
||||||
|
private final DeprecatedMetadata deprecated;
|
||||||
|
|
||||||
public Option(String key, OptionCategory category, boolean build, String type, String description, String defaultValue, Iterable<String> expectedValues, DeprecatedMetadata deprecatedMetadata) {
|
public Option(String key,
|
||||||
|
OptionCategory category,
|
||||||
|
boolean build,
|
||||||
|
String type,
|
||||||
|
String description,
|
||||||
|
String defaultValue,
|
||||||
|
Iterable<String> expectedValues,
|
||||||
|
String enabledWhen,
|
||||||
|
DeprecatedMetadata deprecatedMetadata) {
|
||||||
this.key = key;
|
this.key = key;
|
||||||
this.category = category;
|
this.category = category;
|
||||||
this.build = build;
|
this.build = build;
|
||||||
|
@ -128,6 +188,7 @@ public class Options {
|
||||||
this.description = description;
|
this.description = description;
|
||||||
this.defaultValue = defaultValue;
|
this.defaultValue = defaultValue;
|
||||||
this.expectedValues = StreamSupport.stream(expectedValues.spliterator(), false).collect(Collectors.toList());
|
this.expectedValues = StreamSupport.stream(expectedValues.spliterator(), false).collect(Collectors.toList());
|
||||||
|
this.enabledWhen = enabledWhen;
|
||||||
this.deprecated = deprecatedMetadata;
|
this.deprecated = deprecatedMetadata;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -178,6 +239,11 @@ public class Options {
|
||||||
return expectedValues;
|
return expectedValues;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getEnabledWhen() {
|
||||||
|
if (StringUtil.isBlank(enabledWhen)) return null;
|
||||||
|
return enabledWhen;
|
||||||
|
}
|
||||||
|
|
||||||
public DeprecatedMetadata getDeprecated() {
|
public DeprecatedMetadata getDeprecated() {
|
||||||
return deprecated;
|
return deprecated;
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,14 +44,10 @@ public enum OptionCategory {
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getHeadingBySupportLevel(String heading) {
|
private String getHeadingBySupportLevel(String heading) {
|
||||||
if (this.supportLevel.equals(ConfigSupportLevel.EXPERIMENTAL)){
|
return switch (supportLevel) {
|
||||||
heading = heading + " (Experimental)";
|
case EXPERIMENTAL -> heading + " (Experimental)";
|
||||||
}
|
case PREVIEW -> heading + " (Preview)";
|
||||||
|
default -> heading;
|
||||||
if (this.supportLevel.equals(ConfigSupportLevel.PREVIEW)){
|
};
|
||||||
heading = heading + " (Preview)";
|
|
||||||
}
|
|
||||||
|
|
||||||
return heading;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -68,6 +68,8 @@ import org.keycloak.common.Profile;
|
||||||
import org.keycloak.common.crypto.FipsMode;
|
import org.keycloak.common.crypto.FipsMode;
|
||||||
import org.keycloak.common.util.StreamUtil;
|
import org.keycloak.common.util.StreamUtil;
|
||||||
import org.keycloak.config.DatabaseOptions;
|
import org.keycloak.config.DatabaseOptions;
|
||||||
|
import org.keycloak.config.HealthOptions;
|
||||||
|
import org.keycloak.config.MetricsOptions;
|
||||||
import org.keycloak.config.SecurityOptions;
|
import org.keycloak.config.SecurityOptions;
|
||||||
import org.keycloak.connections.jpa.DefaultJpaConnectionProviderFactory;
|
import org.keycloak.connections.jpa.DefaultJpaConnectionProviderFactory;
|
||||||
import org.keycloak.connections.jpa.JpaConnectionProvider;
|
import org.keycloak.connections.jpa.JpaConnectionProvider;
|
||||||
|
@ -915,11 +917,11 @@ class KeycloakProcessor {
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isMetricsEnabled() {
|
private boolean isMetricsEnabled() {
|
||||||
return Configuration.getOptionalBooleanValue(NS_KEYCLOAK_PREFIX.concat("metrics-enabled")).orElse(false);
|
return Configuration.isTrue(MetricsOptions.METRICS_ENABLED);
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isHealthEnabled() {
|
private boolean isHealthEnabled() {
|
||||||
return Configuration.getOptionalBooleanValue(NS_KEYCLOAK_PREFIX.concat("health-enabled")).orElse(false);
|
return Configuration.isTrue(HealthOptions.HEALTH_ENABLED);
|
||||||
}
|
}
|
||||||
|
|
||||||
static JdbcDataSourceBuildItem getDefaultDataSource(List<JdbcDataSourceBuildItem> jdbcDataSources) {
|
static JdbcDataSourceBuildItem getDefaultDataSource(List<JdbcDataSourceBuildItem> jdbcDataSources) {
|
||||||
|
|
|
@ -38,6 +38,7 @@ import org.apache.commons.lang3.SystemUtils;
|
||||||
import org.keycloak.common.Profile;
|
import org.keycloak.common.Profile;
|
||||||
import org.keycloak.common.profile.PropertiesFileProfileConfigResolver;
|
import org.keycloak.common.profile.PropertiesFileProfileConfigResolver;
|
||||||
import org.keycloak.common.profile.PropertiesProfileConfigResolver;
|
import org.keycloak.common.profile.PropertiesProfileConfigResolver;
|
||||||
|
import org.keycloak.quarkus.runtime.cli.command.AbstractCommand;
|
||||||
import org.keycloak.quarkus.runtime.configuration.PersistedConfigSource;
|
import org.keycloak.quarkus.runtime.configuration.PersistedConfigSource;
|
||||||
|
|
||||||
public final class Environment {
|
public final class Environment {
|
||||||
|
@ -50,6 +51,9 @@ public final class Environment {
|
||||||
public static final String DEV_PROFILE_VALUE = "dev";
|
public static final String DEV_PROFILE_VALUE = "dev";
|
||||||
public static final String PROD_PROFILE_VALUE = "prod";
|
public static final String PROD_PROFILE_VALUE = "prod";
|
||||||
public static final String LAUNCH_MODE = "kc.launch.mode";
|
public static final String LAUNCH_MODE = "kc.launch.mode";
|
||||||
|
|
||||||
|
private static volatile AbstractCommand parsedCommand;
|
||||||
|
|
||||||
private Environment() {}
|
private Environment() {}
|
||||||
|
|
||||||
public static Boolean isRebuild() {
|
public static Boolean isRebuild() {
|
||||||
|
@ -255,4 +259,19 @@ public final class Environment {
|
||||||
|
|
||||||
return profile;
|
return profile;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get parsed AbstractCommand we obtained from the CLI
|
||||||
|
*/
|
||||||
|
public static Optional<AbstractCommand> getParsedCommand() {
|
||||||
|
return Optional.ofNullable(parsedCommand);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isParsedCommand(String commandName) {
|
||||||
|
return getParsedCommand().filter(f -> f.getName().equals(commandName)).isPresent();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setParsedCommand(AbstractCommand command) {
|
||||||
|
Environment.parsedCommand = command;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,6 +31,8 @@ import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import jakarta.enterprise.context.ApplicationScoped;
|
import jakarta.enterprise.context.ApplicationScoped;
|
||||||
|
import org.keycloak.common.profile.ProfileException;
|
||||||
|
import org.keycloak.quarkus.runtime.configuration.mappers.PropertyMappers;
|
||||||
import picocli.CommandLine.ExitCode;
|
import picocli.CommandLine.ExitCode;
|
||||||
|
|
||||||
import io.quarkus.runtime.ApplicationLifecycleManager;
|
import io.quarkus.runtime.ApplicationLifecycleManager;
|
||||||
|
@ -68,10 +70,7 @@ public class KeycloakMain implements QuarkusApplication {
|
||||||
try {
|
try {
|
||||||
cliArgs = Picocli.parseArgs(args);
|
cliArgs = Picocli.parseArgs(args);
|
||||||
} catch (PropertyException e) {
|
} catch (PropertyException e) {
|
||||||
ExecutionExceptionHandler errorHandler = new ExecutionExceptionHandler();
|
handleUsageError(e.getMessage());
|
||||||
PrintWriter errStream = new PrintWriter(System.err, true);
|
|
||||||
errorHandler.error(errStream, e.getMessage(), null);
|
|
||||||
System.exit(ExitCode.USAGE);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -79,25 +78,24 @@ public class KeycloakMain implements QuarkusApplication {
|
||||||
cliArgs = new ArrayList<>(cliArgs);
|
cliArgs = new ArrayList<>(cliArgs);
|
||||||
// default to show help message
|
// default to show help message
|
||||||
cliArgs.add("-h");
|
cliArgs.add("-h");
|
||||||
} else if (isFastStart(cliArgs)) {
|
} else if (isFastStart(cliArgs)) { // fast path for starting the server without bootstrapping CLI
|
||||||
// fast path for starting the server without bootstrapping CLI
|
|
||||||
ExecutionExceptionHandler errorHandler = new ExecutionExceptionHandler();
|
|
||||||
PrintWriter errStream = new PrintWriter(System.err, true);
|
|
||||||
|
|
||||||
if (isDevProfileNotAllowed()) {
|
if (isDevProfileNotAllowed()) {
|
||||||
errorHandler.error(errStream, Messages.devProfileNotAllowedError(Start.NAME), null);
|
handleUsageError(Messages.devProfileNotAllowedError(Start.NAME));
|
||||||
System.exit(ExitCode.USAGE);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
PropertyMappers.sanitizeDisabledMappers();
|
||||||
Picocli.validateConfig(cliArgs, new Start());
|
Picocli.validateConfig(cliArgs, new Start());
|
||||||
} catch (PropertyException e) {
|
} catch (PropertyException | ProfileException e) {
|
||||||
errorHandler.error(errStream, e.getMessage(), null);
|
handleUsageError(e.getMessage(), e.getCause());
|
||||||
System.exit(ExitCode.USAGE);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ExecutionExceptionHandler errorHandler = new ExecutionExceptionHandler();
|
||||||
|
PrintWriter errStream = new PrintWriter(System.err, true);
|
||||||
|
|
||||||
start(errorHandler, errStream, args);
|
start(errorHandler, errStream, args);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
|
@ -107,6 +105,17 @@ public class KeycloakMain implements QuarkusApplication {
|
||||||
parseAndRun(cliArgs);
|
parseAndRun(cliArgs);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void handleUsageError(String message) {
|
||||||
|
handleUsageError(message, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void handleUsageError(String message, Throwable cause) {
|
||||||
|
ExecutionExceptionHandler errorHandler = new ExecutionExceptionHandler();
|
||||||
|
PrintWriter errStream = new PrintWriter(System.err, true);
|
||||||
|
errorHandler.error(errStream, message, cause);
|
||||||
|
System.exit(ExitCode.USAGE);
|
||||||
|
}
|
||||||
|
|
||||||
private static boolean isFastStart(List<String> cliArgs) {
|
private static boolean isFastStart(List<String> cliArgs) {
|
||||||
// 'start --optimized' should start the server without parsing CLI
|
// 'start --optimized' should start the server without parsing CLI
|
||||||
return cliArgs.size() == 2 && cliArgs.get(0).equals(Start.NAME) && cliArgs.stream().anyMatch(OPTIMIZED_BUILD_OPTION_LONG::equals);
|
return cliArgs.size() == 2 && cliArgs.get(0).equals(Start.NAME) && cliArgs.stream().anyMatch(OPTIMIZED_BUILD_OPTION_LONG::equals);
|
||||||
|
|
|
@ -161,8 +161,21 @@ public final class Help extends CommandLine.Help {
|
||||||
PropertyMapper<?> mapper = getMapper(option.longestName());
|
PropertyMapper<?> mapper = getMapper(option.longestName());
|
||||||
|
|
||||||
if (mapper == null) {
|
if (mapper == null) {
|
||||||
|
final var disabledMapper = PropertyMappers.getDisabledMapper(option.longestName());
|
||||||
|
final var isDisabledMapper = disabledMapper.isPresent();
|
||||||
|
|
||||||
|
// Show disabled mappers, which do not have a description when they're enabled
|
||||||
|
final var isEnabledWhenEmpty = isDisabledMapper && disabledMapper.get().getEnabledWhen().isEmpty();
|
||||||
|
if (isEnabledWhenEmpty) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (allOptions && isDisabledMapper) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
// only filter mapped options, defaults to the hidden marker
|
// only filter mapped options, defaults to the hidden marker
|
||||||
return !option.hidden();
|
return !option.hidden() && !isDisabledMapper;
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean isUnsupportedOption = !PropertyMappers.isSupported(mapper);
|
boolean isUnsupportedOption = !PropertyMappers.isSupported(mapper);
|
||||||
|
|
|
@ -17,8 +17,10 @@
|
||||||
|
|
||||||
package org.keycloak.quarkus.runtime.cli;
|
package org.keycloak.quarkus.runtime.cli;
|
||||||
|
|
||||||
|
import static java.lang.String.format;
|
||||||
import static java.util.Optional.ofNullable;
|
import static java.util.Optional.ofNullable;
|
||||||
import static java.util.stream.StreamSupport.stream;
|
import static java.util.stream.StreamSupport.stream;
|
||||||
|
import static org.keycloak.quarkus.runtime.Environment.isRebuild;
|
||||||
import static org.keycloak.quarkus.runtime.Environment.isRebuildCheck;
|
import static org.keycloak.quarkus.runtime.Environment.isRebuildCheck;
|
||||||
import static org.keycloak.quarkus.runtime.Environment.isRebuilt;
|
import static org.keycloak.quarkus.runtime.Environment.isRebuilt;
|
||||||
import static org.keycloak.quarkus.runtime.cli.command.AbstractStartCommand.OPTIMIZED_BUILD_OPTION_LONG;
|
import static org.keycloak.quarkus.runtime.cli.command.AbstractStartCommand.OPTIMIZED_BUILD_OPTION_LONG;
|
||||||
|
@ -40,7 +42,6 @@ import java.io.File;
|
||||||
import java.io.PrintWriter;
|
import java.io.PrintWriter;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.EnumMap;
|
import java.util.EnumMap;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
|
@ -56,11 +57,13 @@ import java.util.stream.Collectors;
|
||||||
|
|
||||||
import org.eclipse.microprofile.config.spi.ConfigSource;
|
import org.eclipse.microprofile.config.spi.ConfigSource;
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
|
import org.keycloak.common.profile.ProfileException;
|
||||||
import org.keycloak.config.DeprecatedMetadata;
|
import org.keycloak.config.DeprecatedMetadata;
|
||||||
import org.keycloak.config.Option;
|
import org.keycloak.config.Option;
|
||||||
import org.keycloak.config.OptionCategory;
|
import org.keycloak.config.OptionCategory;
|
||||||
import org.keycloak.quarkus.runtime.cli.command.AbstractCommand;
|
import org.keycloak.quarkus.runtime.cli.command.AbstractCommand;
|
||||||
import org.keycloak.quarkus.runtime.cli.command.Build;
|
import org.keycloak.quarkus.runtime.cli.command.Build;
|
||||||
|
import org.keycloak.quarkus.runtime.cli.command.HelpAllMixin;
|
||||||
import org.keycloak.quarkus.runtime.cli.command.ImportRealmMixin;
|
import org.keycloak.quarkus.runtime.cli.command.ImportRealmMixin;
|
||||||
import org.keycloak.quarkus.runtime.cli.command.Main;
|
import org.keycloak.quarkus.runtime.cli.command.Main;
|
||||||
import org.keycloak.quarkus.runtime.cli.command.ShowConfig;
|
import org.keycloak.quarkus.runtime.cli.command.ShowConfig;
|
||||||
|
@ -68,6 +71,8 @@ import org.keycloak.quarkus.runtime.cli.command.StartDev;
|
||||||
import org.keycloak.quarkus.runtime.cli.command.Tools;
|
import org.keycloak.quarkus.runtime.cli.command.Tools;
|
||||||
import org.keycloak.quarkus.runtime.configuration.ConfigArgsConfigSource;
|
import org.keycloak.quarkus.runtime.configuration.ConfigArgsConfigSource;
|
||||||
import org.keycloak.quarkus.runtime.configuration.Configuration;
|
import org.keycloak.quarkus.runtime.configuration.Configuration;
|
||||||
|
import org.keycloak.quarkus.runtime.configuration.DisabledMappersInterceptor;
|
||||||
|
import org.keycloak.quarkus.runtime.configuration.KcUnmatchedArgumentException;
|
||||||
import org.keycloak.quarkus.runtime.configuration.PersistedConfigSource;
|
import org.keycloak.quarkus.runtime.configuration.PersistedConfigSource;
|
||||||
import org.keycloak.quarkus.runtime.configuration.PropertyMappingInterceptor;
|
import org.keycloak.quarkus.runtime.configuration.PropertyMappingInterceptor;
|
||||||
import org.keycloak.quarkus.runtime.configuration.QuarkusPropertiesConfigSource;
|
import org.keycloak.quarkus.runtime.configuration.QuarkusPropertiesConfigSource;
|
||||||
|
@ -94,6 +99,7 @@ public final class Picocli {
|
||||||
private static class IncludeOptions {
|
private static class IncludeOptions {
|
||||||
boolean includeRuntime;
|
boolean includeRuntime;
|
||||||
boolean includeBuildTime;
|
boolean includeBuildTime;
|
||||||
|
boolean includeDisabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Picocli() {
|
private Picocli() {
|
||||||
|
@ -101,31 +107,45 @@ public final class Picocli {
|
||||||
|
|
||||||
public static void parseAndRun(List<String> cliArgs) {
|
public static void parseAndRun(List<String> cliArgs) {
|
||||||
CommandLine cmd = createCommandLine(cliArgs);
|
CommandLine cmd = createCommandLine(cliArgs);
|
||||||
|
|
||||||
String[] argArray = cliArgs.toArray(new String[0]);
|
String[] argArray = cliArgs.toArray(new String[0]);
|
||||||
if (Environment.isRebuildCheck()) {
|
|
||||||
int exitCode = 0;
|
|
||||||
try {
|
|
||||||
// process the cli args first to init the config file and perform validation
|
|
||||||
cmd.parseArgs(argArray);
|
|
||||||
exitCode = runReAugmentationIfNeeded(cliArgs, cmd);
|
|
||||||
} catch (ParameterException ex) {
|
|
||||||
try {
|
|
||||||
exitCode = cmd.getParameterExceptionHandler().handleParseException(ex, argArray);
|
|
||||||
} catch (Exception e) {
|
|
||||||
ExecutionExceptionHandler errorHandler = new ExecutionExceptionHandler();
|
|
||||||
errorHandler.error(cmd.getErr(), e.getMessage(), null);
|
|
||||||
exitCode = ex.getCommandLine().getCommandSpec().exitCodeOnInvalidInput();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
exitOnFailure(exitCode, cmd);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
int exitCode = cmd.execute(argArray);
|
try {
|
||||||
|
cmd.parseArgs(argArray); // process the cli args first to init the config file and perform validation
|
||||||
|
|
||||||
|
int exitCode;
|
||||||
|
if (isRebuildCheck()) {
|
||||||
|
exitCode = runReAugmentationIfNeeded(cliArgs, cmd);
|
||||||
|
} else {
|
||||||
|
PropertyMappers.sanitizeDisabledMappers();
|
||||||
|
exitCode = cmd.execute(argArray);
|
||||||
|
}
|
||||||
|
|
||||||
|
exitOnFailure(exitCode, cmd);
|
||||||
|
} catch (ParameterException parEx) {
|
||||||
|
catchParameterException(parEx, cmd, argArray);
|
||||||
|
} catch (ProfileException | PropertyException proEx) {
|
||||||
|
catchProfileException(proEx.getMessage(), proEx.getCause(), cmd);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void catchParameterException(ParameterException parEx, CommandLine cmd, String[] args) {
|
||||||
|
int exitCode;
|
||||||
|
try {
|
||||||
|
exitCode = cmd.getParameterExceptionHandler().handleParseException(parEx, args);
|
||||||
|
} catch (Exception e) {
|
||||||
|
ExecutionExceptionHandler errorHandler = new ExecutionExceptionHandler();
|
||||||
|
errorHandler.error(cmd.getErr(), e.getMessage(), null);
|
||||||
|
exitCode = parEx.getCommandLine().getCommandSpec().exitCodeOnInvalidInput();
|
||||||
|
}
|
||||||
exitOnFailure(exitCode, cmd);
|
exitOnFailure(exitCode, cmd);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void catchProfileException(String message, Throwable cause, CommandLine cmd) {
|
||||||
|
ExecutionExceptionHandler errorHandler = new ExecutionExceptionHandler();
|
||||||
|
errorHandler.error(cmd.getErr(), message, cause);
|
||||||
|
exitOnFailure(CommandLine.ExitCode.USAGE, cmd);
|
||||||
|
}
|
||||||
|
|
||||||
private static void exitOnFailure(int exitCode, CommandLine cmd) {
|
private static void exitOnFailure(int exitCode, CommandLine cmd) {
|
||||||
if (exitCode != cmd.getCommandSpec().exitCodeOnSuccess() && !Environment.isTestLaunchMode() || isRebuildCheck()) {
|
if (exitCode != cmd.getCommandSpec().exitCodeOnSuccess() && !Environment.isTestLaunchMode() || isRebuildCheck()) {
|
||||||
// hard exit wanted, as build failed and no subsequent command should be executed. no quarkus involved.
|
// hard exit wanted, as build failed and no subsequent command should be executed. no quarkus involved.
|
||||||
|
@ -157,6 +177,7 @@ public final class Picocli {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (requiresReAugmentation(currentCommandSpec)) {
|
if (requiresReAugmentation(currentCommandSpec)) {
|
||||||
|
PropertyMappers.sanitizeDisabledMappers();
|
||||||
exitCode = runReAugmentation(cliArgs, cmd);
|
exitCode = runReAugmentation(cliArgs, cmd);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -279,13 +300,27 @@ public final class Picocli {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final boolean disabledMappersInterceptorEnabled = DisabledMappersInterceptor.isEnabled(); // return to the state before the disable
|
||||||
try {
|
try {
|
||||||
PropertyMappingInterceptor.disable(); // we don't want the mapped / transformed properties, we want what the user effectively supplied
|
PropertyMappingInterceptor.disable(); // we don't want the mapped / transformed properties, we want what the user effectively supplied
|
||||||
List<String> ignoredBuildTime = new ArrayList<>();
|
DisabledMappersInterceptor.disable(); // we want all properties, even disabled ones
|
||||||
List<String> ignoredRunTime = new ArrayList<>();
|
|
||||||
Set<String> deprecatedInUse = new HashSet<>();
|
final List<String> ignoredBuildTime = new ArrayList<>();
|
||||||
|
final List<String> ignoredRunTime = new ArrayList<>();
|
||||||
|
final Set<String> disabledBuildTime = new HashSet<>();
|
||||||
|
final Set<String> disabledRunTime = new HashSet<>();
|
||||||
|
final Set<String> deprecatedInUse = new HashSet<>();
|
||||||
|
|
||||||
|
final Set<PropertyMapper<?>> disabledMappers = new HashSet<>();
|
||||||
|
if (options.includeBuildTime) {
|
||||||
|
disabledMappers.addAll(PropertyMappers.getDisabledBuildTimeMappers().values());
|
||||||
|
}
|
||||||
|
if (options.includeRuntime) {
|
||||||
|
disabledMappers.addAll(PropertyMappers.getDisabledRuntimeMappers().values());
|
||||||
|
}
|
||||||
|
|
||||||
for (OptionCategory category : abstractCommand.getOptionCategories()) {
|
for (OptionCategory category : abstractCommand.getOptionCategories()) {
|
||||||
List<PropertyMapper<?>> mappers = new ArrayList<>();
|
List<PropertyMapper<?>> mappers = new ArrayList<>(disabledMappers);
|
||||||
Optional.ofNullable(PropertyMappers.getRuntimeMappers().get(category)).ifPresent(mappers::addAll);
|
Optional.ofNullable(PropertyMappers.getRuntimeMappers().get(category)).ifPresent(mappers::addAll);
|
||||||
Optional.ofNullable(PropertyMappers.getBuildTimeMappers().get(category)).ifPresent(mappers::addAll);
|
Optional.ofNullable(PropertyMappers.getBuildTimeMappers().get(category)).ifPresent(mappers::addAll);
|
||||||
for (PropertyMapper<?> mapper : mappers) {
|
for (PropertyMapper<?> mapper : mappers) {
|
||||||
|
@ -297,6 +332,20 @@ public final class Picocli {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (disabledMappers.contains(mapper)) {
|
||||||
|
if (!PropertyMappers.isDisabledMapper(mapper.getFrom())) {
|
||||||
|
continue; // we found enabled mapper with the same name
|
||||||
|
}
|
||||||
|
final boolean deniedPrintException = mapper.isRunTime() && isRebuild();
|
||||||
|
|
||||||
|
if (PropertyMapper.isCliOption(configValue) && !deniedPrintException) {
|
||||||
|
throw new KcUnmatchedArgumentException(abstractCommand.getCommandLine(), List.of(mapper.getCliFormat()));
|
||||||
|
} else {
|
||||||
|
handleDisabled(mapper.isRunTime() ? disabledRunTime : disabledBuildTime, mapper);
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if (mapper.isBuildTime() && !options.includeBuildTime) {
|
if (mapper.isBuildTime() && !options.includeBuildTime) {
|
||||||
ignoredBuildTime.add(mapper.getFrom());
|
ignoredBuildTime.add(mapper.getFrom());
|
||||||
continue;
|
continue;
|
||||||
|
@ -322,10 +371,17 @@ public final class Picocli {
|
||||||
outputIgnoredProperties(ignoredRunTime, false, logger);
|
outputIgnoredProperties(ignoredRunTime, false, logger);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!disabledBuildTime.isEmpty()) {
|
||||||
|
outputDisabledProperties(disabledBuildTime, true, logger);
|
||||||
|
} else if (!disabledRunTime.isEmpty()) {
|
||||||
|
outputDisabledProperties(disabledRunTime, false, logger);
|
||||||
|
}
|
||||||
|
|
||||||
if (!deprecatedInUse.isEmpty()) {
|
if (!deprecatedInUse.isEmpty()) {
|
||||||
logger.warn("The following used options or option values are DEPRECATED and will be removed in a future release:\n" + String.join("\n", deprecatedInUse) + "\nConsult the Release Notes for details.");
|
logger.warn("The following used options or option values are DEPRECATED and will be removed in a future release:\n" + String.join("\n", deprecatedInUse) + "\nConsult the Release Notes for details.");
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
|
DisabledMappersInterceptor.enable(disabledMappersInterceptorEnabled);
|
||||||
PropertyMappingInterceptor.enable();
|
PropertyMappingInterceptor.enable();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -372,10 +428,36 @@ public final class Picocli {
|
||||||
deprecatedInUse.add(sb.toString());
|
deprecatedInUse.add(sb.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void handleDisabled(Set<String> disabledInUse, PropertyMapper<?> mapper) {
|
||||||
|
String optionName = mapper.getFrom();
|
||||||
|
if (optionName.startsWith(NS_KEYCLOAK_PREFIX)) {
|
||||||
|
optionName = optionName.substring(NS_KEYCLOAK_PREFIX.length());
|
||||||
|
}
|
||||||
|
|
||||||
|
final StringBuilder sb = new StringBuilder("\t- ");
|
||||||
|
sb.append(optionName);
|
||||||
|
|
||||||
|
if (mapper.getEnabledWhen().isPresent()) {
|
||||||
|
final String enabledWhen = mapper.getEnabledWhen().get();
|
||||||
|
sb.append(": ");
|
||||||
|
sb.append(enabledWhen);
|
||||||
|
if (!enabledWhen.endsWith(".")) {
|
||||||
|
sb.append(".");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
disabledInUse.add(sb.toString());
|
||||||
|
}
|
||||||
|
|
||||||
private static void outputIgnoredProperties(List<String> properties, boolean build, Logger logger) {
|
private static void outputIgnoredProperties(List<String> properties, boolean build, Logger logger) {
|
||||||
logger.warn(String.format("The following %s time non-cli options were found, but will be ignored during %s time: %s\n",
|
logger.warn(format("The following %s time non-cli options were found, but will be ignored during %s time: %s\n",
|
||||||
build ? "build" : "run", build ? "run" : "build",
|
build ? "build" : "run", build ? "run" : "build",
|
||||||
properties.stream().collect(Collectors.joining(", "))));
|
String.join(", ", properties)));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void outputDisabledProperties(Set<String> properties, boolean build, Logger logger) {
|
||||||
|
logger.warn(format("The following used %s time options are UNAVAILABLE and will be ignored during %s time:\n %s",
|
||||||
|
build ? "build" : "run", build ? "run" : "build",
|
||||||
|
String.join("\n", properties)));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean hasConfigChanges(CommandLine cmdCommand) {
|
private static boolean hasConfigChanges(CommandLine cmdCommand) {
|
||||||
|
@ -536,6 +618,7 @@ public final class Picocli {
|
||||||
}
|
}
|
||||||
result.includeRuntime = abstractCommand.includeRuntime();
|
result.includeRuntime = abstractCommand.includeRuntime();
|
||||||
result.includeBuildTime = abstractCommand.includeBuildTime();
|
result.includeBuildTime = abstractCommand.includeBuildTime();
|
||||||
|
result.includeDisabled = cliArgs.contains(HelpAllMixin.HELP_ALL_OPTION);
|
||||||
|
|
||||||
if (!result.includeBuildTime && !result.includeRuntime) {
|
if (!result.includeBuildTime && !result.includeRuntime) {
|
||||||
return result;
|
return result;
|
||||||
|
@ -548,14 +631,17 @@ public final class Picocli {
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void addCommandOptions(List<String> cliArgs, CommandLine command) {
|
private static void addCommandOptions(List<String> cliArgs, CommandLine command) {
|
||||||
if (command != null && command.getCommand() instanceof AbstractCommand) {
|
if (command != null && command.getCommand() instanceof AbstractCommand ac) {
|
||||||
IncludeOptions options = getIncludeOptions(cliArgs, command.getCommand(), command.getCommandName());
|
IncludeOptions options = getIncludeOptions(cliArgs, command.getCommand(), command.getCommandName());
|
||||||
|
|
||||||
|
// set current parsed command
|
||||||
|
Environment.setParsedCommand(ac);
|
||||||
|
|
||||||
if (!options.includeBuildTime && !options.includeRuntime) {
|
if (!options.includeBuildTime && !options.includeRuntime) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
addOptionsToCli(command, options.includeBuildTime, options.includeRuntime);
|
addOptionsToCli(command, options);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -571,30 +657,48 @@ public final class Picocli {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void addOptionsToCli(CommandLine commandLine, boolean includeBuildTime, boolean includeRuntime) {
|
private static void addOptionsToCli(CommandLine commandLine, IncludeOptions includeOptions) {
|
||||||
Map<OptionCategory, List<PropertyMapper<?>>> mappers = new EnumMap<>(OptionCategory.class);
|
final Map<OptionCategory, List<PropertyMapper<?>>> mappers = new EnumMap<>(OptionCategory.class);
|
||||||
|
|
||||||
if (includeRuntime) {
|
if (includeOptions.includeRuntime) {
|
||||||
mappers.putAll(PropertyMappers.getRuntimeMappers());
|
mappers.putAll(PropertyMappers.getRuntimeMappers());
|
||||||
|
|
||||||
|
if (includeOptions.includeDisabled) {
|
||||||
|
appendDisabledMappers(mappers, PropertyMappers.getDisabledRuntimeMappers());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (includeBuildTime) {
|
if (includeOptions.includeBuildTime) {
|
||||||
for (Map.Entry<OptionCategory, List<PropertyMapper<?>>> entry : PropertyMappers.getBuildTimeMappers()
|
combinePropertyMappers(mappers, PropertyMappers.getBuildTimeMappers());
|
||||||
.entrySet()) {
|
|
||||||
List<PropertyMapper<?>> result = new ArrayList<>(mappers.getOrDefault(entry.getKey(), Collections.emptyList()));
|
|
||||||
|
|
||||||
result.addAll(entry.getValue());
|
if (includeOptions.includeDisabled) {
|
||||||
|
appendDisabledMappers(mappers, PropertyMappers.getDisabledBuildTimeMappers());
|
||||||
mappers.put(entry.getKey(), result);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
addMappedOptionsToArgGroups(commandLine, mappers);
|
addMappedOptionsToArgGroups(commandLine, mappers);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void appendDisabledMappers(Map<OptionCategory, List<PropertyMapper<?>>> origMappers,
|
||||||
|
Map<String, PropertyMapper<?>> additionalMappers) {
|
||||||
|
for (var pm : additionalMappers.values()) {
|
||||||
|
final List<PropertyMapper<?>> result = origMappers.getOrDefault(pm.getCategory(), new ArrayList<>());
|
||||||
|
result.add(pm);
|
||||||
|
origMappers.put(pm.getCategory(), result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static <T extends Map<OptionCategory, List<PropertyMapper<?>>>> void combinePropertyMappers(T origMappers, T additionalMappers) {
|
||||||
|
for (var entry : additionalMappers.entrySet()) {
|
||||||
|
final List<PropertyMapper<?>> result = origMappers.getOrDefault(entry.getKey(), new ArrayList<>());
|
||||||
|
result.addAll(entry.getValue());
|
||||||
|
origMappers.put(entry.getKey(), result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static void addMappedOptionsToArgGroups(CommandLine commandLine, Map<OptionCategory, List<PropertyMapper<?>>> propertyMappers) {
|
private static void addMappedOptionsToArgGroups(CommandLine commandLine, Map<OptionCategory, List<PropertyMapper<?>>> propertyMappers) {
|
||||||
CommandSpec cSpec = commandLine.getCommandSpec();
|
CommandSpec cSpec = commandLine.getCommandSpec();
|
||||||
for(OptionCategory category : ((AbstractCommand) commandLine.getCommand()).getOptionCategories()) {
|
for (OptionCategory category : ((AbstractCommand) commandLine.getCommand()).getOptionCategories()) {
|
||||||
List<PropertyMapper<?>> mappersInCategory = propertyMappers.get(category);
|
List<PropertyMapper<?>> mappersInCategory = propertyMappers.get(category);
|
||||||
|
|
||||||
if (mappersInCategory == null) {
|
if (mappersInCategory == null) {
|
||||||
|
@ -607,11 +711,13 @@ public final class Picocli {
|
||||||
.order(category.getOrder())
|
.order(category.getOrder())
|
||||||
.validate(false);
|
.validate(false);
|
||||||
|
|
||||||
for (PropertyMapper<?> mapper: mappersInCategory) {
|
final Set<String> alreadyPresentArgs = new HashSet<>();
|
||||||
|
|
||||||
|
for (PropertyMapper<?> mapper : mappersInCategory) {
|
||||||
String name = mapper.getCliFormat();
|
String name = mapper.getCliFormat();
|
||||||
String description = mapper.getDescription();
|
String description = mapper.getDescription();
|
||||||
|
|
||||||
if (description == null || cSpec.optionsMap().containsKey(name) || name.endsWith(OPTION_PART_SEPARATOR)) {
|
if (description == null || cSpec.optionsMap().containsKey(name) || name.endsWith(OPTION_PART_SEPARATOR) || alreadyPresentArgs.contains(name)) {
|
||||||
//when key is already added or has no description, don't add.
|
//when key is already added or has no description, don't add.
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -638,6 +744,8 @@ public final class Picocli {
|
||||||
optBuilder.type(String.class);
|
optBuilder.type(String.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
alreadyPresentArgs.add(name);
|
||||||
|
|
||||||
argGroupBuilder.addArg(optBuilder.build());
|
argGroupBuilder.addArg(optBuilder.build());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -667,6 +775,8 @@ public final class Picocli {
|
||||||
.map(d -> " Default: " + d + ".")
|
.map(d -> " Default: " + d + ".")
|
||||||
.ifPresent(transformedDesc::append);
|
.ifPresent(transformedDesc::append);
|
||||||
|
|
||||||
|
mapper.getEnabledWhen().map(e -> format(" %s.", e)).ifPresent(transformedDesc::append);
|
||||||
|
|
||||||
// only fully deprecated options, not just deprecated values
|
// only fully deprecated options, not just deprecated values
|
||||||
mapper.getDeprecatedMetadata()
|
mapper.getDeprecatedMetadata()
|
||||||
.filter(deprecatedMetadata -> deprecatedMetadata.getDeprecatedValues().isEmpty())
|
.filter(deprecatedMetadata -> deprecatedMetadata.getDeprecatedValues().isEmpty())
|
||||||
|
@ -722,7 +832,7 @@ public final class Picocli {
|
||||||
if (!arg.contains(ARG_KEY_VALUE_SEPARATOR)) {
|
if (!arg.contains(ARG_KEY_VALUE_SEPARATOR)) {
|
||||||
if (!iterator.hasNext()) {
|
if (!iterator.hasNext()) {
|
||||||
if (arg.startsWith("--spi")) {
|
if (arg.startsWith("--spi")) {
|
||||||
throw new PropertyException(String.format("spi argument %s requires a value.", arg));
|
throw new PropertyException(format("spi argument %s requires a value.", arg));
|
||||||
}
|
}
|
||||||
return args;
|
return args;
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,6 +14,10 @@ import picocli.CommandLine.Model.CommandSpec;
|
||||||
import picocli.CommandLine.ParameterException;
|
import picocli.CommandLine.ParameterException;
|
||||||
import picocli.CommandLine.UnmatchedArgumentException;
|
import picocli.CommandLine.UnmatchedArgumentException;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.function.BooleanSupplier;
|
||||||
|
|
||||||
|
import static java.lang.String.format;
|
||||||
import static org.keycloak.quarkus.runtime.cli.command.AbstractStartCommand.OPTIMIZED_BUILD_OPTION_LONG;
|
import static org.keycloak.quarkus.runtime.cli.command.AbstractStartCommand.OPTIMIZED_BUILD_OPTION_LONG;
|
||||||
|
|
||||||
public class ShortErrorMessageHandler implements IParameterExceptionHandler {
|
public class ShortErrorMessageHandler implements IParameterExceptionHandler {
|
||||||
|
@ -23,17 +27,30 @@ public class ShortErrorMessageHandler implements IParameterExceptionHandler {
|
||||||
CommandLine cmd = ex.getCommandLine();
|
CommandLine cmd = ex.getCommandLine();
|
||||||
PrintWriter writer = cmd.getErr();
|
PrintWriter writer = cmd.getErr();
|
||||||
String errorMessage = ex.getMessage();
|
String errorMessage = ex.getMessage();
|
||||||
|
String additionalSuggestion = null;
|
||||||
|
|
||||||
if (ex instanceof UnmatchedArgumentException) {
|
if (ex instanceof UnmatchedArgumentException) {
|
||||||
UnmatchedArgumentException uae = (UnmatchedArgumentException) ex;
|
UnmatchedArgumentException uae = (UnmatchedArgumentException) ex;
|
||||||
|
|
||||||
String[] unmatched = getUnmatchedPartsByOptionSeparator(uae,"=");
|
String[] unmatched = getUnmatchedPartsByOptionSeparator(uae, "=");
|
||||||
|
|
||||||
String cliKey = unmatched[0];
|
String cliKey = unmatched[0];
|
||||||
|
|
||||||
PropertyMapper<?> mapper = PropertyMappers.getMapper(cliKey);
|
PropertyMapper<?> mapper = PropertyMappers.getMapper(cliKey);
|
||||||
|
|
||||||
if (mapper == null || !(cmd.getCommand() instanceof AbstractCommand)) {
|
final boolean isDisabledOption = mapper == null && PropertyMappers.isDisabledMapper(cliKey);
|
||||||
|
final BooleanSupplier isUnknownOption = () -> mapper == null || !(cmd.getCommand() instanceof AbstractCommand);
|
||||||
|
|
||||||
|
if (isDisabledOption) {
|
||||||
|
var enabledWhen = PropertyMappers.getDisabledMapper(cliKey)
|
||||||
|
.map(PropertyMapper::getEnabledWhen)
|
||||||
|
.filter(Optional::isPresent)
|
||||||
|
.map(desc -> format(". %s", desc.get()))
|
||||||
|
.orElse("");
|
||||||
|
|
||||||
|
errorMessage = format("Disabled option: '%s'%s", cliKey, enabledWhen);
|
||||||
|
additionalSuggestion = "Specify '--help-all' to obtain information on all options and their availability.";
|
||||||
|
} else if (isUnknownOption.getAsBoolean()) {
|
||||||
if (cliKey.split("\\s").length > 1) {
|
if (cliKey.split("\\s").length > 1) {
|
||||||
errorMessage = "Option: '" + cliKey + "' is not expected to contain whitespace, please remove any unnecessary quoting/escaping";
|
errorMessage = "Option: '" + cliKey + "' is not expected to contain whitespace, please remove any unnecessary quoting/escaping";
|
||||||
} else {
|
} else {
|
||||||
|
@ -42,12 +59,13 @@ public class ShortErrorMessageHandler implements IParameterExceptionHandler {
|
||||||
} else {
|
} else {
|
||||||
AbstractCommand command = cmd.getCommand();
|
AbstractCommand command = cmd.getCommand();
|
||||||
if (!command.getOptionCategories().contains(mapper.getCategory())) {
|
if (!command.getOptionCategories().contains(mapper.getCategory())) {
|
||||||
errorMessage = "Option: '" + cliKey + "' not valid for command " + cmd.getCommandName();
|
errorMessage = format("Option: '%s' not valid for command %s", cliKey, cmd.getCommandName());
|
||||||
} else {
|
} else {
|
||||||
if (Stream.of(args).anyMatch(OPTIMIZED_BUILD_OPTION_LONG::equals) && mapper.isBuildTime() && Start.NAME.equals(cmd.getCommandName())) {
|
if (Stream.of(args).anyMatch(OPTIMIZED_BUILD_OPTION_LONG::equals) && mapper.isBuildTime() && Start.NAME.equals(cmd.getCommandName())) {
|
||||||
errorMessage = "Build time option: '" + cliKey + "' not usable with pre-built image and --optimized";
|
errorMessage = format("Build time option: '%s' not usable with pre-built image and --optimized", cliKey);
|
||||||
} else {
|
} else {
|
||||||
errorMessage = (mapper.isRunTime()?"Run time":"Build time") + " option: '" + cliKey + "' not usable with " + cmd.getCommandName();
|
final var optionType = mapper.isRunTime() ? "Run time" : "Build time";
|
||||||
|
errorMessage = format("%s option: '%s' not usable with %s", optionType, cliKey, cmd.getCommandName());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -59,6 +77,10 @@ public class ShortErrorMessageHandler implements IParameterExceptionHandler {
|
||||||
CommandSpec spec = cmd.getCommandSpec();
|
CommandSpec spec = cmd.getCommandSpec();
|
||||||
writer.printf("Try '%s --help' for more information on the available options.%n", spec.qualifiedName());
|
writer.printf("Try '%s --help' for more information on the available options.%n", spec.qualifiedName());
|
||||||
|
|
||||||
|
if (additionalSuggestion != null) {
|
||||||
|
writer.println(additionalSuggestion);
|
||||||
|
}
|
||||||
|
|
||||||
return getInvalidInputExitCode(ex, cmd);
|
return getInvalidInputExitCode(ex, cmd);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -69,4 +69,8 @@ public abstract class AbstractCommand {
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract String getName();
|
public abstract String getName();
|
||||||
|
|
||||||
|
public CommandLine getCommandLine() {
|
||||||
|
return spec.commandLine();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,23 +19,20 @@ package org.keycloak.quarkus.runtime.cli.command;
|
||||||
|
|
||||||
import static org.keycloak.config.ClassLoaderOptions.QUARKUS_REMOVED_ARTIFACTS_PROPERTY;
|
import static org.keycloak.config.ClassLoaderOptions.QUARKUS_REMOVED_ARTIFACTS_PROPERTY;
|
||||||
import static org.keycloak.quarkus.runtime.Environment.getHomePath;
|
import static org.keycloak.quarkus.runtime.Environment.getHomePath;
|
||||||
import static org.keycloak.quarkus.runtime.Environment.isDevMode;
|
import static org.keycloak.quarkus.runtime.Environment.isDevProfile;
|
||||||
import static org.keycloak.quarkus.runtime.cli.Picocli.println;
|
import static org.keycloak.quarkus.runtime.cli.Picocli.println;
|
||||||
import static org.keycloak.quarkus.runtime.configuration.ConfigArgsConfigSource.getAllCliArgs;
|
import static org.keycloak.quarkus.runtime.configuration.ConfigArgsConfigSource.getAllCliArgs;
|
||||||
|
|
||||||
import org.keycloak.config.ClassLoaderOptions;
|
|
||||||
import org.keycloak.config.OptionCategory;
|
import org.keycloak.config.OptionCategory;
|
||||||
import org.keycloak.quarkus.runtime.Environment;
|
import org.keycloak.quarkus.runtime.Environment;
|
||||||
import org.keycloak.quarkus.runtime.Messages;
|
import org.keycloak.quarkus.runtime.Messages;
|
||||||
import org.keycloak.quarkus.runtime.configuration.Configuration;
|
import org.keycloak.quarkus.runtime.configuration.Configuration;
|
||||||
import org.keycloak.quarkus.runtime.configuration.MicroProfileConfigProvider;
|
|
||||||
|
|
||||||
import io.quarkus.bootstrap.runner.QuarkusEntryPoint;
|
import io.quarkus.bootstrap.runner.QuarkusEntryPoint;
|
||||||
import io.quarkus.bootstrap.runner.RunnerClassLoader;
|
import io.quarkus.bootstrap.runner.RunnerClassLoader;
|
||||||
|
|
||||||
import io.quarkus.runtime.configuration.ProfileManager;
|
import io.quarkus.runtime.configuration.ProfileManager;
|
||||||
import io.smallrye.config.ConfigValue;
|
import io.smallrye.config.ConfigValue;
|
||||||
import org.keycloak.utils.StringUtil;
|
|
||||||
import picocli.CommandLine;
|
import picocli.CommandLine;
|
||||||
import picocli.CommandLine.Command;
|
import picocli.CommandLine.Command;
|
||||||
|
|
||||||
|
@ -84,7 +81,7 @@ public final class Build extends AbstractCommand implements Runnable {
|
||||||
beforeReaugmentationOnWindows();
|
beforeReaugmentationOnWindows();
|
||||||
QuarkusEntryPoint.main();
|
QuarkusEntryPoint.main();
|
||||||
|
|
||||||
if (!isDevMode()) {
|
if (!isDevProfile()) {
|
||||||
println(spec.commandLine(), "Server configuration updated and persisted. Run the following command to review the configuration:\n");
|
println(spec.commandLine(), "Server configuration updated and persisted. Run the following command to review the configuration:\n");
|
||||||
println(spec.commandLine(), "\t" + Environment.getCommand() + " show-config\n");
|
println(spec.commandLine(), "\t" + Environment.getCommand() + " show-config\n");
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,6 +20,7 @@ package org.keycloak.quarkus.runtime.cli.command;
|
||||||
import static org.keycloak.exportimport.ExportImportConfig.ACTION_EXPORT;
|
import static org.keycloak.exportimport.ExportImportConfig.ACTION_EXPORT;
|
||||||
|
|
||||||
import org.keycloak.config.OptionCategory;
|
import org.keycloak.config.OptionCategory;
|
||||||
|
import org.keycloak.quarkus.runtime.configuration.mappers.ExportPropertyMappers;
|
||||||
import picocli.CommandLine.Command;
|
import picocli.CommandLine.Command;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
@ -42,6 +43,12 @@ public final class Export extends AbstractExportImportCommand implements Runnabl
|
||||||
optionCategory != OptionCategory.IMPORT).collect(Collectors.toList());
|
optionCategory != OptionCategory.IMPORT).collect(Collectors.toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void validateConfig() {
|
||||||
|
ExportPropertyMappers.validateConfig();
|
||||||
|
super.validateConfig();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getName() {
|
public String getName() {
|
||||||
return NAME;
|
return NAME;
|
||||||
|
|
|
@ -23,10 +23,12 @@ import picocli.CommandLine;
|
||||||
|
|
||||||
public final class HelpAllMixin {
|
public final class HelpAllMixin {
|
||||||
|
|
||||||
|
public static final String HELP_ALL_OPTION = "--help-all";
|
||||||
|
|
||||||
@CommandLine.Spec
|
@CommandLine.Spec
|
||||||
private CommandLine.Model.CommandSpec spec;
|
private CommandLine.Model.CommandSpec spec;
|
||||||
|
|
||||||
@CommandLine.Option(names = { "--help-all" }, usageHelp = true, description = "This same help message but with additional options.")
|
@CommandLine.Option(names = {HELP_ALL_OPTION}, usageHelp = true, description = "This same help message but with additional options.")
|
||||||
public void setHelpAll(boolean allOptions) {
|
public void setHelpAll(boolean allOptions) {
|
||||||
Help help = (Help) spec.commandLine().getHelp();
|
Help help = (Help) spec.commandLine().getHelp();
|
||||||
help.setAllOptions(true);
|
help.setAllOptions(true);
|
||||||
|
|
|
@ -20,6 +20,7 @@ package org.keycloak.quarkus.runtime.cli.command;
|
||||||
import static org.keycloak.exportimport.ExportImportConfig.ACTION_IMPORT;
|
import static org.keycloak.exportimport.ExportImportConfig.ACTION_IMPORT;
|
||||||
|
|
||||||
import org.keycloak.config.OptionCategory;
|
import org.keycloak.config.OptionCategory;
|
||||||
|
import org.keycloak.quarkus.runtime.configuration.mappers.ImportPropertyMappers;
|
||||||
import picocli.CommandLine.Command;
|
import picocli.CommandLine.Command;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
@ -42,6 +43,12 @@ public final class Import extends AbstractExportImportCommand implements Runnabl
|
||||||
optionCategory != OptionCategory.EXPORT).collect(Collectors.toList());
|
optionCategory != OptionCategory.EXPORT).collect(Collectors.toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void validateConfig() {
|
||||||
|
ImportPropertyMappers.validateConfig();
|
||||||
|
super.validateConfig();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getName() {
|
public String getName() {
|
||||||
return NAME;
|
return NAME;
|
||||||
|
|
|
@ -28,6 +28,7 @@ import io.smallrye.config.SmallRyeConfig;
|
||||||
|
|
||||||
import org.eclipse.microprofile.config.spi.ConfigProviderResolver;
|
import org.eclipse.microprofile.config.spi.ConfigProviderResolver;
|
||||||
import org.eclipse.microprofile.config.spi.ConfigSource;
|
import org.eclipse.microprofile.config.spi.ConfigSource;
|
||||||
|
import org.keycloak.config.Option;
|
||||||
import org.keycloak.quarkus.runtime.Environment;
|
import org.keycloak.quarkus.runtime.Environment;
|
||||||
import org.keycloak.quarkus.runtime.configuration.mappers.PropertyMapper;
|
import org.keycloak.quarkus.runtime.configuration.mappers.PropertyMapper;
|
||||||
import org.keycloak.quarkus.runtime.configuration.mappers.PropertyMappers;
|
import org.keycloak.quarkus.runtime.configuration.mappers.PropertyMappers;
|
||||||
|
@ -47,6 +48,22 @@ public final class Configuration {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static boolean isTrue(Option<Boolean> option) {
|
||||||
|
return getOptionalBooleanValue(NS_KEYCLOAK_PREFIX + option.getKey()).orElse(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean contains(Option<?> option, String value) {
|
||||||
|
return getOptionalValue(NS_KEYCLOAK_PREFIX + option.getKey())
|
||||||
|
.filter(f -> f.contains(value))
|
||||||
|
.isPresent();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean equals(Option<?> option, String value) {
|
||||||
|
return getOptionalValue(NS_KEYCLOAK_PREFIX + option.getKey())
|
||||||
|
.filter(f -> f.equals(value))
|
||||||
|
.isPresent();
|
||||||
|
}
|
||||||
|
|
||||||
public static synchronized SmallRyeConfig getConfig() {
|
public static synchronized SmallRyeConfig getConfig() {
|
||||||
return (SmallRyeConfig) ConfigProviderResolver.instance().getConfig();
|
return (SmallRyeConfig) ConfigProviderResolver.instance().getConfig();
|
||||||
}
|
}
|
||||||
|
@ -222,13 +239,6 @@ public final class Configuration {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ConfigValue getCurrentBuiltTimeProperty(String name) {
|
public static ConfigValue getCurrentBuiltTimeProperty(String name) {
|
||||||
PersistedConfigSource persistedConfigSource = PersistedConfigSource.getInstance();
|
return PersistedConfigSource.getInstance().runWithDisabled(() -> getConfigValue(name));
|
||||||
|
|
||||||
try {
|
|
||||||
persistedConfigSource.enable(false);
|
|
||||||
return getConfigValue(name);
|
|
||||||
} finally {
|
|
||||||
persistedConfigSource.enable(true);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,91 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2024 Red Hat, Inc. and/or its affiliates
|
||||||
|
* and other contributors as indicated by the @author tags.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.keycloak.quarkus.runtime.configuration;
|
||||||
|
|
||||||
|
import io.smallrye.config.ConfigSourceInterceptor;
|
||||||
|
import io.smallrye.config.ConfigSourceInterceptorContext;
|
||||||
|
import io.smallrye.config.ConfigValue;
|
||||||
|
import io.smallrye.config.Priorities;
|
||||||
|
import jakarta.annotation.Priority;
|
||||||
|
import org.apache.commons.collections4.iterators.FilterIterator;
|
||||||
|
import org.keycloak.quarkus.runtime.configuration.mappers.PropertyMappers;
|
||||||
|
|
||||||
|
import java.util.Iterator;
|
||||||
|
|
||||||
|
import static org.keycloak.quarkus.runtime.configuration.MicroProfileConfigProvider.NS_KEYCLOAK_PREFIX;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>This interceptor is responsible for ignoring disabled Keycloak properties
|
||||||
|
*
|
||||||
|
* <p>This interceptor should execute before the {@link PropertyMappingInterceptor} so that disabled properties
|
||||||
|
* are not mapped to the Quarkus properties.
|
||||||
|
* <p>
|
||||||
|
* The reason for the used priority is to always execute the interceptor before default Application Config Source interceptors
|
||||||
|
* and before the {@link PropertyMappingInterceptor}
|
||||||
|
*/
|
||||||
|
@Priority(Priorities.APPLICATION - 20)
|
||||||
|
public class DisabledMappersInterceptor implements ConfigSourceInterceptor {
|
||||||
|
|
||||||
|
private static final ThreadLocal<Boolean> ENABLED = ThreadLocal.withInitial(() -> false);
|
||||||
|
|
||||||
|
public static void enable() {
|
||||||
|
enable(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void disable() {
|
||||||
|
enable(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void enable(boolean enable) {
|
||||||
|
ENABLED.set(enable);
|
||||||
|
}
|
||||||
|
|
||||||
|
private <T> boolean isDisabledMapper(String property) {
|
||||||
|
return property.startsWith(NS_KEYCLOAK_PREFIX) && PropertyMappers.isDisabledMapper(property);
|
||||||
|
}
|
||||||
|
|
||||||
|
Iterator<String> filterDisabledMappers(Iterator<String> iter) {
|
||||||
|
return new FilterIterator<>(iter, item -> !isDisabledMapper(item));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Iterator<String> iterateNames(ConfigSourceInterceptorContext context) {
|
||||||
|
return filterDisabledMappers(context.iterateNames());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ConfigValue getValue(ConfigSourceInterceptorContext context, String name) {
|
||||||
|
if (isEnabled() && isDisabledMapper(name)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return context.proceed(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isEnabled() {
|
||||||
|
return Boolean.TRUE.equals(ENABLED.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void runWithDisabled(Runnable execution) {
|
||||||
|
try {
|
||||||
|
disable();
|
||||||
|
execution.run();
|
||||||
|
} finally {
|
||||||
|
enable();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,39 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2024 Red Hat, Inc. and/or its affiliates
|
||||||
|
* and other contributors as indicated by the @author tags.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.keycloak.quarkus.runtime.configuration;
|
||||||
|
|
||||||
|
import org.keycloak.quarkus.runtime.configuration.mappers.PropertyMappers;
|
||||||
|
import picocli.CommandLine;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Custom CommandLine.UnmatchedArgumentException with amended suggestions
|
||||||
|
*/
|
||||||
|
public class KcUnmatchedArgumentException extends CommandLine.UnmatchedArgumentException {
|
||||||
|
|
||||||
|
public KcUnmatchedArgumentException(CommandLine commandLine, List<String> args) {
|
||||||
|
super(commandLine, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<String> getSuggestions() {
|
||||||
|
// filter out disabled mappers
|
||||||
|
return super.getSuggestions().stream().filter(f -> !PropertyMappers.isDisabledMapper(f)).toList();
|
||||||
|
}
|
||||||
|
}
|
|
@ -51,7 +51,7 @@ public final class PersistedConfigSource extends PropertiesConfigSource {
|
||||||
* to ignore this config source. Otherwise, default values are not resolved at runtime because the property will be
|
* to ignore this config source. Otherwise, default values are not resolved at runtime because the property will be
|
||||||
* resolved from this config source, if persisted.
|
* resolved from this config source, if persisted.
|
||||||
*/
|
*/
|
||||||
private static final ThreadLocal<Boolean> ENABLED = new ThreadLocal<>();
|
private static final ThreadLocal<Boolean> ENABLED = ThreadLocal.withInitial(() -> true);
|
||||||
|
|
||||||
private PersistedConfigSource() {
|
private PersistedConfigSource() {
|
||||||
super(readProperties(), "", 200);
|
super(readProperties(), "", 200);
|
||||||
|
@ -146,16 +146,24 @@ public final class PersistedConfigSource extends PropertiesConfigSource {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void enable(boolean enabled) {
|
public void enable() {
|
||||||
if (enabled) {
|
ENABLED.set(true);
|
||||||
ENABLED.remove();
|
}
|
||||||
} else {
|
|
||||||
ENABLED.set(enabled);
|
public void disable() {
|
||||||
}
|
ENABLED.set(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isEnabled() {
|
private boolean isEnabled() {
|
||||||
Boolean result = ENABLED.get();
|
return Boolean.TRUE.equals(ENABLED.get());
|
||||||
return result == null ? true : result;
|
}
|
||||||
|
|
||||||
|
public <T> T runWithDisabled(Supplier<T> execution) {
|
||||||
|
try {
|
||||||
|
disable();
|
||||||
|
return execution.get();
|
||||||
|
} finally {
|
||||||
|
enable();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,6 +20,8 @@ import io.smallrye.config.ConfigSourceInterceptor;
|
||||||
import io.smallrye.config.ConfigSourceInterceptorContext;
|
import io.smallrye.config.ConfigSourceInterceptorContext;
|
||||||
import io.smallrye.config.ConfigValue;
|
import io.smallrye.config.ConfigValue;
|
||||||
|
|
||||||
|
import io.smallrye.config.Priorities;
|
||||||
|
import jakarta.annotation.Priority;
|
||||||
import org.apache.commons.collections4.iterators.FilterIterator;
|
import org.apache.commons.collections4.iterators.FilterIterator;
|
||||||
import org.keycloak.common.util.StringPropertyReplacer;
|
import org.keycloak.common.util.StringPropertyReplacer;
|
||||||
import org.keycloak.quarkus.runtime.Environment;
|
import org.keycloak.quarkus.runtime.Environment;
|
||||||
|
@ -42,8 +44,11 @@ import static org.keycloak.quarkus.runtime.Environment.isRebuild;
|
||||||
* from Keycloak (e.g.: database) is mapped to multiple properties in Quarkus.
|
* from Keycloak (e.g.: database) is mapped to multiple properties in Quarkus.
|
||||||
*
|
*
|
||||||
* <p>This interceptor must execute after the {@link io.smallrye.config.ExpressionConfigSourceInterceptor} so that expressions
|
* <p>This interceptor must execute after the {@link io.smallrye.config.ExpressionConfigSourceInterceptor} so that expressions
|
||||||
* are properly resolved before executing this interceptor. Hence, leaving the default priority.
|
* are properly resolved before executing this interceptor.
|
||||||
|
* <p>
|
||||||
|
* The reason for the used priority is to always execute the interceptor before default Application Config Source interceptors
|
||||||
*/
|
*/
|
||||||
|
@Priority(Priorities.APPLICATION - 10)
|
||||||
public class PropertyMappingInterceptor implements ConfigSourceInterceptor {
|
public class PropertyMappingInterceptor implements ConfigSourceInterceptor {
|
||||||
|
|
||||||
private static ThreadLocal<Boolean> disable = new ThreadLocal<>();
|
private static ThreadLocal<Boolean> disable = new ThreadLocal<>();
|
||||||
|
|
|
@ -20,22 +20,30 @@ package org.keycloak.quarkus.runtime.configuration.mappers;
|
||||||
import io.smallrye.config.ConfigSourceInterceptorContext;
|
import io.smallrye.config.ConfigSourceInterceptorContext;
|
||||||
import io.smallrye.config.ConfigValue;
|
import io.smallrye.config.ConfigValue;
|
||||||
import org.keycloak.config.ExportOptions;
|
import org.keycloak.config.ExportOptions;
|
||||||
import picocli.CommandLine;
|
import org.keycloak.config.Option;
|
||||||
|
import org.keycloak.config.OptionBuilder;
|
||||||
|
import org.keycloak.config.OptionCategory;
|
||||||
|
import org.keycloak.quarkus.runtime.cli.PropertyException;
|
||||||
|
import org.keycloak.quarkus.runtime.configuration.Configuration;
|
||||||
|
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
import static org.keycloak.exportimport.ExportImportConfig.PROVIDER;
|
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;
|
import static org.keycloak.quarkus.runtime.configuration.mappers.PropertyMapper.fromOption;
|
||||||
|
|
||||||
final class ExportPropertyMappers {
|
public final class ExportPropertyMappers {
|
||||||
|
private static final String EXPORTER_PROPERTY = "kc.spi-export-exporter";
|
||||||
|
private static final String SINGLE_FILE = "singleFile";
|
||||||
|
private static final String DIR = "dir";
|
||||||
|
|
||||||
private ExportPropertyMappers() {
|
private ExportPropertyMappers() {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static PropertyMapper<?>[] getMappers() {
|
public static PropertyMapper<?>[] getMappers() {
|
||||||
return new PropertyMapper[] {
|
return new PropertyMapper[]{
|
||||||
fromOption(ExportOptions.FILE)
|
fromOption(EXPORTER_PLACEHOLDER)
|
||||||
.to("kc.spi-export-exporter")
|
.to(EXPORTER_PROPERTY)
|
||||||
.transformer(ExportPropertyMappers::transformExporter)
|
.transformer(ExportPropertyMappers::transformExporter)
|
||||||
.paramLabel("file")
|
.paramLabel("file")
|
||||||
.build(),
|
.build(),
|
||||||
|
@ -49,43 +57,69 @@ final class ExportPropertyMappers {
|
||||||
.build(),
|
.build(),
|
||||||
fromOption(ExportOptions.REALM)
|
fromOption(ExportOptions.REALM)
|
||||||
.to("kc.spi-export-single-file-realm-name")
|
.to("kc.spi-export-single-file-realm-name")
|
||||||
|
.isEnabled(ExportPropertyMappers::isSingleFileProvider)
|
||||||
.paramLabel("realm")
|
.paramLabel("realm")
|
||||||
.build(),
|
.build(),
|
||||||
fromOption(ExportOptions.REALM)
|
fromOption(ExportOptions.REALM)
|
||||||
.to("kc.spi-export-dir-realm-name")
|
.to("kc.spi-export-dir-realm-name")
|
||||||
|
.isEnabled(ExportPropertyMappers::isDirProvider)
|
||||||
.paramLabel("realm")
|
.paramLabel("realm")
|
||||||
.build(),
|
.build(),
|
||||||
fromOption(ExportOptions.USERS)
|
fromOption(ExportOptions.USERS)
|
||||||
.to("kc.spi-export-dir-users-export-strategy")
|
.to("kc.spi-export-dir-users-export-strategy")
|
||||||
|
.isEnabled(ExportPropertyMappers::isDirProvider)
|
||||||
.paramLabel("strategy")
|
.paramLabel("strategy")
|
||||||
.build(),
|
.build(),
|
||||||
fromOption(ExportOptions.USERS_PER_FILE)
|
fromOption(ExportOptions.USERS_PER_FILE)
|
||||||
.to("kc.spi-export-dir-users-per-file")
|
.to("kc.spi-export-dir-users-per-file")
|
||||||
|
.isEnabled(ExportPropertyMappers::isDirProvider)
|
||||||
.paramLabel("number")
|
.paramLabel("number")
|
||||||
.build()
|
.build()
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void validateConfig() {
|
||||||
|
if (getOptionalValue(EXPORTER_PROPERTY).isEmpty() && System.getProperty(PROVIDER) == null) {
|
||||||
|
throw new PropertyException("Must specify either --dir or --file options.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final Option<String> EXPORTER_PLACEHOLDER = new OptionBuilder<>("exporter", String.class)
|
||||||
|
.category(OptionCategory.EXPORT)
|
||||||
|
.description("Placeholder for determining export mode")
|
||||||
|
.buildTime(false)
|
||||||
|
.hidden()
|
||||||
|
.build();
|
||||||
|
|
||||||
|
private static boolean isSingleFileProvider() {
|
||||||
|
return isProvider(SINGLE_FILE);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isDirProvider() {
|
||||||
|
return isProvider(DIR);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isProvider(String provider) {
|
||||||
|
return Configuration.getOptionalValue(EXPORTER_PROPERTY)
|
||||||
|
.filter(provider::equals)
|
||||||
|
.isPresent();
|
||||||
|
}
|
||||||
|
|
||||||
private static Optional<String> transformExporter(Optional<String> option, ConfigSourceInterceptorContext context) {
|
private static Optional<String> transformExporter(Optional<String> option, ConfigSourceInterceptorContext context) {
|
||||||
ConfigValue exporter = context.proceed("kc.spi-export-exporter");
|
ConfigValue exporter = context.proceed(EXPORTER_PROPERTY);
|
||||||
if (exporter != null) {
|
if (exporter != null) {
|
||||||
return Optional.of(exporter.getValue());
|
return Optional.of(exporter.getValue());
|
||||||
}
|
}
|
||||||
if (option.isPresent()) {
|
|
||||||
return Optional.of("singleFile");
|
var file = Configuration.getOptionalValue("kc.spi-export-single-file-file").map(f -> SINGLE_FILE);
|
||||||
}
|
var dir = Configuration.getOptionalValue("kc.spi-export-dir-dir")
|
||||||
ConfigValue dirConfigValue = context.proceed("kc.spi-export-dir-dir");
|
.or(() -> Configuration.getOptionalValue("kc.dir"))
|
||||||
if (dirConfigValue != null && dirConfigValue.getValue() != null) {
|
.map(f -> DIR);
|
||||||
return Optional.of("dir");
|
|
||||||
}
|
// Only one option can be specified
|
||||||
ConfigValue dirValue = context.proceed("kc.dir");
|
boolean xor = file.isPresent() ^ dir.isPresent();
|
||||||
if (dirValue != null && dirValue.getValue() != null) {
|
|
||||||
return Optional.of("dir");
|
return xor ? file.or(() -> dir) : Optional.empty();
|
||||||
}
|
|
||||||
if (System.getProperty(PROVIDER) == null) {
|
|
||||||
throw new CommandLine.PicocliException("Must specify either --dir or --file options.");
|
|
||||||
}
|
|
||||||
return Optional.empty();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,23 +20,30 @@ package org.keycloak.quarkus.runtime.configuration.mappers;
|
||||||
import io.smallrye.config.ConfigSourceInterceptorContext;
|
import io.smallrye.config.ConfigSourceInterceptorContext;
|
||||||
import io.smallrye.config.ConfigValue;
|
import io.smallrye.config.ConfigValue;
|
||||||
import org.keycloak.config.ImportOptions;
|
import org.keycloak.config.ImportOptions;
|
||||||
|
import org.keycloak.config.Option;
|
||||||
|
import org.keycloak.config.OptionBuilder;
|
||||||
|
import org.keycloak.config.OptionCategory;
|
||||||
import org.keycloak.exportimport.Strategy;
|
import org.keycloak.exportimport.Strategy;
|
||||||
import picocli.CommandLine;
|
import org.keycloak.quarkus.runtime.cli.PropertyException;
|
||||||
|
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
import static org.keycloak.exportimport.ExportImportConfig.PROVIDER;
|
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;
|
import static org.keycloak.quarkus.runtime.configuration.mappers.PropertyMapper.fromOption;
|
||||||
|
|
||||||
final class ImportPropertyMappers {
|
public final class ImportPropertyMappers {
|
||||||
|
private static final String IMPORTER_PROPERTY = "kc.spi-import-importer";
|
||||||
|
private static final String SINGLE_FILE = "singleFile";
|
||||||
|
private static final String DIR = "dir";
|
||||||
|
|
||||||
private ImportPropertyMappers() {
|
private ImportPropertyMappers() {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static PropertyMapper<?>[] getMappers() {
|
public static PropertyMapper<?>[] getMappers() {
|
||||||
return new PropertyMapper[] {
|
return new PropertyMapper[]{
|
||||||
fromOption(ImportOptions.FILE)
|
fromOption(IMPORTER_PLACEHOLDER)
|
||||||
.to("kc.spi-import-importer")
|
.to(IMPORTER_PROPERTY)
|
||||||
.transformer(ImportPropertyMappers::transformImporter)
|
.transformer(ImportPropertyMappers::transformImporter)
|
||||||
.paramLabel("file")
|
.paramLabel("file")
|
||||||
.build(),
|
.build(),
|
||||||
|
@ -51,14 +58,43 @@ final class ImportPropertyMappers {
|
||||||
fromOption(ImportOptions.OVERRIDE)
|
fromOption(ImportOptions.OVERRIDE)
|
||||||
.to("kc.spi-import-single-file-strategy")
|
.to("kc.spi-import-single-file-strategy")
|
||||||
.transformer(ImportPropertyMappers::transformOverride)
|
.transformer(ImportPropertyMappers::transformOverride)
|
||||||
|
.isEnabled(ImportPropertyMappers::isSingleFileProvider)
|
||||||
.build(),
|
.build(),
|
||||||
fromOption(ImportOptions.OVERRIDE)
|
fromOption(ImportOptions.OVERRIDE)
|
||||||
.to("kc.spi-import-dir-strategy")
|
.to("kc.spi-import-dir-strategy")
|
||||||
.transformer(ImportPropertyMappers::transformOverride)
|
.transformer(ImportPropertyMappers::transformOverride)
|
||||||
|
.isEnabled(ImportPropertyMappers::isDirProvider)
|
||||||
.build(),
|
.build(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void validateConfig() {
|
||||||
|
if (getOptionalValue(IMPORTER_PROPERTY).isEmpty() && System.getProperty(PROVIDER) == null) {
|
||||||
|
throw new PropertyException("Must specify either --dir or --file options.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final Option<String> IMPORTER_PLACEHOLDER = new OptionBuilder<>("importer", String.class)
|
||||||
|
.category(OptionCategory.IMPORT)
|
||||||
|
.description("Placeholder for determining import mode")
|
||||||
|
.buildTime(false)
|
||||||
|
.hidden()
|
||||||
|
.build();
|
||||||
|
|
||||||
|
private static boolean isSingleFileProvider() {
|
||||||
|
return isProvider(SINGLE_FILE);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isDirProvider() {
|
||||||
|
return isProvider(DIR);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isProvider(String provider) {
|
||||||
|
return getOptionalValue(IMPORTER_PROPERTY)
|
||||||
|
.filter(provider::equals)
|
||||||
|
.isPresent();
|
||||||
|
}
|
||||||
|
|
||||||
private static Optional<String> transformOverride(Optional<String> option, ConfigSourceInterceptorContext context) {
|
private static Optional<String> transformOverride(Optional<String> option, ConfigSourceInterceptorContext context) {
|
||||||
if (option.isPresent() && Boolean.parseBoolean(option.get())) {
|
if (option.isPresent() && Boolean.parseBoolean(option.get())) {
|
||||||
return Optional.of(Strategy.OVERWRITE_EXISTING.name());
|
return Optional.of(Strategy.OVERWRITE_EXISTING.name());
|
||||||
|
@ -68,25 +104,20 @@ final class ImportPropertyMappers {
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Optional<String> transformImporter(Optional<String> option, ConfigSourceInterceptorContext context) {
|
private static Optional<String> transformImporter(Optional<String> option, ConfigSourceInterceptorContext context) {
|
||||||
ConfigValue importer = context.proceed("kc.spi-import-importer");
|
ConfigValue importer = context.proceed(IMPORTER_PROPERTY);
|
||||||
if (importer != null) {
|
if (importer != null) {
|
||||||
return Optional.of(importer.getValue());
|
return Optional.of(importer.getValue());
|
||||||
}
|
}
|
||||||
if (option.isPresent()) {
|
|
||||||
return Optional.of("singleFile");
|
var file = getOptionalValue("kc.spi-import-single-file-file").map(f -> SINGLE_FILE);
|
||||||
}
|
var dir = getOptionalValue("kc.spi-import-dir-dir")
|
||||||
ConfigValue dirConfigValue = context.proceed("kc.spi-import-dir-dir");
|
.or(() -> getOptionalValue("kc.dir"))
|
||||||
if (dirConfigValue != null && dirConfigValue.getValue() != null) {
|
.map(f -> DIR);
|
||||||
return Optional.of("dir");
|
|
||||||
}
|
// Only one option can be specified
|
||||||
ConfigValue dirValue = context.proceed("kc.dir");
|
boolean xor = file.isPresent() ^ dir.isPresent();
|
||||||
if (dirConfigValue != null && dirValue.getValue() != null) {
|
|
||||||
return Optional.of("dir");
|
return xor ? file.or(() -> dir) : Optional.empty();
|
||||||
}
|
|
||||||
if (System.getProperty(PROVIDER) == null) {
|
|
||||||
throw new CommandLine.PicocliException("Must specify either --dir or --file options.");
|
|
||||||
}
|
|
||||||
return Optional.empty();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ package org.keycloak.quarkus.runtime.configuration.mappers;
|
||||||
|
|
||||||
import static java.util.Optional.of;
|
import static java.util.Optional.of;
|
||||||
import static org.keycloak.config.LoggingOptions.GELF_ACTIVATED;
|
import static org.keycloak.config.LoggingOptions.GELF_ACTIVATED;
|
||||||
|
import static org.keycloak.quarkus.runtime.configuration.Configuration.isTrue;
|
||||||
import static org.keycloak.quarkus.runtime.configuration.mappers.PropertyMapper.fromOption;
|
import static org.keycloak.quarkus.runtime.configuration.mappers.PropertyMapper.fromOption;
|
||||||
import static org.keycloak.quarkus.runtime.integration.QuarkusPlatform.addInitializationException;
|
import static org.keycloak.quarkus.runtime.integration.QuarkusPlatform.addInitializationException;
|
||||||
|
|
||||||
|
@ -18,10 +19,16 @@ import org.keycloak.config.LoggingOptions;
|
||||||
import org.keycloak.quarkus.runtime.Messages;
|
import org.keycloak.quarkus.runtime.Messages;
|
||||||
|
|
||||||
import io.smallrye.config.ConfigSourceInterceptorContext;
|
import io.smallrye.config.ConfigSourceInterceptorContext;
|
||||||
|
import org.keycloak.quarkus.runtime.configuration.Configuration;
|
||||||
|
|
||||||
public final class LoggingPropertyMappers {
|
public final class LoggingPropertyMappers {
|
||||||
|
|
||||||
private LoggingPropertyMappers(){}
|
private static final String CONSOLE_ENABLED_MSG = "Console log handler is activated";
|
||||||
|
private static final String FILE_ENABLED_MSG = "File log handler is activated";
|
||||||
|
private static final String GELF_ENABLED_MSG = "GELF is activated";
|
||||||
|
|
||||||
|
private LoggingPropertyMappers() {
|
||||||
|
}
|
||||||
|
|
||||||
public static PropertyMapper<?>[] getMappers() {
|
public static PropertyMapper<?>[] getMappers() {
|
||||||
PropertyMapper<?>[] defaultMappers = new PropertyMapper[]{
|
PropertyMapper<?>[] defaultMappers = new PropertyMapper[]{
|
||||||
|
@ -29,15 +36,18 @@ public final class LoggingPropertyMappers {
|
||||||
.paramLabel("<handler>")
|
.paramLabel("<handler>")
|
||||||
.build(),
|
.build(),
|
||||||
fromOption(LoggingOptions.LOG_CONSOLE_OUTPUT)
|
fromOption(LoggingOptions.LOG_CONSOLE_OUTPUT)
|
||||||
|
.isEnabled(LoggingPropertyMappers::isConsoleEnabled, CONSOLE_ENABLED_MSG)
|
||||||
.to("quarkus.log.console.json")
|
.to("quarkus.log.console.json")
|
||||||
.paramLabel("output")
|
.paramLabel("output")
|
||||||
.transformer(LoggingPropertyMappers::resolveLogOutput)
|
.transformer(LoggingPropertyMappers::resolveLogOutput)
|
||||||
.build(),
|
.build(),
|
||||||
fromOption(LoggingOptions.LOG_CONSOLE_FORMAT)
|
fromOption(LoggingOptions.LOG_CONSOLE_FORMAT)
|
||||||
|
.isEnabled(LoggingPropertyMappers::isConsoleEnabled, CONSOLE_ENABLED_MSG)
|
||||||
.to("quarkus.log.console.format")
|
.to("quarkus.log.console.format")
|
||||||
.paramLabel("format")
|
.paramLabel("format")
|
||||||
.build(),
|
.build(),
|
||||||
fromOption(LoggingOptions.LOG_CONSOLE_COLOR)
|
fromOption(LoggingOptions.LOG_CONSOLE_COLOR)
|
||||||
|
.isEnabled(LoggingPropertyMappers::isConsoleEnabled, CONSOLE_ENABLED_MSG)
|
||||||
.to("quarkus.log.console.color")
|
.to("quarkus.log.console.color")
|
||||||
.build(),
|
.build(),
|
||||||
fromOption(LoggingOptions.LOG_CONSOLE_ENABLED)
|
fromOption(LoggingOptions.LOG_CONSOLE_ENABLED)
|
||||||
|
@ -51,15 +61,18 @@ public final class LoggingPropertyMappers {
|
||||||
.transformer(LoggingPropertyMappers.resolveLogHandler("file"))
|
.transformer(LoggingPropertyMappers.resolveLogHandler("file"))
|
||||||
.build(),
|
.build(),
|
||||||
fromOption(LoggingOptions.LOG_FILE)
|
fromOption(LoggingOptions.LOG_FILE)
|
||||||
|
.isEnabled(LoggingPropertyMappers::isFileEnabled, FILE_ENABLED_MSG)
|
||||||
.to("quarkus.log.file.path")
|
.to("quarkus.log.file.path")
|
||||||
.paramLabel("file")
|
.paramLabel("file")
|
||||||
.transformer(LoggingPropertyMappers::resolveFileLogLocation)
|
.transformer(LoggingPropertyMappers::resolveFileLogLocation)
|
||||||
.build(),
|
.build(),
|
||||||
fromOption(LoggingOptions.LOG_FILE_FORMAT)
|
fromOption(LoggingOptions.LOG_FILE_FORMAT)
|
||||||
|
.isEnabled(LoggingPropertyMappers::isFileEnabled, FILE_ENABLED_MSG)
|
||||||
.to("quarkus.log.file.format")
|
.to("quarkus.log.file.format")
|
||||||
.paramLabel("<format>")
|
.paramLabel("<format>")
|
||||||
.build(),
|
.build(),
|
||||||
fromOption(LoggingOptions.LOG_FILE_OUTPUT)
|
fromOption(LoggingOptions.LOG_FILE_OUTPUT)
|
||||||
|
.isEnabled(LoggingPropertyMappers::isFileEnabled, FILE_ENABLED_MSG)
|
||||||
.to("quarkus.log.file.json")
|
.to("quarkus.log.file.json")
|
||||||
.paramLabel("output")
|
.paramLabel("output")
|
||||||
.transformer(LoggingPropertyMappers::resolveLogOutput)
|
.transformer(LoggingPropertyMappers::resolveLogOutput)
|
||||||
|
@ -82,52 +95,74 @@ public final class LoggingPropertyMappers {
|
||||||
.transformer(LoggingPropertyMappers.resolveLogHandler("gelf"))
|
.transformer(LoggingPropertyMappers.resolveLogHandler("gelf"))
|
||||||
.build(),
|
.build(),
|
||||||
fromOption(LoggingOptions.LOG_GELF_LEVEL)
|
fromOption(LoggingOptions.LOG_GELF_LEVEL)
|
||||||
|
.isEnabled(LoggingPropertyMappers::isGelfEnabled, GELF_ENABLED_MSG)
|
||||||
.to("quarkus.log.handler.gelf.level")
|
.to("quarkus.log.handler.gelf.level")
|
||||||
.paramLabel("level")
|
.paramLabel("level")
|
||||||
.build(),
|
.build(),
|
||||||
fromOption(LoggingOptions.LOG_GELF_HOST)
|
fromOption(LoggingOptions.LOG_GELF_HOST)
|
||||||
|
.isEnabled(LoggingPropertyMappers::isGelfEnabled, GELF_ENABLED_MSG)
|
||||||
.to("quarkus.log.handler.gelf.host")
|
.to("quarkus.log.handler.gelf.host")
|
||||||
.paramLabel("hostname")
|
.paramLabel("hostname")
|
||||||
.build(),
|
.build(),
|
||||||
fromOption(LoggingOptions.LOG_GELF_PORT)
|
fromOption(LoggingOptions.LOG_GELF_PORT)
|
||||||
|
.isEnabled(LoggingPropertyMappers::isGelfEnabled, GELF_ENABLED_MSG)
|
||||||
.to("quarkus.log.handler.gelf.port")
|
.to("quarkus.log.handler.gelf.port")
|
||||||
.paramLabel("port")
|
.paramLabel("port")
|
||||||
.build(),
|
.build(),
|
||||||
fromOption(LoggingOptions.LOG_GELF_VERSION)
|
fromOption(LoggingOptions.LOG_GELF_VERSION)
|
||||||
|
.isEnabled(LoggingPropertyMappers::isGelfEnabled, GELF_ENABLED_MSG)
|
||||||
.to("quarkus.log.handler.gelf.version")
|
.to("quarkus.log.handler.gelf.version")
|
||||||
.paramLabel("version")
|
.paramLabel("version")
|
||||||
.build(),
|
.build(),
|
||||||
fromOption(LoggingOptions.LOG_GELF_INCLUDE_STACK_TRACE)
|
fromOption(LoggingOptions.LOG_GELF_INCLUDE_STACK_TRACE)
|
||||||
|
.isEnabled(LoggingPropertyMappers::isGelfEnabled, GELF_ENABLED_MSG)
|
||||||
.to("quarkus.log.handler.gelf.extract-stack-trace")
|
.to("quarkus.log.handler.gelf.extract-stack-trace")
|
||||||
.build(),
|
.build(),
|
||||||
fromOption(LoggingOptions.LOG_GELF_TIMESTAMP_FORMAT)
|
fromOption(LoggingOptions.LOG_GELF_TIMESTAMP_FORMAT)
|
||||||
|
.isEnabled(LoggingPropertyMappers::isGelfEnabled, GELF_ENABLED_MSG)
|
||||||
.to("quarkus.log.handler.gelf.timestamp-pattern")
|
.to("quarkus.log.handler.gelf.timestamp-pattern")
|
||||||
.paramLabel("pattern")
|
.paramLabel("pattern")
|
||||||
.build(),
|
.build(),
|
||||||
fromOption(LoggingOptions.LOG_GELF_FACILITY)
|
fromOption(LoggingOptions.LOG_GELF_FACILITY)
|
||||||
|
.isEnabled(LoggingPropertyMappers::isGelfEnabled, GELF_ENABLED_MSG)
|
||||||
.to("quarkus.log.handler.gelf.facility")
|
.to("quarkus.log.handler.gelf.facility")
|
||||||
.paramLabel("name")
|
.paramLabel("name")
|
||||||
.build(),
|
.build(),
|
||||||
fromOption(LoggingOptions.LOG_GELF_MAX_MSG_SIZE)
|
fromOption(LoggingOptions.LOG_GELF_MAX_MSG_SIZE)
|
||||||
|
.isEnabled(LoggingPropertyMappers::isGelfEnabled, GELF_ENABLED_MSG)
|
||||||
.to("quarkus.log.handler.gelf.maximum-message-size")
|
.to("quarkus.log.handler.gelf.maximum-message-size")
|
||||||
.paramLabel("size")
|
.paramLabel("size")
|
||||||
.build(),
|
.build(),
|
||||||
fromOption(LoggingOptions.LOG_GELF_INCLUDE_LOG_MSG_PARAMS)
|
fromOption(LoggingOptions.LOG_GELF_INCLUDE_LOG_MSG_PARAMS)
|
||||||
|
.isEnabled(LoggingPropertyMappers::isGelfEnabled, GELF_ENABLED_MSG)
|
||||||
.to("quarkus.log.handler.gelf.include-log-message-parameters")
|
.to("quarkus.log.handler.gelf.include-log-message-parameters")
|
||||||
.build(),
|
.build(),
|
||||||
fromOption(LoggingOptions.LOG_GELF_INCLUDE_LOCATION)
|
fromOption(LoggingOptions.LOG_GELF_INCLUDE_LOCATION)
|
||||||
|
.isEnabled(LoggingPropertyMappers::isGelfEnabled, GELF_ENABLED_MSG)
|
||||||
.to("quarkus.log.handler.gelf.include-location")
|
.to("quarkus.log.handler.gelf.include-location")
|
||||||
.build()
|
.build()
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static boolean isGelfEnabled() {
|
||||||
|
return isTrue(LoggingOptions.LOG_GELF_ENABLED);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isConsoleEnabled() {
|
||||||
|
return isTrue(LoggingOptions.LOG_CONSOLE_ENABLED);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isFileEnabled() {
|
||||||
|
return isTrue(LoggingOptions.LOG_FILE_ENABLED);
|
||||||
|
}
|
||||||
|
|
||||||
private static BiFunction<Optional<String>, ConfigSourceInterceptorContext, Optional<String>> resolveLogHandler(String handler) {
|
private static BiFunction<Optional<String>, ConfigSourceInterceptorContext, Optional<String>> resolveLogHandler(String handler) {
|
||||||
return (parentValue, context) -> {
|
return (parentValue, context) -> {
|
||||||
//we want to fall back to console to not have nothing shown up when wrong values are set.
|
//we want to fall back to console to not have nothing shown up when wrong values are set.
|
||||||
String consoleDependantErrorResult = handler.equals(LoggingOptions.DEFAULT_LOG_HANDLER.name()) ? Boolean.TRUE.toString() : Boolean.FALSE.toString();
|
String consoleDependantErrorResult = handler.equals(LoggingOptions.DEFAULT_LOG_HANDLER.name()) ? Boolean.TRUE.toString() : Boolean.FALSE.toString();
|
||||||
String handlers = parentValue.get();
|
String handlers = parentValue.get();
|
||||||
|
|
||||||
if(handlers.isBlank()) {
|
if (handlers.isBlank()) {
|
||||||
addInitializationException(Messages.emptyValueForKey("log"));
|
addInitializationException(Messages.emptyValueForKey("log"));
|
||||||
return of(consoleDependantErrorResult);
|
return of(consoleDependantErrorResult);
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,6 +27,7 @@ import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.function.BiConsumer;
|
import java.util.function.BiConsumer;
|
||||||
import java.util.function.BiFunction;
|
import java.util.function.BiFunction;
|
||||||
|
import java.util.function.BooleanSupplier;
|
||||||
|
|
||||||
import io.smallrye.config.ConfigSourceInterceptorContext;
|
import io.smallrye.config.ConfigSourceInterceptorContext;
|
||||||
import io.smallrye.config.ConfigValue;
|
import io.smallrye.config.ConfigValue;
|
||||||
|
@ -40,12 +41,15 @@ import org.keycloak.quarkus.runtime.cli.PropertyMapperParameterConsumer;
|
||||||
import org.keycloak.quarkus.runtime.configuration.ConfigArgsConfigSource;
|
import org.keycloak.quarkus.runtime.configuration.ConfigArgsConfigSource;
|
||||||
import org.keycloak.quarkus.runtime.Environment;
|
import org.keycloak.quarkus.runtime.Environment;
|
||||||
import org.keycloak.quarkus.runtime.configuration.MicroProfileConfigProvider;
|
import org.keycloak.quarkus.runtime.configuration.MicroProfileConfigProvider;
|
||||||
|
import org.keycloak.utils.StringUtil;
|
||||||
|
|
||||||
public class PropertyMapper<T> {
|
public class PropertyMapper<T> {
|
||||||
|
|
||||||
static PropertyMapper<?> IDENTITY = new PropertyMapper<>(
|
static PropertyMapper<?> IDENTITY = new PropertyMapper<>(
|
||||||
new OptionBuilder<>(null, String.class).build(),
|
new OptionBuilder<>(null, String.class).build(),
|
||||||
null,
|
null,
|
||||||
|
() -> false,
|
||||||
|
"",
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
|
@ -59,18 +63,23 @@ public class PropertyMapper<T> {
|
||||||
|
|
||||||
private final Option<T> option;
|
private final Option<T> option;
|
||||||
private final String to;
|
private final String to;
|
||||||
|
private BooleanSupplier enabled;
|
||||||
|
private String enabledWhen;
|
||||||
private final BiFunction<Optional<String>, ConfigSourceInterceptorContext, Optional<String>> mapper;
|
private final BiFunction<Optional<String>, ConfigSourceInterceptorContext, Optional<String>> mapper;
|
||||||
private final String mapFrom;
|
private final String mapFrom;
|
||||||
private final boolean mask;
|
private final boolean mask;
|
||||||
private final String paramLabel;
|
private final String paramLabel;
|
||||||
private final String envVarFormat;
|
private final String envVarFormat;
|
||||||
private String cliFormat;
|
private final String cliFormat;
|
||||||
private BiConsumer<PropertyMapper<T>, ConfigValue> validator;
|
private final BiConsumer<PropertyMapper<T>, ConfigValue> validator;
|
||||||
|
|
||||||
PropertyMapper(Option<T> option, String to, BiFunction<Optional<String>, ConfigSourceInterceptorContext, Optional<String>> mapper,
|
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) {
|
String mapFrom, String paramLabel, boolean mask, BiConsumer<PropertyMapper<T>, ConfigValue> validator) {
|
||||||
this.option = option;
|
this.option = option;
|
||||||
this.to = to == null ? getFrom() : to;
|
this.to = to == null ? getFrom() : to;
|
||||||
|
this.enabled = enabled;
|
||||||
|
this.enabledWhen = enabledWhen;
|
||||||
this.mapper = mapper == null ? PropertyMapper::defaultTransformer : mapper;
|
this.mapper = mapper == null ? PropertyMapper::defaultTransformer : mapper;
|
||||||
this.mapFrom = mapFrom;
|
this.mapFrom = mapFrom;
|
||||||
this.paramLabel = paramLabel;
|
this.paramLabel = paramLabel;
|
||||||
|
@ -148,15 +157,39 @@ public class PropertyMapper<T> {
|
||||||
return transformedValue;
|
return transformedValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Option<T> getOption() { return this.option; }
|
public Option<T> getOption() {
|
||||||
|
return this.option;
|
||||||
|
}
|
||||||
|
|
||||||
public Class<T> getType() { return this.option.getType(); }
|
public void setEnabled(BooleanSupplier enabled) {
|
||||||
|
this.enabled = enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isEnabled() {
|
||||||
|
return enabled.getAsBoolean();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Optional<String> getEnabledWhen() {
|
||||||
|
return Optional.of(enabledWhen)
|
||||||
|
.filter(StringUtil::isNotBlank)
|
||||||
|
.map(e -> "Available only when " + e);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEnabledWhen(String enabledWhen) {
|
||||||
|
this.enabledWhen = enabledWhen;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Class<T> getType() {
|
||||||
|
return this.option.getType();
|
||||||
|
}
|
||||||
|
|
||||||
public String getFrom() {
|
public String getFrom() {
|
||||||
return MicroProfileConfigProvider.NS_KEYCLOAK_PREFIX + this.option.getKey();
|
return MicroProfileConfigProvider.NS_KEYCLOAK_PREFIX + this.option.getKey();
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getDescription() { return this.option.getDescription(); }
|
public String getDescription() {
|
||||||
|
return this.option.getDescription();
|
||||||
|
}
|
||||||
|
|
||||||
public List<String> getExpectedValues() {
|
public List<String> getExpectedValues() {
|
||||||
return this.option.getExpectedValues();
|
return this.option.getExpectedValues();
|
||||||
|
@ -209,7 +242,11 @@ public class PropertyMapper<T> {
|
||||||
|
|
||||||
if (mapper == null || (mapFrom == null && name.equals(getFrom()))) {
|
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
|
// 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();
|
return ConfigValue.builder()
|
||||||
|
.withName(name)
|
||||||
|
.withValue(value.orElse(null))
|
||||||
|
.withConfigSourceName(configSourceName)
|
||||||
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
Optional<String> mappedValue = mapper.apply(value, context);
|
Optional<String> mappedValue = mapper.apply(value, context);
|
||||||
|
@ -218,8 +255,12 @@ public class PropertyMapper<T> {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return ConfigValue.builder().withName(name).withValue(mappedValue.get()).withRawValue(value.orElse(null))
|
return ConfigValue.builder()
|
||||||
.withConfigSourceName(configSourceName).build();
|
.withName(name)
|
||||||
|
.withValue(mappedValue.get())
|
||||||
|
.withRawValue(value.orElse(null))
|
||||||
|
.withConfigSourceName(configSourceName)
|
||||||
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
private ConfigValue convertValue(ConfigValue configValue) {
|
private ConfigValue convertValue(ConfigValue configValue) {
|
||||||
|
@ -237,8 +278,10 @@ public class PropertyMapper<T> {
|
||||||
private BiFunction<Optional<String>, ConfigSourceInterceptorContext, Optional<String>> mapper;
|
private BiFunction<Optional<String>, ConfigSourceInterceptorContext, Optional<String>> mapper;
|
||||||
private String mapFrom = null;
|
private String mapFrom = null;
|
||||||
private boolean isMasked = false;
|
private boolean isMasked = false;
|
||||||
|
private BooleanSupplier isEnabled = () -> true;
|
||||||
|
private String enabledWhen = "";
|
||||||
private String paramLabel;
|
private String paramLabel;
|
||||||
private BiConsumer<PropertyMapper<T>, ConfigValue> validator = (mapper, value) -> mapper.validateExpectedValues(value, (c, v) -> mapper.validateSingleValue(c, v));
|
private BiConsumer<PropertyMapper<T>, ConfigValue> validator = (mapper, value) -> mapper.validateExpectedValues(value, mapper::validateSingleValue);
|
||||||
|
|
||||||
public Builder(Option<T> option) {
|
public Builder(Option<T> option) {
|
||||||
this.option = option;
|
this.option = option;
|
||||||
|
@ -269,6 +312,17 @@ public class PropertyMapper<T> {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Builder<T> isEnabled(BooleanSupplier isEnabled, String enabledWhen) {
|
||||||
|
this.isEnabled = isEnabled;
|
||||||
|
this.enabledWhen=enabledWhen;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder<T> isEnabled(BooleanSupplier isEnabled) {
|
||||||
|
this.isEnabled = isEnabled;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
public Builder<T> validator(BiConsumer<PropertyMapper<T>, ConfigValue> validator) {
|
public Builder<T> validator(BiConsumer<PropertyMapper<T>, ConfigValue> validator) {
|
||||||
this.validator = validator;
|
this.validator = validator;
|
||||||
return this;
|
return this;
|
||||||
|
@ -278,7 +332,7 @@ public class PropertyMapper<T> {
|
||||||
if (paramLabel == null && Boolean.class.equals(option.getType())) {
|
if (paramLabel == null && Boolean.class.equals(option.getType())) {
|
||||||
paramLabel = Boolean.TRUE + "|" + Boolean.FALSE;
|
paramLabel = Boolean.TRUE + "|" + Boolean.FALSE;
|
||||||
}
|
}
|
||||||
return new PropertyMapper<T>(option, to, mapper, mapFrom, paramLabel, isMasked, validator);
|
return new PropertyMapper<T>(option, to, isEnabled, enabledWhen, mapper, mapFrom, paramLabel, isMasked, validator);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -309,7 +363,7 @@ public class PropertyMapper<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isCliOption(ConfigValue configValue) {
|
public static boolean isCliOption(ConfigValue configValue) {
|
||||||
return Optional.ofNullable(configValue.getConfigSourceName()).filter(name -> name.contains(ConfigArgsConfigSource.NAME)).isPresent();
|
return Optional.ofNullable(configValue.getConfigSourceName()).filter(name -> name.contains(ConfigArgsConfigSource.NAME)).isPresent();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,23 +3,45 @@ package org.keycloak.quarkus.runtime.configuration.mappers;
|
||||||
import io.smallrye.config.ConfigSourceInterceptorContext;
|
import io.smallrye.config.ConfigSourceInterceptorContext;
|
||||||
import io.smallrye.config.ConfigValue;
|
import io.smallrye.config.ConfigValue;
|
||||||
|
|
||||||
|
import jakarta.ws.rs.core.MultivaluedHashMap;
|
||||||
|
import org.jboss.logging.Logger;
|
||||||
|
import org.keycloak.common.util.CollectionUtil;
|
||||||
import org.keycloak.config.ConfigSupportLevel;
|
import org.keycloak.config.ConfigSupportLevel;
|
||||||
import org.keycloak.config.OptionCategory;
|
import org.keycloak.config.OptionCategory;
|
||||||
import org.keycloak.quarkus.runtime.Environment;
|
import org.keycloak.quarkus.runtime.Environment;
|
||||||
|
import org.keycloak.quarkus.runtime.cli.PropertyException;
|
||||||
|
import org.keycloak.quarkus.runtime.cli.command.AbstractCommand;
|
||||||
|
import org.keycloak.quarkus.runtime.cli.command.Build;
|
||||||
|
import org.keycloak.quarkus.runtime.cli.command.ShowConfig;
|
||||||
import org.keycloak.quarkus.runtime.configuration.ConfigArgsConfigSource;
|
import org.keycloak.quarkus.runtime.configuration.ConfigArgsConfigSource;
|
||||||
|
import org.keycloak.quarkus.runtime.configuration.DisabledMappersInterceptor;
|
||||||
|
import org.keycloak.quarkus.runtime.configuration.PersistedConfigSource;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.EnumMap;
|
import java.util.EnumMap;
|
||||||
|
import java.util.EnumSet;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.function.Function;
|
import java.util.Optional;
|
||||||
|
import java.util.function.BiConsumer;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.function.BooleanSupplier;
|
||||||
|
import java.util.function.Predicate;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import static org.keycloak.quarkus.runtime.Environment.isParsedCommand;
|
||||||
|
import static org.keycloak.quarkus.runtime.Environment.isRebuild;
|
||||||
|
import static org.keycloak.quarkus.runtime.Environment.isRebuildCheck;
|
||||||
|
|
||||||
public final class PropertyMappers {
|
public final class PropertyMappers {
|
||||||
|
|
||||||
public static String VALUE_MASK = "*******";
|
public static String VALUE_MASK = "*******";
|
||||||
private static final MappersConfig MAPPERS = new MappersConfig();
|
private static final MappersConfig MAPPERS = new MappersConfig();
|
||||||
|
private static final Logger log = Logger.getLogger(PropertyMappers.class);
|
||||||
|
|
||||||
private PropertyMappers(){}
|
private PropertyMappers(){}
|
||||||
|
|
||||||
|
@ -44,8 +66,7 @@ public final class PropertyMappers {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ConfigValue getValue(ConfigSourceInterceptorContext context, String name) {
|
public static ConfigValue getValue(ConfigSourceInterceptorContext context, String name) {
|
||||||
PropertyMapper<?> mapper = MAPPERS.getOrDefault(name, PropertyMapper.IDENTITY);
|
return getMapperOrDefault(name, PropertyMapper.IDENTITY).getConfigValue(name, context);
|
||||||
return mapper.getConfigValue(name, context);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean isBuildTimeProperty(String name) {
|
public static boolean isBuildTimeProperty(String name) {
|
||||||
|
@ -53,7 +74,7 @@ public final class PropertyMappers {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
PropertyMapper<?> mapper = MAPPERS.get(name);
|
final PropertyMapper<?> mapper = getMapperOrDefault(name, null);
|
||||||
boolean isBuildTimeProperty = mapper == null ? false : mapper.isBuildTime();
|
boolean isBuildTimeProperty = mapper == null ? false : mapper.isBuildTime();
|
||||||
|
|
||||||
return isBuildTimeProperty
|
return isBuildTimeProperty
|
||||||
|
@ -83,6 +104,27 @@ public final class PropertyMappers {
|
||||||
return MAPPERS.getBuildTimeMappers();
|
return MAPPERS.getBuildTimeMappers();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Map<String, PropertyMapper<?>> getDisabledMappers() {
|
||||||
|
final var disabledMappers = new HashMap<>(getDisabledBuildTimeMappers());
|
||||||
|
disabledMappers.putAll(getDisabledRuntimeMappers());
|
||||||
|
return disabledMappers;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Map<String, PropertyMapper<?>> getDisabledRuntimeMappers() {
|
||||||
|
return MAPPERS.getDisabledRuntimeMappers();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Map<String, PropertyMapper<?>> getDisabledBuildTimeMappers() {
|
||||||
|
return MAPPERS.getDisabledBuildTimeMappers();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes all disabled mappers from the runtime/buildtime mappers
|
||||||
|
*/
|
||||||
|
public static void sanitizeDisabledMappers() {
|
||||||
|
MAPPERS.sanitizeDisabledMappers();
|
||||||
|
}
|
||||||
|
|
||||||
public static String formatValue(String property, String value) {
|
public static String formatValue(String property, String value) {
|
||||||
property = removeProfilePrefixIfNeeded(property);
|
property = removeProfilePrefixIfNeeded(property);
|
||||||
PropertyMapper<?> mapper = getMapper(property);
|
PropertyMapper<?> mapper = getMapper(property);
|
||||||
|
@ -102,32 +144,95 @@ public final class PropertyMappers {
|
||||||
return property;
|
return property;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static PropertyMapper<?> getMapper(String property) {
|
private static PropertyMapper<?> getMapperOrDefault(String property, PropertyMapper<?> defaultMapper) {
|
||||||
if (property.startsWith("%")) {
|
final var mappers = MAPPERS.getOrDefault(property, Collections.emptyList());
|
||||||
return MAPPERS.get(property.substring(property.indexOf('.') + 1));
|
|
||||||
}
|
return switch (mappers.size()) {
|
||||||
return MAPPERS.get(property);
|
case 0 -> defaultMapper;
|
||||||
|
case 1 -> mappers.get(0);
|
||||||
|
default -> {
|
||||||
|
var allowedMappers = filterDeniedCategories(mappers);
|
||||||
|
|
||||||
|
yield switch (allowedMappers.size()) {
|
||||||
|
case 0 -> defaultMapper;
|
||||||
|
case 1 -> allowedMappers.iterator().next();
|
||||||
|
default -> {
|
||||||
|
log.debugf("Duplicated mappers for key '%s'. Used the first found.", property);
|
||||||
|
yield allowedMappers.iterator().next();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Collection<PropertyMapper<?>> getMappers() {
|
public static PropertyMapper<?> getMapper(String property) {
|
||||||
return MAPPERS.values();
|
return getMapperOrDefault(polishProperty(property), null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<PropertyMapper<?>> getMappers(String property) {
|
||||||
|
return MAPPERS.get(polishProperty(property));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Set<PropertyMapper<?>> getMappers() {
|
||||||
|
return MAPPERS.values().stream().flatMap(Collection::stream).collect(Collectors.toSet());
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean isSupported(PropertyMapper<?> mapper) {
|
public static boolean isSupported(PropertyMapper<?> mapper) {
|
||||||
return mapper.getCategory().getSupportLevel().equals(ConfigSupportLevel.SUPPORTED);
|
return mapper.getCategory().getSupportLevel().equals(ConfigSupportLevel.SUPPORTED);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class MappersConfig extends HashMap<String, PropertyMapper<?>> {
|
public static Optional<PropertyMapper<?>> getDisabledMapper(String property) {
|
||||||
|
if (property == null) return Optional.empty();
|
||||||
|
|
||||||
private Map<OptionCategory, List<PropertyMapper<?>>> buildTimeMappers = new EnumMap<>(OptionCategory.class);
|
PropertyMapper<?> mapper = getDisabledBuildTimeMappers().get(property);
|
||||||
private Map<OptionCategory, List<PropertyMapper<?>>> runtimeTimeMappers = new EnumMap<>(OptionCategory.class);
|
if (mapper == null) {
|
||||||
|
mapper = getDisabledRuntimeMappers().get(property);
|
||||||
|
}
|
||||||
|
return Optional.ofNullable(mapper);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isDisabledMapper(String property) {
|
||||||
|
final Predicate<String> isDisabledMapper = (p) -> getDisabledMapper(p).isPresent() && getMapper(p) == null;
|
||||||
|
|
||||||
|
if (property.startsWith("%")) {
|
||||||
|
return isDisabledMapper.test(property.substring(property.indexOf('.') + 1));
|
||||||
|
}
|
||||||
|
return isDisabledMapper.test(property);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String polishProperty(String property) {
|
||||||
|
return property.startsWith("%") ? property.substring(property.indexOf('.') + 1) : property;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Set<PropertyMapper<?>> filterDeniedCategories(List<PropertyMapper<?>> mappers) {
|
||||||
|
final var allowedCategories = Environment.getParsedCommand()
|
||||||
|
.map(AbstractCommand::getOptionCategories)
|
||||||
|
.map(EnumSet::copyOf)
|
||||||
|
.orElseGet(() -> EnumSet.allOf(OptionCategory.class));
|
||||||
|
|
||||||
|
return mappers.stream().filter(f -> allowedCategories.contains(f.getCategory())).collect(Collectors.toSet());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class MappersConfig extends MultivaluedHashMap<String, PropertyMapper<?>> {
|
||||||
|
|
||||||
|
private final Map<OptionCategory, List<PropertyMapper<?>>> buildTimeMappers = new EnumMap<>(OptionCategory.class);
|
||||||
|
private final Map<OptionCategory, List<PropertyMapper<?>>> runtimeTimeMappers = new EnumMap<>(OptionCategory.class);
|
||||||
|
|
||||||
|
private final Map<String, PropertyMapper<?>> disabledBuildTimeMappers = new HashMap<>();
|
||||||
|
private final Map<String, PropertyMapper<?>> disabledRuntimeMappers = new HashMap<>();
|
||||||
|
|
||||||
|
public void addAll(PropertyMapper<?>[] mappers, BooleanSupplier isEnabled, String enabledWhen) {
|
||||||
|
Arrays.stream(mappers).forEach(mapper -> {
|
||||||
|
mapper.setEnabled(isEnabled);
|
||||||
|
mapper.setEnabledWhen(enabledWhen);
|
||||||
|
});
|
||||||
|
|
||||||
|
addAll(mappers);
|
||||||
|
}
|
||||||
|
|
||||||
public void addAll(PropertyMapper<?>[] mappers) {
|
public void addAll(PropertyMapper<?>[] mappers) {
|
||||||
for (PropertyMapper<?> mapper : mappers) {
|
for (PropertyMapper<?> mapper : mappers) {
|
||||||
super.put(mapper.getTo(), mapper);
|
addMapper(mapper);
|
||||||
super.put(mapper.getFrom(), mapper);
|
|
||||||
super.put(mapper.getCliFormat(), mapper);
|
|
||||||
super.put(mapper.getEnvVarFormat(), mapper);
|
|
||||||
|
|
||||||
if (mapper.isBuildTime()) {
|
if (mapper.isBuildTime()) {
|
||||||
addMapperByStage(mapper, buildTimeMappers);
|
addMapperByStage(mapper, buildTimeMappers);
|
||||||
|
@ -137,22 +242,68 @@ public final class PropertyMappers {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addMapperByStage(PropertyMapper<?> mapper, Map<OptionCategory, List<PropertyMapper<?>>> mappers) {
|
private static void addMapperByStage(PropertyMapper<?> mapper, Map<OptionCategory, List<PropertyMapper<?>>> mappers) {
|
||||||
mappers.computeIfAbsent(mapper.getCategory(),
|
mappers.computeIfAbsent(mapper.getCategory(), c -> new ArrayList<>()).add(mapper);
|
||||||
new Function<OptionCategory, List<PropertyMapper<?>>>() {
|
|
||||||
@Override
|
|
||||||
public List<PropertyMapper<?>> apply(OptionCategory c) {
|
|
||||||
return new ArrayList<>();
|
|
||||||
}
|
|
||||||
}).add(mapper);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
public void addMapper(PropertyMapper<?> mapper) {
|
||||||
public PropertyMapper<?> put(String key, PropertyMapper<?> value) {
|
handleMapper(mapper, this::add);
|
||||||
if (containsKey(key)) {
|
}
|
||||||
throw new IllegalArgumentException("Duplicated mapper for key [" + key + "]");
|
|
||||||
|
public void removeMapper(PropertyMapper<?> mapper) {
|
||||||
|
handleMapper(mapper, this::remove);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void remove(String key, PropertyMapper<?> mapper) {
|
||||||
|
List<PropertyMapper<?>> list = get(key);
|
||||||
|
if (CollectionUtil.isNotEmpty(list)) {
|
||||||
|
list.remove(mapper);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void sanitizeDisabledMappers() {
|
||||||
|
if (Environment.getParsedCommand().isEmpty()) return; // do not sanitize when no command is present
|
||||||
|
|
||||||
|
DisabledMappersInterceptor.runWithDisabled(() -> { // We need to have the whole configuration available
|
||||||
|
|
||||||
|
// Initialize profile in order to check state of features. Disable Persisted CS for re-augmentation
|
||||||
|
if (isRebuildCheck()) {
|
||||||
|
PersistedConfigSource.getInstance().runWithDisabled(Environment::getCurrentOrCreateFeatureProfile);
|
||||||
|
} else {
|
||||||
|
Environment.getCurrentOrCreateFeatureProfile();
|
||||||
|
}
|
||||||
|
|
||||||
|
sanitizeMappers(buildTimeMappers, disabledBuildTimeMappers);
|
||||||
|
sanitizeMappers(runtimeTimeMappers, disabledRuntimeMappers);
|
||||||
|
|
||||||
|
assertDuplicatedMappers();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertDuplicatedMappers() {
|
||||||
|
final var duplicatedMappers = entrySet().stream()
|
||||||
|
.filter(e -> CollectionUtil.isNotEmpty(e.getValue()))
|
||||||
|
.filter(e -> e.getValue().size() > 1)
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
final var isBuildPhase = isRebuild() || isRebuildCheck() || isParsedCommand(Build.NAME);
|
||||||
|
final var allowedForCommand = isParsedCommand(ShowConfig.NAME);
|
||||||
|
|
||||||
|
if (!duplicatedMappers.isEmpty()) {
|
||||||
|
duplicatedMappers.forEach(f -> {
|
||||||
|
final var filteredMappers = filterDeniedCategories(f.getValue());
|
||||||
|
|
||||||
|
if (filteredMappers.size() > 1) {
|
||||||
|
final var areBuildTimeMappers = filteredMappers.stream().anyMatch(PropertyMapper::isBuildTime);
|
||||||
|
|
||||||
|
// thrown in runtime, or in build time, when some mapper is marked as buildTime + not allowed to have duplicates for specific command
|
||||||
|
final var shouldBeThrown = !allowedForCommand && (!isBuildPhase || areBuildTimeMappers);
|
||||||
|
if (shouldBeThrown) {
|
||||||
|
throw new PropertyException(String.format("Duplicated mapper for key '%s'.", f.getKey()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
return super.put(key, value);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Map<OptionCategory, List<PropertyMapper<?>>> getRuntimeMappers() {
|
public Map<OptionCategory, List<PropertyMapper<?>>> getRuntimeMappers() {
|
||||||
|
@ -162,6 +313,35 @@ public final class PropertyMappers {
|
||||||
public Map<OptionCategory, List<PropertyMapper<?>>> getBuildTimeMappers() {
|
public Map<OptionCategory, List<PropertyMapper<?>>> getBuildTimeMappers() {
|
||||||
return buildTimeMappers;
|
return buildTimeMappers;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
public Map<String, PropertyMapper<?>> getDisabledBuildTimeMappers() {
|
||||||
|
return disabledBuildTimeMappers;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, PropertyMapper<?>> getDisabledRuntimeMappers() {
|
||||||
|
return disabledRuntimeMappers;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void sanitizeMappers(Map<OptionCategory, List<PropertyMapper<?>>> mappers,
|
||||||
|
Map<String, PropertyMapper<?>> disabledMappers) {
|
||||||
|
mappers.forEach((category, propertyMappers) ->
|
||||||
|
propertyMappers.removeIf(pm -> {
|
||||||
|
final boolean shouldRemove = !pm.isEnabled();
|
||||||
|
if (shouldRemove) {
|
||||||
|
MAPPERS.removeMapper(pm);
|
||||||
|
handleMapper(pm, disabledMappers::put);
|
||||||
|
}
|
||||||
|
return shouldRemove;
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void handleMapper(PropertyMapper<?> mapper, BiConsumer<String, PropertyMapper<?>> operation) {
|
||||||
|
operation.accept(mapper.getFrom(), mapper);
|
||||||
|
if (!mapper.getFrom().equals(mapper.getTo())) {
|
||||||
|
operation.accept(mapper.getTo(), mapper);
|
||||||
|
}
|
||||||
|
operation.accept(mapper.getCliFormat(), mapper);
|
||||||
|
operation.accept(mapper.getEnvVarFormat(), mapper);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,4 +15,5 @@
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
#
|
#
|
||||||
|
|
||||||
org.keycloak.quarkus.runtime.configuration.PropertyMappingInterceptor
|
org.keycloak.quarkus.runtime.configuration.PropertyMappingInterceptor
|
||||||
|
org.keycloak.quarkus.runtime.configuration.DisabledMappersInterceptor
|
|
@ -2,4 +2,8 @@ spi-hostname-default-frontend-url = ${keycloak.frontendUrl:http://filepropdefaul
|
||||||
%user-profile.spi-hostname-default-frontend-url = http://filepropprofile.unittest
|
%user-profile.spi-hostname-default-frontend-url = http://filepropprofile.unittest
|
||||||
log-level=${SOME_LOG_LEVEL:warn}
|
log-level=${SOME_LOG_LEVEL:warn}
|
||||||
config-keystore=src/test/resources/keystore
|
config-keystore=src/test/resources/keystore
|
||||||
config-keystore-password=secret
|
config-keystore-password=secret
|
||||||
|
|
||||||
|
quarkus.log.file.path=random/path
|
||||||
|
|
||||||
|
log-gelf-level=WARN
|
|
@ -87,7 +87,7 @@ public class OptionValidationTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Launch({"start", "--db-username=foobar","--db-pasword=mytestpw", "--foobar=barfoo"})
|
@Launch({"start", "--db-username=foobar", "--db-pasword=mytestpw", "--foobar=barfoo"})
|
||||||
public void failWithFirstOptionOnMultipleUnknownOptions(LaunchResult result) {
|
public void failWithFirstOptionOnMultipleUnknownOptions(LaunchResult result) {
|
||||||
CLIResult cliResult = (CLIResult) result;
|
CLIResult cliResult = (CLIResult) result;
|
||||||
assertEquals("Unknown option: '--db-pasword'\n" +
|
assertEquals("Unknown option: '--db-pasword'\n" +
|
||||||
|
@ -96,7 +96,7 @@ public class OptionValidationTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Launch({ "start", "--db postgres" })
|
@Launch({"start", "--db postgres"})
|
||||||
void failSingleParamWithSpace(LaunchResult result) {
|
void failSingleParamWithSpace(LaunchResult result) {
|
||||||
CLIResult cliResult = (CLIResult) result;
|
CLIResult cliResult = (CLIResult) result;
|
||||||
cliResult.assertError("Option: '--db postgres' is not expected to contain whitespace, please remove any unnecessary quoting/escaping");
|
cliResult.assertError("Option: '--db postgres' is not expected to contain whitespace, please remove any unnecessary quoting/escaping");
|
||||||
|
|
|
@ -38,6 +38,6 @@ public class ExportDistTest {
|
||||||
cliResult.assertNoMessage("Listening on: http");
|
cliResult.assertNoMessage("Listening on: http");
|
||||||
|
|
||||||
cliResult = dist.run("export", "--realm=master");
|
cliResult = dist.run("export", "--realm=master");
|
||||||
cliResult.assertMessage("Must specify either --dir or --file options.");
|
cliResult.assertError("Must specify either --dir or --file options.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,5 +43,8 @@ public class ImportDistTest {
|
||||||
cliResult.assertMessage("Import finished successfully");
|
cliResult.assertMessage("Import finished successfully");
|
||||||
cliResult.assertNoMessage("Changes detected in configuration");
|
cliResult.assertNoMessage("Changes detected in configuration");
|
||||||
cliResult.assertNoMessage("Listening on: http");
|
cliResult.assertNoMessage("Listening on: http");
|
||||||
|
|
||||||
|
cliResult = dist.run("import");
|
||||||
|
cliResult.assertError("Must specify either --dir or --file options.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -96,6 +96,9 @@ public class LoggingDistTest {
|
||||||
@Launch({ "start-dev", "--log-console-output=json" })
|
@Launch({ "start-dev", "--log-console-output=json" })
|
||||||
void testJsonFormatApplied(LaunchResult result) throws JsonProcessingException {
|
void testJsonFormatApplied(LaunchResult result) throws JsonProcessingException {
|
||||||
CLIResult cliResult = (CLIResult) result;
|
CLIResult cliResult = (CLIResult) result;
|
||||||
|
|
||||||
|
cliResult.assertMessage("The following used run time options are UNAVAILABLE and will be ignored during build time:");
|
||||||
|
cliResult.assertMessage("- log-console-output: Available only when Console log handler is activated.");
|
||||||
cliResult.assertJsonLogDefaultsApplied();
|
cliResult.assertJsonLogDefaultsApplied();
|
||||||
cliResult.assertStartedDevMode();
|
cliResult.assertStartedDevMode();
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,43 +20,117 @@ package org.keycloak.it.cli.dist;
|
||||||
import io.quarkus.test.junit.main.Launch;
|
import io.quarkus.test.junit.main.Launch;
|
||||||
import io.quarkus.test.junit.main.LaunchResult;
|
import io.quarkus.test.junit.main.LaunchResult;
|
||||||
import org.junit.jupiter.api.Assertions;
|
import org.junit.jupiter.api.Assertions;
|
||||||
|
import org.junit.jupiter.api.MethodOrderer;
|
||||||
|
import org.junit.jupiter.api.Order;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.TestMethodOrder;
|
||||||
import org.keycloak.it.junit5.extension.CLIResult;
|
import org.keycloak.it.junit5.extension.CLIResult;
|
||||||
import org.keycloak.it.junit5.extension.DistributionTest;
|
import org.keycloak.it.junit5.extension.DistributionTest;
|
||||||
import org.keycloak.it.junit5.extension.RawDistOnly;
|
import org.keycloak.it.junit5.extension.RawDistOnly;
|
||||||
|
import org.keycloak.it.junit5.extension.WithEnvVars;
|
||||||
import org.keycloak.it.utils.KeycloakDistribution;
|
import org.keycloak.it.utils.KeycloakDistribution;
|
||||||
|
|
||||||
import java.nio.file.Paths;
|
import java.nio.file.Paths;
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
import static org.keycloak.quarkus.runtime.cli.command.Main.CONFIG_FILE_LONG_NAME;
|
import static org.keycloak.quarkus.runtime.cli.command.Main.CONFIG_FILE_LONG_NAME;
|
||||||
|
|
||||||
@DistributionTest
|
@DistributionTest
|
||||||
|
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
|
||||||
public class OptionsDistTest {
|
public class OptionsDistTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@Order(1)
|
||||||
@Launch({"build", "--db=invalid"})
|
@Launch({"build", "--db=invalid"})
|
||||||
public void failInvalidOptionValue(LaunchResult result) {
|
public void failInvalidOptionValue(LaunchResult result) {
|
||||||
Assertions.assertTrue(result.getErrorOutput().contains("Invalid value for option '--db': invalid. Expected values are: dev-file, dev-mem, mariadb, mssql, mysql, oracle, postgres"));
|
Assertions.assertTrue(result.getErrorOutput().contains("Invalid value for option '--db': invalid. Expected values are: dev-file, dev-mem, mariadb, mssql, mysql, oracle, postgres"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Launch({"start-dev", "--test=invalid"})
|
@Order(2)
|
||||||
public void testServerDoesNotStartIfValidationFailDuringReAugStartDev(LaunchResult result) {
|
|
||||||
assertEquals(1, result.getErrorStream().stream().filter(s -> s.contains("Unknown option: '--test'")).count());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@Launch({"start", "--test=invalid"})
|
@Launch({"start", "--test=invalid"})
|
||||||
public void testServerDoesNotStartIfValidationFailDuringReAugStart(LaunchResult result) {
|
public void testServerDoesNotStartIfValidationFailDuringReAugStart(LaunchResult result) {
|
||||||
assertEquals(1, result.getErrorStream().stream().filter(s -> s.contains("Unknown option: '--test'")).count());
|
assertEquals(1, result.getErrorStream().stream().filter(s -> s.contains("Unknown option: '--test'")).count());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@Order(3)
|
||||||
|
@Launch({"start", "--log=console", "--log-file-output=json", "--http-enabled=true", "--hostname-strict=false"})
|
||||||
|
public void testServerDoesNotStartIfDisabledFileLogOption(LaunchResult result) {
|
||||||
|
assertEquals(1, result.getErrorStream().stream().filter(s -> s.contains("Disabled option: '--log-file-output'. Available only when File log handler is activated")).count());
|
||||||
|
assertEquals(1, result.getErrorStream().stream().filter(s -> s.contains("Possible solutions: --log, --log-console-output, --log-console-format, --log-console-color")).count());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Order(4)
|
||||||
|
@Launch({"start", "--log=file", "--log-file-output=json", "--http-enabled=true", "--hostname-strict=false"})
|
||||||
|
public void testServerStartIfEnabledFileLogOption(LaunchResult result) {
|
||||||
|
assertEquals(0, result.getErrorStream().stream().filter(s -> s.contains("Disabled option: '--log-file-output'. Available only when File log handler is activated")).count());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Order(5)
|
||||||
|
@WithEnvVars({"KC_LOG", "console", "KC_LOG_CONSOLE_COLOR", "true", "KC_LOG_FILE", "something-env", "KC_LOG_GELF_VERSION", "1.1", "KC_HTTP_ENABLED", "true", "KC_HOSTNAME_STRICT", "false"})
|
||||||
|
@Launch({"start"})
|
||||||
|
public void testSettingEnvVars(LaunchResult result) {
|
||||||
|
CLIResult cliResult = (CLIResult) result;
|
||||||
|
|
||||||
|
cliResult.assertMessage("The following used run time options are UNAVAILABLE and will be ignored during build time:");
|
||||||
|
cliResult.assertMessage("- log-file: Available only when File log handler is activated.");
|
||||||
|
cliResult.assertMessage("- log-gelf-version: Available only when GELF is activated.");
|
||||||
|
cliResult.assertMessage("quarkus.log.console.color");
|
||||||
|
cliResult.assertMessage("config property is deprecated and should not be used anymore");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Order(6)
|
||||||
@RawDistOnly(reason = "Raw is enough and we avoid issues with including custom conf file in the container")
|
@RawDistOnly(reason = "Raw is enough and we avoid issues with including custom conf file in the container")
|
||||||
public void testExpressionsInConfigFile(KeycloakDistribution distribution) {
|
public void testExpressionsInConfigFile(KeycloakDistribution distribution) {
|
||||||
distribution.setEnvVar("MY_LOG_LEVEL", "debug");
|
distribution.setEnvVar("MY_LOG_LEVEL", "warn");
|
||||||
CLIResult result = distribution.run(CONFIG_FILE_LONG_NAME + "=" + Paths.get("src/test/resources/OptionsDistTest/keycloak.conf").toAbsolutePath().normalize(), "start-dev");
|
CLIResult result = distribution.run(CONFIG_FILE_LONG_NAME + "=" + Paths.get("src/test/resources/OptionsDistTest/keycloak.conf").toAbsolutePath().normalize(), "start", "--http-enabled=true", "--hostname-strict=false", "--optimized");
|
||||||
result.assertMessage("DEBUG [org.keycloak");
|
result.assertNoMessage("INFO [io.quarkus]");
|
||||||
|
result.assertNoMessage("Listening on:");
|
||||||
|
|
||||||
|
// specified in the OptionsDistTest/keycloak.conf
|
||||||
|
result.assertMessage("The following used run time options are UNAVAILABLE and will be ignored during build time:");
|
||||||
|
result.assertMessage("- log-gelf-level: Available only when GELF is activated.");
|
||||||
|
result.assertMessage("- log-gelf-version: Available only when GELF is activated.");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Order(7)
|
||||||
|
@Launch({"start", "--log=console", "--log-gelf-include-stack-trace=true"})
|
||||||
|
public void testDisabledGelfOption(LaunchResult result) {
|
||||||
|
CLIResult cliResult = (CLIResult) result;
|
||||||
|
cliResult.assertError("Disabled option: '--log-gelf-include-stack-trace'. Available only when GELF is activated");
|
||||||
|
cliResult.assertError("Possible solutions: --log, --log-console-output, --log-console-format, --log-console-color, --log-level");
|
||||||
|
cliResult.assertError("Try '" + KeycloakDistribution.SCRIPT_CMD + " start --help' for more information on the available options.");
|
||||||
|
cliResult.assertError("Specify '--help-all' to obtain information on all options and their availability.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start-dev should be executed as last tests - build is done for development mode
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Order(8)
|
||||||
|
@Launch({"start-dev", "--test=invalid"})
|
||||||
|
public void testServerDoesNotStartIfValidationFailDuringReAugStartDev(LaunchResult result) {
|
||||||
|
assertEquals(1, result.getErrorStream().stream().filter(s -> s.contains("Unknown option: '--test'")).count());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Order(9)
|
||||||
|
@Launch({"start-dev", "--log=console", "--log-file-output=json"})
|
||||||
|
public void testServerDoesNotStartDevIfDisabledFileLogOption(LaunchResult result) {
|
||||||
|
assertEquals(1, result.getErrorStream().stream().filter(s -> s.contains("Disabled option: '--log-file-output'. Available only when File log handler is activated")).count());
|
||||||
|
assertEquals(1, result.getErrorStream().stream().filter(s -> s.contains("Possible solutions: --log, --log-console-output, --log-console-format, --log-console-color")).count());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Order(10)
|
||||||
|
@Launch({"start-dev", "--log=file", "--log-file-output=json", "--log-console-color=true"})
|
||||||
|
public void testServerStartDevIfEnabledFileLogOption(LaunchResult result) {
|
||||||
|
assertEquals(0, result.getErrorStream().stream().filter(s -> s.contains("Disabled option: '--log-file-output'. Available only when File log handler is activated")).count());
|
||||||
|
assertEquals(1, result.getErrorStream().stream().filter(s -> s.contains("Disabled option: '--log-console-color'. Available only when Console log handler is activated")).count());
|
||||||
|
assertEquals(1, result.getErrorStream().stream().filter(s -> s.contains("Possible solutions: --log, --log-file, --log-file-format, --log-file-output, --log-level")).count());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1 +1,8 @@
|
||||||
log-level=${MY_LOG_LEVEL:warn}
|
log-level=${MY_LOG_LEVEL:warn}
|
||||||
|
|
||||||
|
log=console,file
|
||||||
|
log-file-output=json
|
||||||
|
|
||||||
|
# Ignored disabled
|
||||||
|
log-gelf-level=WARN
|
||||||
|
log-gelf-version=1.1
|
||||||
|
|
|
@ -74,47 +74,16 @@ Logging:
|
||||||
--log <handler> Enable one or more log handlers in a comma-separated list. Possible values
|
--log <handler> Enable one or more log handlers in a comma-separated list. Possible values
|
||||||
are: console, file, gelf (deprecated). Default: console.
|
are: console, file, gelf (deprecated). Default: console.
|
||||||
--log-console-color <true|false>
|
--log-console-color <true|false>
|
||||||
Enable or disable colors when logging to console. Default: false.
|
Enable or disable colors when logging to console. Default: false. Available
|
||||||
|
only when Console log handler is activated.
|
||||||
--log-console-format <format>
|
--log-console-format <format>
|
||||||
The format of unstructured console log entries. If the format has spaces in
|
The format of unstructured console log entries. If the format has spaces in
|
||||||
it, escape the value using "<format>". Default: %d{yyyy-MM-dd HH:mm:ss,SSS} %
|
it, escape the value using "<format>". Default: %d{yyyy-MM-dd HH:mm:ss,SSS} %
|
||||||
-5p [%c] (%t) %s%e%n.
|
-5p [%c] (%t) %s%e%n. Available only when Console log handler is activated.
|
||||||
--log-console-output <output>
|
--log-console-output <output>
|
||||||
Set the log output to JSON or default (plain) unstructured logging. Possible
|
Set the log output to JSON or default (plain) unstructured logging. Possible
|
||||||
values are: default, json. Default: default.
|
values are: default, json. Default: default. Available only when Console log
|
||||||
--log-file <file> Set the log file path and filename. Default: data/log/keycloak.log.
|
handler is activated.
|
||||||
--log-file-format <format>
|
|
||||||
Set a format specific to file log entries. Default: %d{yyyy-MM-dd HH:mm:ss,
|
|
||||||
SSS} %-5p [%c] (%t) %s%e%n.
|
|
||||||
--log-file-output <output>
|
|
||||||
Set the log output to JSON or default (plain) unstructured logging. Possible
|
|
||||||
values are: default, json. Default: default.
|
|
||||||
--log-gelf-facility <name>
|
|
||||||
DEPRECATED. The facility (name of the process) that sends the message.
|
|
||||||
Default: keycloak.
|
|
||||||
--log-gelf-host <hostname>
|
|
||||||
DEPRECATED. Hostname of the Logstash or Graylog Host. By default UDP is used,
|
|
||||||
prefix the host with 'tcp:' to switch to TCP. Example: 'tcp:localhost'
|
|
||||||
Default: localhost.
|
|
||||||
--log-gelf-include-location <true|false>
|
|
||||||
DEPRECATED. Include source code location. Default: true.
|
|
||||||
--log-gelf-include-message-parameters <true|false>
|
|
||||||
DEPRECATED. Include message parameters from the log event. Default: true.
|
|
||||||
--log-gelf-include-stack-trace <true|false>
|
|
||||||
DEPRECATED. If set to true, occuring stack traces are included in the
|
|
||||||
'StackTrace' field in the GELF output. Default: true.
|
|
||||||
--log-gelf-level <level>
|
|
||||||
DEPRECATED. The log level specifying which message levels will be logged by
|
|
||||||
the GELF logger. Message levels lower than this value will be discarded.
|
|
||||||
Default: INFO.
|
|
||||||
--log-gelf-max-message-size <size>
|
|
||||||
DEPRECATED. Maximum message size (in bytes). If the message size is exceeded,
|
|
||||||
GELF will submit the message in multiple chunks. Default: 8192.
|
|
||||||
--log-gelf-port <port>
|
|
||||||
DEPRECATED. The port the Logstash or Graylog Host is called on. Default: 12201.
|
|
||||||
--log-gelf-timestamp-format <pattern>
|
|
||||||
DEPRECATED. Set the format for the GELF timestamp field. Uses Java
|
|
||||||
SimpleDateFormat pattern. Default: yyyy-MM-dd HH:mm:ss,SSS.
|
|
||||||
--log-level <category:level>
|
--log-level <category:level>
|
||||||
The log level of the root category or a comma-separated list of individual
|
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
|
categories and their levels. For the root category, you don't need to
|
||||||
|
|
|
@ -74,47 +74,61 @@ Logging:
|
||||||
--log <handler> Enable one or more log handlers in a comma-separated list. Possible values
|
--log <handler> Enable one or more log handlers in a comma-separated list. Possible values
|
||||||
are: console, file, gelf (deprecated). Default: console.
|
are: console, file, gelf (deprecated). Default: console.
|
||||||
--log-console-color <true|false>
|
--log-console-color <true|false>
|
||||||
Enable or disable colors when logging to console. Default: false.
|
Enable or disable colors when logging to console. Default: false. Available
|
||||||
|
only when Console log handler is activated.
|
||||||
--log-console-format <format>
|
--log-console-format <format>
|
||||||
The format of unstructured console log entries. If the format has spaces in
|
The format of unstructured console log entries. If the format has spaces in
|
||||||
it, escape the value using "<format>". Default: %d{yyyy-MM-dd HH:mm:ss,SSS} %
|
it, escape the value using "<format>". Default: %d{yyyy-MM-dd HH:mm:ss,SSS} %
|
||||||
-5p [%c] (%t) %s%e%n.
|
-5p [%c] (%t) %s%e%n. Available only when Console log handler is activated.
|
||||||
--log-console-output <output>
|
--log-console-output <output>
|
||||||
Set the log output to JSON or default (plain) unstructured logging. Possible
|
Set the log output to JSON or default (plain) unstructured logging. Possible
|
||||||
values are: default, json. Default: default.
|
values are: default, json. Default: default. Available only when Console log
|
||||||
--log-file <file> Set the log file path and filename. Default: data/log/keycloak.log.
|
handler is activated.
|
||||||
|
--log-file <file> Set the log file path and filename. Default: data/log/keycloak.log. Available
|
||||||
|
only when File log handler is activated.
|
||||||
--log-file-format <format>
|
--log-file-format <format>
|
||||||
Set a format specific to file log entries. Default: %d{yyyy-MM-dd HH:mm:ss,
|
Set a format specific to file log entries. Default: %d{yyyy-MM-dd HH:mm:ss,
|
||||||
SSS} %-5p [%c] (%t) %s%e%n.
|
SSS} %-5p [%c] (%t) %s%e%n. Available only when File log handler is
|
||||||
|
activated.
|
||||||
--log-file-output <output>
|
--log-file-output <output>
|
||||||
Set the log output to JSON or default (plain) unstructured logging. Possible
|
Set the log output to JSON or default (plain) unstructured logging. Possible
|
||||||
values are: default, json. Default: default.
|
values are: default, json. Default: default. Available only when File log
|
||||||
|
handler is activated.
|
||||||
--log-gelf-facility <name>
|
--log-gelf-facility <name>
|
||||||
DEPRECATED. The facility (name of the process) that sends the message.
|
DEPRECATED. The facility (name of the process) that sends the message.
|
||||||
Default: keycloak.
|
Default: keycloak. Available only when GELF is activated.
|
||||||
--log-gelf-host <hostname>
|
--log-gelf-host <hostname>
|
||||||
DEPRECATED. Hostname of the Logstash or Graylog Host. By default UDP is used,
|
DEPRECATED. Hostname of the Logstash or Graylog Host. By default UDP is used,
|
||||||
prefix the host with 'tcp:' to switch to TCP. Example: 'tcp:localhost'
|
prefix the host with 'tcp:' to switch to TCP. Example: 'tcp:localhost'
|
||||||
Default: localhost.
|
Default: localhost. Available only when GELF is activated.
|
||||||
--log-gelf-include-location <true|false>
|
--log-gelf-include-location <true|false>
|
||||||
DEPRECATED. Include source code location. Default: true.
|
DEPRECATED. Include source code location. Default: true. Available only when
|
||||||
|
GELF is activated.
|
||||||
--log-gelf-include-message-parameters <true|false>
|
--log-gelf-include-message-parameters <true|false>
|
||||||
DEPRECATED. Include message parameters from the log event. Default: true.
|
DEPRECATED. Include message parameters from the log event. Default: true.
|
||||||
|
Available only when GELF is activated.
|
||||||
--log-gelf-include-stack-trace <true|false>
|
--log-gelf-include-stack-trace <true|false>
|
||||||
DEPRECATED. If set to true, occuring stack traces are included in the
|
DEPRECATED. If set to true, occuring stack traces are included in the
|
||||||
'StackTrace' field in the GELF output. Default: true.
|
'StackTrace' field in the GELF output. Default: true. Available only when
|
||||||
|
GELF is activated.
|
||||||
--log-gelf-level <level>
|
--log-gelf-level <level>
|
||||||
DEPRECATED. The log level specifying which message levels will be logged by
|
DEPRECATED. The log level specifying which message levels will be logged by
|
||||||
the GELF logger. Message levels lower than this value will be discarded.
|
the GELF logger. Message levels lower than this value will be discarded.
|
||||||
Default: INFO.
|
Default: INFO. Available only when GELF is activated.
|
||||||
--log-gelf-max-message-size <size>
|
--log-gelf-max-message-size <size>
|
||||||
DEPRECATED. Maximum message size (in bytes). If the message size is exceeded,
|
DEPRECATED. Maximum message size (in bytes). If the message size is exceeded,
|
||||||
GELF will submit the message in multiple chunks. Default: 8192.
|
GELF will submit the message in multiple chunks. Default: 8192. Available
|
||||||
|
only when GELF is activated.
|
||||||
--log-gelf-port <port>
|
--log-gelf-port <port>
|
||||||
DEPRECATED. The port the Logstash or Graylog Host is called on. Default: 12201.
|
DEPRECATED. The port the Logstash or Graylog Host is called on. Default:
|
||||||
|
12201. Available only when GELF is activated.
|
||||||
--log-gelf-timestamp-format <pattern>
|
--log-gelf-timestamp-format <pattern>
|
||||||
DEPRECATED. Set the format for the GELF timestamp field. Uses Java
|
DEPRECATED. Set the format for the GELF timestamp field. Uses Java
|
||||||
SimpleDateFormat pattern. Default: yyyy-MM-dd HH:mm:ss,SSS.
|
SimpleDateFormat pattern. Default: yyyy-MM-dd HH:mm:ss,SSS. Available only
|
||||||
|
when GELF is activated.
|
||||||
|
--log-gelf-version <version>
|
||||||
|
The GELF version to be used. Possible values are: 1.0, 1.1. Default: 1.1.
|
||||||
|
Available only when GELF is activated.
|
||||||
--log-level <category:level>
|
--log-level <category:level>
|
||||||
The log level of the root category or a comma-separated list of individual
|
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
|
categories and their levels. For the root category, you don't need to
|
||||||
|
|
|
@ -74,47 +74,16 @@ Logging:
|
||||||
--log <handler> Enable one or more log handlers in a comma-separated list. Possible values
|
--log <handler> Enable one or more log handlers in a comma-separated list. Possible values
|
||||||
are: console, file, gelf (deprecated). Default: console.
|
are: console, file, gelf (deprecated). Default: console.
|
||||||
--log-console-color <true|false>
|
--log-console-color <true|false>
|
||||||
Enable or disable colors when logging to console. Default: false.
|
Enable or disable colors when logging to console. Default: false. Available
|
||||||
|
only when Console log handler is activated.
|
||||||
--log-console-format <format>
|
--log-console-format <format>
|
||||||
The format of unstructured console log entries. If the format has spaces in
|
The format of unstructured console log entries. If the format has spaces in
|
||||||
it, escape the value using "<format>". Default: %d{yyyy-MM-dd HH:mm:ss,SSS} %
|
it, escape the value using "<format>". Default: %d{yyyy-MM-dd HH:mm:ss,SSS} %
|
||||||
-5p [%c] (%t) %s%e%n.
|
-5p [%c] (%t) %s%e%n. Available only when Console log handler is activated.
|
||||||
--log-console-output <output>
|
--log-console-output <output>
|
||||||
Set the log output to JSON or default (plain) unstructured logging. Possible
|
Set the log output to JSON or default (plain) unstructured logging. Possible
|
||||||
values are: default, json. Default: default.
|
values are: default, json. Default: default. Available only when Console log
|
||||||
--log-file <file> Set the log file path and filename. Default: data/log/keycloak.log.
|
handler is activated.
|
||||||
--log-file-format <format>
|
|
||||||
Set a format specific to file log entries. Default: %d{yyyy-MM-dd HH:mm:ss,
|
|
||||||
SSS} %-5p [%c] (%t) %s%e%n.
|
|
||||||
--log-file-output <output>
|
|
||||||
Set the log output to JSON or default (plain) unstructured logging. Possible
|
|
||||||
values are: default, json. Default: default.
|
|
||||||
--log-gelf-facility <name>
|
|
||||||
DEPRECATED. The facility (name of the process) that sends the message.
|
|
||||||
Default: keycloak.
|
|
||||||
--log-gelf-host <hostname>
|
|
||||||
DEPRECATED. Hostname of the Logstash or Graylog Host. By default UDP is used,
|
|
||||||
prefix the host with 'tcp:' to switch to TCP. Example: 'tcp:localhost'
|
|
||||||
Default: localhost.
|
|
||||||
--log-gelf-include-location <true|false>
|
|
||||||
DEPRECATED. Include source code location. Default: true.
|
|
||||||
--log-gelf-include-message-parameters <true|false>
|
|
||||||
DEPRECATED. Include message parameters from the log event. Default: true.
|
|
||||||
--log-gelf-include-stack-trace <true|false>
|
|
||||||
DEPRECATED. If set to true, occuring stack traces are included in the
|
|
||||||
'StackTrace' field in the GELF output. Default: true.
|
|
||||||
--log-gelf-level <level>
|
|
||||||
DEPRECATED. The log level specifying which message levels will be logged by
|
|
||||||
the GELF logger. Message levels lower than this value will be discarded.
|
|
||||||
Default: INFO.
|
|
||||||
--log-gelf-max-message-size <size>
|
|
||||||
DEPRECATED. Maximum message size (in bytes). If the message size is exceeded,
|
|
||||||
GELF will submit the message in multiple chunks. Default: 8192.
|
|
||||||
--log-gelf-port <port>
|
|
||||||
DEPRECATED. The port the Logstash or Graylog Host is called on. Default: 12201.
|
|
||||||
--log-gelf-timestamp-format <pattern>
|
|
||||||
DEPRECATED. Set the format for the GELF timestamp field. Uses Java
|
|
||||||
SimpleDateFormat pattern. Default: yyyy-MM-dd HH:mm:ss,SSS.
|
|
||||||
--log-level <category:level>
|
--log-level <category:level>
|
||||||
The log level of the root category or a comma-separated list of individual
|
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
|
categories and their levels. For the root category, you don't need to
|
||||||
|
|
|
@ -74,47 +74,61 @@ Logging:
|
||||||
--log <handler> Enable one or more log handlers in a comma-separated list. Possible values
|
--log <handler> Enable one or more log handlers in a comma-separated list. Possible values
|
||||||
are: console, file, gelf (deprecated). Default: console.
|
are: console, file, gelf (deprecated). Default: console.
|
||||||
--log-console-color <true|false>
|
--log-console-color <true|false>
|
||||||
Enable or disable colors when logging to console. Default: false.
|
Enable or disable colors when logging to console. Default: false. Available
|
||||||
|
only when Console log handler is activated.
|
||||||
--log-console-format <format>
|
--log-console-format <format>
|
||||||
The format of unstructured console log entries. If the format has spaces in
|
The format of unstructured console log entries. If the format has spaces in
|
||||||
it, escape the value using "<format>". Default: %d{yyyy-MM-dd HH:mm:ss,SSS} %
|
it, escape the value using "<format>". Default: %d{yyyy-MM-dd HH:mm:ss,SSS} %
|
||||||
-5p [%c] (%t) %s%e%n.
|
-5p [%c] (%t) %s%e%n. Available only when Console log handler is activated.
|
||||||
--log-console-output <output>
|
--log-console-output <output>
|
||||||
Set the log output to JSON or default (plain) unstructured logging. Possible
|
Set the log output to JSON or default (plain) unstructured logging. Possible
|
||||||
values are: default, json. Default: default.
|
values are: default, json. Default: default. Available only when Console log
|
||||||
--log-file <file> Set the log file path and filename. Default: data/log/keycloak.log.
|
handler is activated.
|
||||||
|
--log-file <file> Set the log file path and filename. Default: data/log/keycloak.log. Available
|
||||||
|
only when File log handler is activated.
|
||||||
--log-file-format <format>
|
--log-file-format <format>
|
||||||
Set a format specific to file log entries. Default: %d{yyyy-MM-dd HH:mm:ss,
|
Set a format specific to file log entries. Default: %d{yyyy-MM-dd HH:mm:ss,
|
||||||
SSS} %-5p [%c] (%t) %s%e%n.
|
SSS} %-5p [%c] (%t) %s%e%n. Available only when File log handler is
|
||||||
|
activated.
|
||||||
--log-file-output <output>
|
--log-file-output <output>
|
||||||
Set the log output to JSON or default (plain) unstructured logging. Possible
|
Set the log output to JSON or default (plain) unstructured logging. Possible
|
||||||
values are: default, json. Default: default.
|
values are: default, json. Default: default. Available only when File log
|
||||||
|
handler is activated.
|
||||||
--log-gelf-facility <name>
|
--log-gelf-facility <name>
|
||||||
DEPRECATED. The facility (name of the process) that sends the message.
|
DEPRECATED. The facility (name of the process) that sends the message.
|
||||||
Default: keycloak.
|
Default: keycloak. Available only when GELF is activated.
|
||||||
--log-gelf-host <hostname>
|
--log-gelf-host <hostname>
|
||||||
DEPRECATED. Hostname of the Logstash or Graylog Host. By default UDP is used,
|
DEPRECATED. Hostname of the Logstash or Graylog Host. By default UDP is used,
|
||||||
prefix the host with 'tcp:' to switch to TCP. Example: 'tcp:localhost'
|
prefix the host with 'tcp:' to switch to TCP. Example: 'tcp:localhost'
|
||||||
Default: localhost.
|
Default: localhost. Available only when GELF is activated.
|
||||||
--log-gelf-include-location <true|false>
|
--log-gelf-include-location <true|false>
|
||||||
DEPRECATED. Include source code location. Default: true.
|
DEPRECATED. Include source code location. Default: true. Available only when
|
||||||
|
GELF is activated.
|
||||||
--log-gelf-include-message-parameters <true|false>
|
--log-gelf-include-message-parameters <true|false>
|
||||||
DEPRECATED. Include message parameters from the log event. Default: true.
|
DEPRECATED. Include message parameters from the log event. Default: true.
|
||||||
|
Available only when GELF is activated.
|
||||||
--log-gelf-include-stack-trace <true|false>
|
--log-gelf-include-stack-trace <true|false>
|
||||||
DEPRECATED. If set to true, occuring stack traces are included in the
|
DEPRECATED. If set to true, occuring stack traces are included in the
|
||||||
'StackTrace' field in the GELF output. Default: true.
|
'StackTrace' field in the GELF output. Default: true. Available only when
|
||||||
|
GELF is activated.
|
||||||
--log-gelf-level <level>
|
--log-gelf-level <level>
|
||||||
DEPRECATED. The log level specifying which message levels will be logged by
|
DEPRECATED. The log level specifying which message levels will be logged by
|
||||||
the GELF logger. Message levels lower than this value will be discarded.
|
the GELF logger. Message levels lower than this value will be discarded.
|
||||||
Default: INFO.
|
Default: INFO. Available only when GELF is activated.
|
||||||
--log-gelf-max-message-size <size>
|
--log-gelf-max-message-size <size>
|
||||||
DEPRECATED. Maximum message size (in bytes). If the message size is exceeded,
|
DEPRECATED. Maximum message size (in bytes). If the message size is exceeded,
|
||||||
GELF will submit the message in multiple chunks. Default: 8192.
|
GELF will submit the message in multiple chunks. Default: 8192. Available
|
||||||
|
only when GELF is activated.
|
||||||
--log-gelf-port <port>
|
--log-gelf-port <port>
|
||||||
DEPRECATED. The port the Logstash or Graylog Host is called on. Default: 12201.
|
DEPRECATED. The port the Logstash or Graylog Host is called on. Default:
|
||||||
|
12201. Available only when GELF is activated.
|
||||||
--log-gelf-timestamp-format <pattern>
|
--log-gelf-timestamp-format <pattern>
|
||||||
DEPRECATED. Set the format for the GELF timestamp field. Uses Java
|
DEPRECATED. Set the format for the GELF timestamp field. Uses Java
|
||||||
SimpleDateFormat pattern. Default: yyyy-MM-dd HH:mm:ss,SSS.
|
SimpleDateFormat pattern. Default: yyyy-MM-dd HH:mm:ss,SSS. Available only
|
||||||
|
when GELF is activated.
|
||||||
|
--log-gelf-version <version>
|
||||||
|
The GELF version to be used. Possible values are: 1.0, 1.1. Default: 1.1.
|
||||||
|
Available only when GELF is activated.
|
||||||
--log-level <category:level>
|
--log-level <category:level>
|
||||||
The log level of the root category or a comma-separated list of individual
|
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
|
categories and their levels. For the root category, you don't need to
|
||||||
|
|
|
@ -238,47 +238,16 @@ Logging:
|
||||||
--log <handler> Enable one or more log handlers in a comma-separated list. Possible values
|
--log <handler> Enable one or more log handlers in a comma-separated list. Possible values
|
||||||
are: console, file, gelf (deprecated). Default: console.
|
are: console, file, gelf (deprecated). Default: console.
|
||||||
--log-console-color <true|false>
|
--log-console-color <true|false>
|
||||||
Enable or disable colors when logging to console. Default: false.
|
Enable or disable colors when logging to console. Default: false. Available
|
||||||
|
only when Console log handler is activated.
|
||||||
--log-console-format <format>
|
--log-console-format <format>
|
||||||
The format of unstructured console log entries. If the format has spaces in
|
The format of unstructured console log entries. If the format has spaces in
|
||||||
it, escape the value using "<format>". Default: %d{yyyy-MM-dd HH:mm:ss,SSS} %
|
it, escape the value using "<format>". Default: %d{yyyy-MM-dd HH:mm:ss,SSS} %
|
||||||
-5p [%c] (%t) %s%e%n.
|
-5p [%c] (%t) %s%e%n. Available only when Console log handler is activated.
|
||||||
--log-console-output <output>
|
--log-console-output <output>
|
||||||
Set the log output to JSON or default (plain) unstructured logging. Possible
|
Set the log output to JSON or default (plain) unstructured logging. Possible
|
||||||
values are: default, json. Default: default.
|
values are: default, json. Default: default. Available only when Console log
|
||||||
--log-file <file> Set the log file path and filename. Default: data/log/keycloak.log.
|
handler is activated.
|
||||||
--log-file-format <format>
|
|
||||||
Set a format specific to file log entries. Default: %d{yyyy-MM-dd HH:mm:ss,
|
|
||||||
SSS} %-5p [%c] (%t) %s%e%n.
|
|
||||||
--log-file-output <output>
|
|
||||||
Set the log output to JSON or default (plain) unstructured logging. Possible
|
|
||||||
values are: default, json. Default: default.
|
|
||||||
--log-gelf-facility <name>
|
|
||||||
DEPRECATED. The facility (name of the process) that sends the message.
|
|
||||||
Default: keycloak.
|
|
||||||
--log-gelf-host <hostname>
|
|
||||||
DEPRECATED. Hostname of the Logstash or Graylog Host. By default UDP is used,
|
|
||||||
prefix the host with 'tcp:' to switch to TCP. Example: 'tcp:localhost'
|
|
||||||
Default: localhost.
|
|
||||||
--log-gelf-include-location <true|false>
|
|
||||||
DEPRECATED. Include source code location. Default: true.
|
|
||||||
--log-gelf-include-message-parameters <true|false>
|
|
||||||
DEPRECATED. Include message parameters from the log event. Default: true.
|
|
||||||
--log-gelf-include-stack-trace <true|false>
|
|
||||||
DEPRECATED. If set to true, occuring stack traces are included in the
|
|
||||||
'StackTrace' field in the GELF output. Default: true.
|
|
||||||
--log-gelf-level <level>
|
|
||||||
DEPRECATED. The log level specifying which message levels will be logged by
|
|
||||||
the GELF logger. Message levels lower than this value will be discarded.
|
|
||||||
Default: INFO.
|
|
||||||
--log-gelf-max-message-size <size>
|
|
||||||
DEPRECATED. Maximum message size (in bytes). If the message size is exceeded,
|
|
||||||
GELF will submit the message in multiple chunks. Default: 8192.
|
|
||||||
--log-gelf-port <port>
|
|
||||||
DEPRECATED. The port the Logstash or Graylog Host is called on. Default: 12201.
|
|
||||||
--log-gelf-timestamp-format <pattern>
|
|
||||||
DEPRECATED. Set the format for the GELF timestamp field. Uses Java
|
|
||||||
SimpleDateFormat pattern. Default: yyyy-MM-dd HH:mm:ss,SSS.
|
|
||||||
--log-level <category:level>
|
--log-level <category:level>
|
||||||
The log level of the root category or a comma-separated list of individual
|
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
|
categories and their levels. For the root category, you don't need to
|
||||||
|
@ -305,4 +274,4 @@ Security:
|
||||||
Do NOT start the server using this command when deploying to production.
|
Do NOT start the server using this command when deploying to production.
|
||||||
|
|
||||||
Use 'kc.sh start-dev --help-all' to list all available options, including build
|
Use 'kc.sh start-dev --help-all' to list all available options, including build
|
||||||
options.
|
options.
|
|
@ -238,47 +238,61 @@ Logging:
|
||||||
--log <handler> Enable one or more log handlers in a comma-separated list. Possible values
|
--log <handler> Enable one or more log handlers in a comma-separated list. Possible values
|
||||||
are: console, file, gelf (deprecated). Default: console.
|
are: console, file, gelf (deprecated). Default: console.
|
||||||
--log-console-color <true|false>
|
--log-console-color <true|false>
|
||||||
Enable or disable colors when logging to console. Default: false.
|
Enable or disable colors when logging to console. Default: false. Available
|
||||||
|
only when Console log handler is activated.
|
||||||
--log-console-format <format>
|
--log-console-format <format>
|
||||||
The format of unstructured console log entries. If the format has spaces in
|
The format of unstructured console log entries. If the format has spaces in
|
||||||
it, escape the value using "<format>". Default: %d{yyyy-MM-dd HH:mm:ss,SSS} %
|
it, escape the value using "<format>". Default: %d{yyyy-MM-dd HH:mm:ss,SSS} %
|
||||||
-5p [%c] (%t) %s%e%n.
|
-5p [%c] (%t) %s%e%n. Available only when Console log handler is activated.
|
||||||
--log-console-output <output>
|
--log-console-output <output>
|
||||||
Set the log output to JSON or default (plain) unstructured logging. Possible
|
Set the log output to JSON or default (plain) unstructured logging. Possible
|
||||||
values are: default, json. Default: default.
|
values are: default, json. Default: default. Available only when Console log
|
||||||
--log-file <file> Set the log file path and filename. Default: data/log/keycloak.log.
|
handler is activated.
|
||||||
|
--log-file <file> Set the log file path and filename. Default: data/log/keycloak.log. Available
|
||||||
|
only when File log handler is activated.
|
||||||
--log-file-format <format>
|
--log-file-format <format>
|
||||||
Set a format specific to file log entries. Default: %d{yyyy-MM-dd HH:mm:ss,
|
Set a format specific to file log entries. Default: %d{yyyy-MM-dd HH:mm:ss,
|
||||||
SSS} %-5p [%c] (%t) %s%e%n.
|
SSS} %-5p [%c] (%t) %s%e%n. Available only when File log handler is
|
||||||
|
activated.
|
||||||
--log-file-output <output>
|
--log-file-output <output>
|
||||||
Set the log output to JSON or default (plain) unstructured logging. Possible
|
Set the log output to JSON or default (plain) unstructured logging. Possible
|
||||||
values are: default, json. Default: default.
|
values are: default, json. Default: default. Available only when File log
|
||||||
|
handler is activated.
|
||||||
--log-gelf-facility <name>
|
--log-gelf-facility <name>
|
||||||
DEPRECATED. The facility (name of the process) that sends the message.
|
DEPRECATED. The facility (name of the process) that sends the message.
|
||||||
Default: keycloak.
|
Default: keycloak. Available only when GELF is activated.
|
||||||
--log-gelf-host <hostname>
|
--log-gelf-host <hostname>
|
||||||
DEPRECATED. Hostname of the Logstash or Graylog Host. By default UDP is used,
|
DEPRECATED. Hostname of the Logstash or Graylog Host. By default UDP is used,
|
||||||
prefix the host with 'tcp:' to switch to TCP. Example: 'tcp:localhost'
|
prefix the host with 'tcp:' to switch to TCP. Example: 'tcp:localhost'
|
||||||
Default: localhost.
|
Default: localhost. Available only when GELF is activated.
|
||||||
--log-gelf-include-location <true|false>
|
--log-gelf-include-location <true|false>
|
||||||
DEPRECATED. Include source code location. Default: true.
|
DEPRECATED. Include source code location. Default: true. Available only when
|
||||||
|
GELF is activated.
|
||||||
--log-gelf-include-message-parameters <true|false>
|
--log-gelf-include-message-parameters <true|false>
|
||||||
DEPRECATED. Include message parameters from the log event. Default: true.
|
DEPRECATED. Include message parameters from the log event. Default: true.
|
||||||
|
Available only when GELF is activated.
|
||||||
--log-gelf-include-stack-trace <true|false>
|
--log-gelf-include-stack-trace <true|false>
|
||||||
DEPRECATED. If set to true, occuring stack traces are included in the
|
DEPRECATED. If set to true, occuring stack traces are included in the
|
||||||
'StackTrace' field in the GELF output. Default: true.
|
'StackTrace' field in the GELF output. Default: true. Available only when
|
||||||
|
GELF is activated.
|
||||||
--log-gelf-level <level>
|
--log-gelf-level <level>
|
||||||
DEPRECATED. The log level specifying which message levels will be logged by
|
DEPRECATED. The log level specifying which message levels will be logged by
|
||||||
the GELF logger. Message levels lower than this value will be discarded.
|
the GELF logger. Message levels lower than this value will be discarded.
|
||||||
Default: INFO.
|
Default: INFO. Available only when GELF is activated.
|
||||||
--log-gelf-max-message-size <size>
|
--log-gelf-max-message-size <size>
|
||||||
DEPRECATED. Maximum message size (in bytes). If the message size is exceeded,
|
DEPRECATED. Maximum message size (in bytes). If the message size is exceeded,
|
||||||
GELF will submit the message in multiple chunks. Default: 8192.
|
GELF will submit the message in multiple chunks. Default: 8192. Available
|
||||||
|
only when GELF is activated.
|
||||||
--log-gelf-port <port>
|
--log-gelf-port <port>
|
||||||
DEPRECATED. The port the Logstash or Graylog Host is called on. Default: 12201.
|
DEPRECATED. The port the Logstash or Graylog Host is called on. Default:
|
||||||
|
12201. Available only when GELF is activated.
|
||||||
--log-gelf-timestamp-format <pattern>
|
--log-gelf-timestamp-format <pattern>
|
||||||
DEPRECATED. Set the format for the GELF timestamp field. Uses Java
|
DEPRECATED. Set the format for the GELF timestamp field. Uses Java
|
||||||
SimpleDateFormat pattern. Default: yyyy-MM-dd HH:mm:ss,SSS.
|
SimpleDateFormat pattern. Default: yyyy-MM-dd HH:mm:ss,SSS. Available only
|
||||||
|
when GELF is activated.
|
||||||
|
--log-gelf-version <version>
|
||||||
|
The GELF version to be used. Possible values are: 1.0, 1.1. Default: 1.1.
|
||||||
|
Available only when GELF is activated.
|
||||||
--log-level <category:level>
|
--log-level <category:level>
|
||||||
The log level of the root category or a comma-separated list of individual
|
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
|
categories and their levels. For the root category, you don't need to
|
||||||
|
@ -305,4 +319,4 @@ Security:
|
||||||
Do NOT start the server using this command when deploying to production.
|
Do NOT start the server using this command when deploying to production.
|
||||||
|
|
||||||
Use 'kc.sh start-dev --help-all' to list all available options, including build
|
Use 'kc.sh start-dev --help-all' to list all available options, including build
|
||||||
options.
|
options.
|
|
@ -239,47 +239,16 @@ Logging:
|
||||||
--log <handler> Enable one or more log handlers in a comma-separated list. Possible values
|
--log <handler> Enable one or more log handlers in a comma-separated list. Possible values
|
||||||
are: console, file, gelf (deprecated). Default: console.
|
are: console, file, gelf (deprecated). Default: console.
|
||||||
--log-console-color <true|false>
|
--log-console-color <true|false>
|
||||||
Enable or disable colors when logging to console. Default: false.
|
Enable or disable colors when logging to console. Default: false. Available
|
||||||
|
only when Console log handler is activated.
|
||||||
--log-console-format <format>
|
--log-console-format <format>
|
||||||
The format of unstructured console log entries. If the format has spaces in
|
The format of unstructured console log entries. If the format has spaces in
|
||||||
it, escape the value using "<format>". Default: %d{yyyy-MM-dd HH:mm:ss,SSS} %
|
it, escape the value using "<format>". Default: %d{yyyy-MM-dd HH:mm:ss,SSS} %
|
||||||
-5p [%c] (%t) %s%e%n.
|
-5p [%c] (%t) %s%e%n. Available only when Console log handler is activated.
|
||||||
--log-console-output <output>
|
--log-console-output <output>
|
||||||
Set the log output to JSON or default (plain) unstructured logging. Possible
|
Set the log output to JSON or default (plain) unstructured logging. Possible
|
||||||
values are: default, json. Default: default.
|
values are: default, json. Default: default. Available only when Console log
|
||||||
--log-file <file> Set the log file path and filename. Default: data/log/keycloak.log.
|
handler is activated.
|
||||||
--log-file-format <format>
|
|
||||||
Set a format specific to file log entries. Default: %d{yyyy-MM-dd HH:mm:ss,
|
|
||||||
SSS} %-5p [%c] (%t) %s%e%n.
|
|
||||||
--log-file-output <output>
|
|
||||||
Set the log output to JSON or default (plain) unstructured logging. Possible
|
|
||||||
values are: default, json. Default: default.
|
|
||||||
--log-gelf-facility <name>
|
|
||||||
DEPRECATED. The facility (name of the process) that sends the message.
|
|
||||||
Default: keycloak.
|
|
||||||
--log-gelf-host <hostname>
|
|
||||||
DEPRECATED. Hostname of the Logstash or Graylog Host. By default UDP is used,
|
|
||||||
prefix the host with 'tcp:' to switch to TCP. Example: 'tcp:localhost'
|
|
||||||
Default: localhost.
|
|
||||||
--log-gelf-include-location <true|false>
|
|
||||||
DEPRECATED. Include source code location. Default: true.
|
|
||||||
--log-gelf-include-message-parameters <true|false>
|
|
||||||
DEPRECATED. Include message parameters from the log event. Default: true.
|
|
||||||
--log-gelf-include-stack-trace <true|false>
|
|
||||||
DEPRECATED. If set to true, occuring stack traces are included in the
|
|
||||||
'StackTrace' field in the GELF output. Default: true.
|
|
||||||
--log-gelf-level <level>
|
|
||||||
DEPRECATED. The log level specifying which message levels will be logged by
|
|
||||||
the GELF logger. Message levels lower than this value will be discarded.
|
|
||||||
Default: INFO.
|
|
||||||
--log-gelf-max-message-size <size>
|
|
||||||
DEPRECATED. Maximum message size (in bytes). If the message size is exceeded,
|
|
||||||
GELF will submit the message in multiple chunks. Default: 8192.
|
|
||||||
--log-gelf-port <port>
|
|
||||||
DEPRECATED. The port the Logstash or Graylog Host is called on. Default: 12201.
|
|
||||||
--log-gelf-timestamp-format <pattern>
|
|
||||||
DEPRECATED. Set the format for the GELF timestamp field. Uses Java
|
|
||||||
SimpleDateFormat pattern. Default: yyyy-MM-dd HH:mm:ss,SSS.
|
|
||||||
--log-level <category:level>
|
--log-level <category:level>
|
||||||
The log level of the root category or a comma-separated list of individual
|
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
|
categories and their levels. For the root category, you don't need to
|
||||||
|
@ -310,4 +279,4 @@ By default, this command tries to update the server configuration by running a
|
||||||
$ kc.sh start '--optimized'
|
$ kc.sh start '--optimized'
|
||||||
|
|
||||||
By doing that, the server should start faster based on any previous
|
By doing that, the server should start faster based on any previous
|
||||||
configuration you have set when manually running the 'build' command.
|
configuration you have set when manually running the 'build' command.
|
|
@ -239,47 +239,61 @@ Logging:
|
||||||
--log <handler> Enable one or more log handlers in a comma-separated list. Possible values
|
--log <handler> Enable one or more log handlers in a comma-separated list. Possible values
|
||||||
are: console, file, gelf (deprecated). Default: console.
|
are: console, file, gelf (deprecated). Default: console.
|
||||||
--log-console-color <true|false>
|
--log-console-color <true|false>
|
||||||
Enable or disable colors when logging to console. Default: false.
|
Enable or disable colors when logging to console. Default: false. Available
|
||||||
|
only when Console log handler is activated.
|
||||||
--log-console-format <format>
|
--log-console-format <format>
|
||||||
The format of unstructured console log entries. If the format has spaces in
|
The format of unstructured console log entries. If the format has spaces in
|
||||||
it, escape the value using "<format>". Default: %d{yyyy-MM-dd HH:mm:ss,SSS} %
|
it, escape the value using "<format>". Default: %d{yyyy-MM-dd HH:mm:ss,SSS} %
|
||||||
-5p [%c] (%t) %s%e%n.
|
-5p [%c] (%t) %s%e%n. Available only when Console log handler is activated.
|
||||||
--log-console-output <output>
|
--log-console-output <output>
|
||||||
Set the log output to JSON or default (plain) unstructured logging. Possible
|
Set the log output to JSON or default (plain) unstructured logging. Possible
|
||||||
values are: default, json. Default: default.
|
values are: default, json. Default: default. Available only when Console log
|
||||||
--log-file <file> Set the log file path and filename. Default: data/log/keycloak.log.
|
handler is activated.
|
||||||
|
--log-file <file> Set the log file path and filename. Default: data/log/keycloak.log. Available
|
||||||
|
only when File log handler is activated.
|
||||||
--log-file-format <format>
|
--log-file-format <format>
|
||||||
Set a format specific to file log entries. Default: %d{yyyy-MM-dd HH:mm:ss,
|
Set a format specific to file log entries. Default: %d{yyyy-MM-dd HH:mm:ss,
|
||||||
SSS} %-5p [%c] (%t) %s%e%n.
|
SSS} %-5p [%c] (%t) %s%e%n. Available only when File log handler is
|
||||||
|
activated.
|
||||||
--log-file-output <output>
|
--log-file-output <output>
|
||||||
Set the log output to JSON or default (plain) unstructured logging. Possible
|
Set the log output to JSON or default (plain) unstructured logging. Possible
|
||||||
values are: default, json. Default: default.
|
values are: default, json. Default: default. Available only when File log
|
||||||
|
handler is activated.
|
||||||
--log-gelf-facility <name>
|
--log-gelf-facility <name>
|
||||||
DEPRECATED. The facility (name of the process) that sends the message.
|
DEPRECATED. The facility (name of the process) that sends the message.
|
||||||
Default: keycloak.
|
Default: keycloak. Available only when GELF is activated.
|
||||||
--log-gelf-host <hostname>
|
--log-gelf-host <hostname>
|
||||||
DEPRECATED. Hostname of the Logstash or Graylog Host. By default UDP is used,
|
DEPRECATED. Hostname of the Logstash or Graylog Host. By default UDP is used,
|
||||||
prefix the host with 'tcp:' to switch to TCP. Example: 'tcp:localhost'
|
prefix the host with 'tcp:' to switch to TCP. Example: 'tcp:localhost'
|
||||||
Default: localhost.
|
Default: localhost. Available only when GELF is activated.
|
||||||
--log-gelf-include-location <true|false>
|
--log-gelf-include-location <true|false>
|
||||||
DEPRECATED. Include source code location. Default: true.
|
DEPRECATED. Include source code location. Default: true. Available only when
|
||||||
|
GELF is activated.
|
||||||
--log-gelf-include-message-parameters <true|false>
|
--log-gelf-include-message-parameters <true|false>
|
||||||
DEPRECATED. Include message parameters from the log event. Default: true.
|
DEPRECATED. Include message parameters from the log event. Default: true.
|
||||||
|
Available only when GELF is activated.
|
||||||
--log-gelf-include-stack-trace <true|false>
|
--log-gelf-include-stack-trace <true|false>
|
||||||
DEPRECATED. If set to true, occuring stack traces are included in the
|
DEPRECATED. If set to true, occuring stack traces are included in the
|
||||||
'StackTrace' field in the GELF output. Default: true.
|
'StackTrace' field in the GELF output. Default: true. Available only when
|
||||||
|
GELF is activated.
|
||||||
--log-gelf-level <level>
|
--log-gelf-level <level>
|
||||||
DEPRECATED. The log level specifying which message levels will be logged by
|
DEPRECATED. The log level specifying which message levels will be logged by
|
||||||
the GELF logger. Message levels lower than this value will be discarded.
|
the GELF logger. Message levels lower than this value will be discarded.
|
||||||
Default: INFO.
|
Default: INFO. Available only when GELF is activated.
|
||||||
--log-gelf-max-message-size <size>
|
--log-gelf-max-message-size <size>
|
||||||
DEPRECATED. Maximum message size (in bytes). If the message size is exceeded,
|
DEPRECATED. Maximum message size (in bytes). If the message size is exceeded,
|
||||||
GELF will submit the message in multiple chunks. Default: 8192.
|
GELF will submit the message in multiple chunks. Default: 8192. Available
|
||||||
|
only when GELF is activated.
|
||||||
--log-gelf-port <port>
|
--log-gelf-port <port>
|
||||||
DEPRECATED. The port the Logstash or Graylog Host is called on. Default: 12201.
|
DEPRECATED. The port the Logstash or Graylog Host is called on. Default:
|
||||||
|
12201. Available only when GELF is activated.
|
||||||
--log-gelf-timestamp-format <pattern>
|
--log-gelf-timestamp-format <pattern>
|
||||||
DEPRECATED. Set the format for the GELF timestamp field. Uses Java
|
DEPRECATED. Set the format for the GELF timestamp field. Uses Java
|
||||||
SimpleDateFormat pattern. Default: yyyy-MM-dd HH:mm:ss,SSS.
|
SimpleDateFormat pattern. Default: yyyy-MM-dd HH:mm:ss,SSS. Available only
|
||||||
|
when GELF is activated.
|
||||||
|
--log-gelf-version <version>
|
||||||
|
The GELF version to be used. Possible values are: 1.0, 1.1. Default: 1.1.
|
||||||
|
Available only when GELF is activated.
|
||||||
--log-level <category:level>
|
--log-level <category:level>
|
||||||
The log level of the root category or a comma-separated list of individual
|
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
|
categories and their levels. For the root category, you don't need to
|
||||||
|
@ -310,4 +324,4 @@ By default, this command tries to update the server configuration by running a
|
||||||
$ kc.sh start '--optimized'
|
$ kc.sh start '--optimized'
|
||||||
|
|
||||||
By doing that, the server should start faster based on any previous
|
By doing that, the server should start faster based on any previous
|
||||||
configuration you have set when manually running the 'build' command.
|
configuration you have set when manually running the 'build' command.
|
|
@ -177,47 +177,16 @@ Logging:
|
||||||
--log <handler> Enable one or more log handlers in a comma-separated list. Possible values
|
--log <handler> Enable one or more log handlers in a comma-separated list. Possible values
|
||||||
are: console, file, gelf (deprecated). Default: console.
|
are: console, file, gelf (deprecated). Default: console.
|
||||||
--log-console-color <true|false>
|
--log-console-color <true|false>
|
||||||
Enable or disable colors when logging to console. Default: false.
|
Enable or disable colors when logging to console. Default: false. Available
|
||||||
|
only when Console log handler is activated.
|
||||||
--log-console-format <format>
|
--log-console-format <format>
|
||||||
The format of unstructured console log entries. If the format has spaces in
|
The format of unstructured console log entries. If the format has spaces in
|
||||||
it, escape the value using "<format>". Default: %d{yyyy-MM-dd HH:mm:ss,SSS} %
|
it, escape the value using "<format>". Default: %d{yyyy-MM-dd HH:mm:ss,SSS} %
|
||||||
-5p [%c] (%t) %s%e%n.
|
-5p [%c] (%t) %s%e%n. Available only when Console log handler is activated.
|
||||||
--log-console-output <output>
|
--log-console-output <output>
|
||||||
Set the log output to JSON or default (plain) unstructured logging. Possible
|
Set the log output to JSON or default (plain) unstructured logging. Possible
|
||||||
values are: default, json. Default: default.
|
values are: default, json. Default: default. Available only when Console log
|
||||||
--log-file <file> Set the log file path and filename. Default: data/log/keycloak.log.
|
handler is activated.
|
||||||
--log-file-format <format>
|
|
||||||
Set a format specific to file log entries. Default: %d{yyyy-MM-dd HH:mm:ss,
|
|
||||||
SSS} %-5p [%c] (%t) %s%e%n.
|
|
||||||
--log-file-output <output>
|
|
||||||
Set the log output to JSON or default (plain) unstructured logging. Possible
|
|
||||||
values are: default, json. Default: default.
|
|
||||||
--log-gelf-facility <name>
|
|
||||||
DEPRECATED. The facility (name of the process) that sends the message.
|
|
||||||
Default: keycloak.
|
|
||||||
--log-gelf-host <hostname>
|
|
||||||
DEPRECATED. Hostname of the Logstash or Graylog Host. By default UDP is used,
|
|
||||||
prefix the host with 'tcp:' to switch to TCP. Example: 'tcp:localhost'
|
|
||||||
Default: localhost.
|
|
||||||
--log-gelf-include-location <true|false>
|
|
||||||
DEPRECATED. Include source code location. Default: true.
|
|
||||||
--log-gelf-include-message-parameters <true|false>
|
|
||||||
DEPRECATED. Include message parameters from the log event. Default: true.
|
|
||||||
--log-gelf-include-stack-trace <true|false>
|
|
||||||
DEPRECATED. If set to true, occuring stack traces are included in the
|
|
||||||
'StackTrace' field in the GELF output. Default: true.
|
|
||||||
--log-gelf-level <level>
|
|
||||||
DEPRECATED. The log level specifying which message levels will be logged by
|
|
||||||
the GELF logger. Message levels lower than this value will be discarded.
|
|
||||||
Default: INFO.
|
|
||||||
--log-gelf-max-message-size <size>
|
|
||||||
DEPRECATED. Maximum message size (in bytes). If the message size is exceeded,
|
|
||||||
GELF will submit the message in multiple chunks. Default: 8192.
|
|
||||||
--log-gelf-port <port>
|
|
||||||
DEPRECATED. The port the Logstash or Graylog Host is called on. Default: 12201.
|
|
||||||
--log-gelf-timestamp-format <pattern>
|
|
||||||
DEPRECATED. Set the format for the GELF timestamp field. Uses Java
|
|
||||||
SimpleDateFormat pattern. Default: yyyy-MM-dd HH:mm:ss,SSS.
|
|
||||||
--log-level <category:level>
|
--log-level <category:level>
|
||||||
The log level of the root category or a comma-separated list of individual
|
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
|
categories and their levels. For the root category, you don't need to
|
||||||
|
@ -239,4 +208,4 @@ By default, this command tries to update the server configuration by running a
|
||||||
$ kc.sh start '--optimized'
|
$ kc.sh start '--optimized'
|
||||||
|
|
||||||
By doing that, the server should start faster based on any previous
|
By doing that, the server should start faster based on any previous
|
||||||
configuration you have set when manually running the 'build' command.
|
configuration you have set when manually running the 'build' command.
|
|
@ -177,47 +177,61 @@ Logging:
|
||||||
--log <handler> Enable one or more log handlers in a comma-separated list. Possible values
|
--log <handler> Enable one or more log handlers in a comma-separated list. Possible values
|
||||||
are: console, file, gelf (deprecated). Default: console.
|
are: console, file, gelf (deprecated). Default: console.
|
||||||
--log-console-color <true|false>
|
--log-console-color <true|false>
|
||||||
Enable or disable colors when logging to console. Default: false.
|
Enable or disable colors when logging to console. Default: false. Available
|
||||||
|
only when Console log handler is activated.
|
||||||
--log-console-format <format>
|
--log-console-format <format>
|
||||||
The format of unstructured console log entries. If the format has spaces in
|
The format of unstructured console log entries. If the format has spaces in
|
||||||
it, escape the value using "<format>". Default: %d{yyyy-MM-dd HH:mm:ss,SSS} %
|
it, escape the value using "<format>". Default: %d{yyyy-MM-dd HH:mm:ss,SSS} %
|
||||||
-5p [%c] (%t) %s%e%n.
|
-5p [%c] (%t) %s%e%n. Available only when Console log handler is activated.
|
||||||
--log-console-output <output>
|
--log-console-output <output>
|
||||||
Set the log output to JSON or default (plain) unstructured logging. Possible
|
Set the log output to JSON or default (plain) unstructured logging. Possible
|
||||||
values are: default, json. Default: default.
|
values are: default, json. Default: default. Available only when Console log
|
||||||
--log-file <file> Set the log file path and filename. Default: data/log/keycloak.log.
|
handler is activated.
|
||||||
|
--log-file <file> Set the log file path and filename. Default: data/log/keycloak.log. Available
|
||||||
|
only when File log handler is activated.
|
||||||
--log-file-format <format>
|
--log-file-format <format>
|
||||||
Set a format specific to file log entries. Default: %d{yyyy-MM-dd HH:mm:ss,
|
Set a format specific to file log entries. Default: %d{yyyy-MM-dd HH:mm:ss,
|
||||||
SSS} %-5p [%c] (%t) %s%e%n.
|
SSS} %-5p [%c] (%t) %s%e%n. Available only when File log handler is
|
||||||
|
activated.
|
||||||
--log-file-output <output>
|
--log-file-output <output>
|
||||||
Set the log output to JSON or default (plain) unstructured logging. Possible
|
Set the log output to JSON or default (plain) unstructured logging. Possible
|
||||||
values are: default, json. Default: default.
|
values are: default, json. Default: default. Available only when File log
|
||||||
|
handler is activated.
|
||||||
--log-gelf-facility <name>
|
--log-gelf-facility <name>
|
||||||
DEPRECATED. The facility (name of the process) that sends the message.
|
DEPRECATED. The facility (name of the process) that sends the message.
|
||||||
Default: keycloak.
|
Default: keycloak. Available only when GELF is activated.
|
||||||
--log-gelf-host <hostname>
|
--log-gelf-host <hostname>
|
||||||
DEPRECATED. Hostname of the Logstash or Graylog Host. By default UDP is used,
|
DEPRECATED. Hostname of the Logstash or Graylog Host. By default UDP is used,
|
||||||
prefix the host with 'tcp:' to switch to TCP. Example: 'tcp:localhost'
|
prefix the host with 'tcp:' to switch to TCP. Example: 'tcp:localhost'
|
||||||
Default: localhost.
|
Default: localhost. Available only when GELF is activated.
|
||||||
--log-gelf-include-location <true|false>
|
--log-gelf-include-location <true|false>
|
||||||
DEPRECATED. Include source code location. Default: true.
|
DEPRECATED. Include source code location. Default: true. Available only when
|
||||||
|
GELF is activated.
|
||||||
--log-gelf-include-message-parameters <true|false>
|
--log-gelf-include-message-parameters <true|false>
|
||||||
DEPRECATED. Include message parameters from the log event. Default: true.
|
DEPRECATED. Include message parameters from the log event. Default: true.
|
||||||
|
Available only when GELF is activated.
|
||||||
--log-gelf-include-stack-trace <true|false>
|
--log-gelf-include-stack-trace <true|false>
|
||||||
DEPRECATED. If set to true, occuring stack traces are included in the
|
DEPRECATED. If set to true, occuring stack traces are included in the
|
||||||
'StackTrace' field in the GELF output. Default: true.
|
'StackTrace' field in the GELF output. Default: true. Available only when
|
||||||
|
GELF is activated.
|
||||||
--log-gelf-level <level>
|
--log-gelf-level <level>
|
||||||
DEPRECATED. The log level specifying which message levels will be logged by
|
DEPRECATED. The log level specifying which message levels will be logged by
|
||||||
the GELF logger. Message levels lower than this value will be discarded.
|
the GELF logger. Message levels lower than this value will be discarded.
|
||||||
Default: INFO.
|
Default: INFO. Available only when GELF is activated.
|
||||||
--log-gelf-max-message-size <size>
|
--log-gelf-max-message-size <size>
|
||||||
DEPRECATED. Maximum message size (in bytes). If the message size is exceeded,
|
DEPRECATED. Maximum message size (in bytes). If the message size is exceeded,
|
||||||
GELF will submit the message in multiple chunks. Default: 8192.
|
GELF will submit the message in multiple chunks. Default: 8192. Available
|
||||||
|
only when GELF is activated.
|
||||||
--log-gelf-port <port>
|
--log-gelf-port <port>
|
||||||
DEPRECATED. The port the Logstash or Graylog Host is called on. Default: 12201.
|
DEPRECATED. The port the Logstash or Graylog Host is called on. Default:
|
||||||
|
12201. Available only when GELF is activated.
|
||||||
--log-gelf-timestamp-format <pattern>
|
--log-gelf-timestamp-format <pattern>
|
||||||
DEPRECATED. Set the format for the GELF timestamp field. Uses Java
|
DEPRECATED. Set the format for the GELF timestamp field. Uses Java
|
||||||
SimpleDateFormat pattern. Default: yyyy-MM-dd HH:mm:ss,SSS.
|
SimpleDateFormat pattern. Default: yyyy-MM-dd HH:mm:ss,SSS. Available only
|
||||||
|
when GELF is activated.
|
||||||
|
--log-gelf-version <version>
|
||||||
|
The GELF version to be used. Possible values are: 1.0, 1.1. Default: 1.1.
|
||||||
|
Available only when GELF is activated.
|
||||||
--log-level <category:level>
|
--log-level <category:level>
|
||||||
The log level of the root category or a comma-separated list of individual
|
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
|
categories and their levels. For the root category, you don't need to
|
||||||
|
@ -239,4 +253,4 @@ By default, this command tries to update the server configuration by running a
|
||||||
$ kc.sh start '--optimized'
|
$ kc.sh start '--optimized'
|
||||||
|
|
||||||
By doing that, the server should start faster based on any previous
|
By doing that, the server should start faster based on any previous
|
||||||
configuration you have set when manually running the 'build' command.
|
configuration you have set when manually running the 'build' command.
|
|
@ -20,14 +20,23 @@ import java.util.Collection;
|
||||||
|
|
||||||
public class StringUtil {
|
public class StringUtil {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if string is null or blank
|
||||||
|
*/
|
||||||
public static boolean isBlank(String str) {
|
public static boolean isBlank(String str) {
|
||||||
return !(isNotBlank(str));
|
return !(isNotBlank(str));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean isNotBlank(String str) {
|
/**
|
||||||
return str != null && !"".equals(str.trim());
|
* Returns true if string is not null and not blank
|
||||||
|
*/
|
||||||
|
public static boolean isNotBlank(String str) {
|
||||||
|
return str != null && !str.isBlank();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if string is null or empty
|
||||||
|
*/
|
||||||
public static boolean isNullOrEmpty(String str) {
|
public static boolean isNullOrEmpty(String str) {
|
||||||
return str == null || str.isEmpty();
|
return str == null || str.isEmpty();
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue