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 Profile CURRENT;
|
||||
private static volatile Profile CURRENT;
|
||||
|
||||
private final ProfileName profileName;
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<#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-title: ${title}
|
||||
:guide-summary: ${summary}
|
||||
|
@ -28,6 +28,6 @@ endif::[]
|
|||
<#if includedOptions?has_content>
|
||||
== 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>
|
||||
</#macro>
|
|
@ -23,6 +23,10 @@
|
|||
*Env:* `${option.keyEnv}`
|
||||
--
|
||||
|
||||
<#if option.enabledWhen?has_content>
|
||||
${option.enabledWhen!}
|
||||
</#if>
|
||||
|
||||
<#if option.deprecated?has_content>
|
||||
<#-- Either mark the whole option as deprecated, or just selected values -->
|
||||
<#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.configuration.Configuration;
|
||||
import org.keycloak.quarkus.runtime.configuration.mappers.PropertyMappers;
|
||||
import org.keycloak.utils.StringUtil;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.EnumMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
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.StreamSupport;
|
||||
|
||||
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<>();
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public Options() {
|
||||
options = PropertyMappers.getMappers().stream()
|
||||
this.options = new EnumMap<>(OptionCategory.class);
|
||||
PropertyMappers.getMappers().stream()
|
||||
.filter(m -> !m.isHidden())
|
||||
.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)))
|
||||
.sorted(Comparator.comparing(Option::getKey))
|
||||
.collect(Collectors.toMap(Option::getKey, o -> o, (o1, o2) -> o1, LinkedHashMap::new)); // Need to ignore duplicate keys??
|
||||
.map(m -> new Option(m.getFrom(),
|
||||
m.getCategory(),
|
||||
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());
|
||||
|
||||
options.forEach((s, option) -> {
|
||||
option.description = option.description.replaceAll("'([^ ]*)'", "`$1`");
|
||||
});
|
||||
options.values()
|
||||
.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 (ProviderFactory providerFactory : providerManager.load(loadSpi).stream().sorted(Comparator.comparing(ProviderFactory::getId)).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)).toList()) {
|
||||
List<ProviderConfigProperty> configMetadata = providerFactory.getConfigMetadata();
|
||||
|
||||
if (configMetadata == null) {
|
||||
|
@ -62,6 +76,7 @@ public class Options {
|
|||
m.getHelpText(),
|
||||
m.getDefaultValue() == null ? null : m.getDefaultValue().toString(),
|
||||
m.getOptions() == null ? Collections.emptyList() : m.getOptions(),
|
||||
"",
|
||||
null))
|
||||
.sorted(Comparator.comparing(Option::getKey)).collect(Collectors.toList());
|
||||
|
||||
|
@ -88,39 +103,84 @@ public class Options {
|
|||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public Collection<Option> getValues() {
|
||||
return options.values();
|
||||
}
|
||||
|
||||
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) {
|
||||
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(' ', '|');
|
||||
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() {
|
||||
return providerOptions;
|
||||
}
|
||||
|
||||
public class Option {
|
||||
public static class Option {
|
||||
|
||||
private String key;
|
||||
private OptionCategory category;
|
||||
private boolean build;
|
||||
private String type;
|
||||
private final String key;
|
||||
private final OptionCategory category;
|
||||
private final boolean build;
|
||||
private final String type;
|
||||
private String description;
|
||||
private String defaultValue;
|
||||
private final String defaultValue;
|
||||
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.category = category;
|
||||
this.build = build;
|
||||
|
@ -128,6 +188,7 @@ public class Options {
|
|||
this.description = description;
|
||||
this.defaultValue = defaultValue;
|
||||
this.expectedValues = StreamSupport.stream(expectedValues.spliterator(), false).collect(Collectors.toList());
|
||||
this.enabledWhen = enabledWhen;
|
||||
this.deprecated = deprecatedMetadata;
|
||||
}
|
||||
|
||||
|
@ -178,6 +239,11 @@ public class Options {
|
|||
return expectedValues;
|
||||
}
|
||||
|
||||
public String getEnabledWhen() {
|
||||
if (StringUtil.isBlank(enabledWhen)) return null;
|
||||
return enabledWhen;
|
||||
}
|
||||
|
||||
public DeprecatedMetadata getDeprecated() {
|
||||
return deprecated;
|
||||
}
|
||||
|
|
|
@ -44,14 +44,10 @@ public enum OptionCategory {
|
|||
}
|
||||
|
||||
private String getHeadingBySupportLevel(String heading) {
|
||||
if (this.supportLevel.equals(ConfigSupportLevel.EXPERIMENTAL)){
|
||||
heading = heading + " (Experimental)";
|
||||
}
|
||||
|
||||
if (this.supportLevel.equals(ConfigSupportLevel.PREVIEW)){
|
||||
heading = heading + " (Preview)";
|
||||
}
|
||||
|
||||
return heading;
|
||||
return switch (supportLevel) {
|
||||
case EXPERIMENTAL -> heading + " (Experimental)";
|
||||
case PREVIEW -> heading + " (Preview)";
|
||||
default -> heading;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -68,6 +68,8 @@ import org.keycloak.common.Profile;
|
|||
import org.keycloak.common.crypto.FipsMode;
|
||||
import org.keycloak.common.util.StreamUtil;
|
||||
import org.keycloak.config.DatabaseOptions;
|
||||
import org.keycloak.config.HealthOptions;
|
||||
import org.keycloak.config.MetricsOptions;
|
||||
import org.keycloak.config.SecurityOptions;
|
||||
import org.keycloak.connections.jpa.DefaultJpaConnectionProviderFactory;
|
||||
import org.keycloak.connections.jpa.JpaConnectionProvider;
|
||||
|
@ -915,11 +917,11 @@ class KeycloakProcessor {
|
|||
}
|
||||
|
||||
private boolean isMetricsEnabled() {
|
||||
return Configuration.getOptionalBooleanValue(NS_KEYCLOAK_PREFIX.concat("metrics-enabled")).orElse(false);
|
||||
return Configuration.isTrue(MetricsOptions.METRICS_ENABLED);
|
||||
}
|
||||
|
||||
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) {
|
||||
|
|
|
@ -38,6 +38,7 @@ import org.apache.commons.lang3.SystemUtils;
|
|||
import org.keycloak.common.Profile;
|
||||
import org.keycloak.common.profile.PropertiesFileProfileConfigResolver;
|
||||
import org.keycloak.common.profile.PropertiesProfileConfigResolver;
|
||||
import org.keycloak.quarkus.runtime.cli.command.AbstractCommand;
|
||||
import org.keycloak.quarkus.runtime.configuration.PersistedConfigSource;
|
||||
|
||||
public final class Environment {
|
||||
|
@ -50,6 +51,9 @@ public final class Environment {
|
|||
public static final String DEV_PROFILE_VALUE = "dev";
|
||||
public static final String PROD_PROFILE_VALUE = "prod";
|
||||
public static final String LAUNCH_MODE = "kc.launch.mode";
|
||||
|
||||
private static volatile AbstractCommand parsedCommand;
|
||||
|
||||
private Environment() {}
|
||||
|
||||
public static Boolean isRebuild() {
|
||||
|
@ -255,4 +259,19 @@ public final class Environment {
|
|||
|
||||
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 jakarta.enterprise.context.ApplicationScoped;
|
||||
import org.keycloak.common.profile.ProfileException;
|
||||
import org.keycloak.quarkus.runtime.configuration.mappers.PropertyMappers;
|
||||
import picocli.CommandLine.ExitCode;
|
||||
|
||||
import io.quarkus.runtime.ApplicationLifecycleManager;
|
||||
|
@ -68,10 +70,7 @@ public class KeycloakMain implements QuarkusApplication {
|
|||
try {
|
||||
cliArgs = Picocli.parseArgs(args);
|
||||
} catch (PropertyException e) {
|
||||
ExecutionExceptionHandler errorHandler = new ExecutionExceptionHandler();
|
||||
PrintWriter errStream = new PrintWriter(System.err, true);
|
||||
errorHandler.error(errStream, e.getMessage(), null);
|
||||
System.exit(ExitCode.USAGE);
|
||||
handleUsageError(e.getMessage());
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -79,25 +78,24 @@ public class KeycloakMain implements QuarkusApplication {
|
|||
cliArgs = new ArrayList<>(cliArgs);
|
||||
// default to show help message
|
||||
cliArgs.add("-h");
|
||||
} else if (isFastStart(cliArgs)) {
|
||||
// fast path for starting the server without bootstrapping CLI
|
||||
ExecutionExceptionHandler errorHandler = new ExecutionExceptionHandler();
|
||||
PrintWriter errStream = new PrintWriter(System.err, true);
|
||||
} else if (isFastStart(cliArgs)) { // fast path for starting the server without bootstrapping CLI
|
||||
|
||||
if (isDevProfileNotAllowed()) {
|
||||
errorHandler.error(errStream, Messages.devProfileNotAllowedError(Start.NAME), null);
|
||||
System.exit(ExitCode.USAGE);
|
||||
handleUsageError(Messages.devProfileNotAllowedError(Start.NAME));
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
PropertyMappers.sanitizeDisabledMappers();
|
||||
Picocli.validateConfig(cliArgs, new Start());
|
||||
} catch (PropertyException e) {
|
||||
errorHandler.error(errStream, e.getMessage(), null);
|
||||
System.exit(ExitCode.USAGE);
|
||||
} catch (PropertyException | ProfileException e) {
|
||||
handleUsageError(e.getMessage(), e.getCause());
|
||||
return;
|
||||
}
|
||||
|
||||
ExecutionExceptionHandler errorHandler = new ExecutionExceptionHandler();
|
||||
PrintWriter errStream = new PrintWriter(System.err, true);
|
||||
|
||||
start(errorHandler, errStream, args);
|
||||
|
||||
return;
|
||||
|
@ -107,6 +105,17 @@ public class KeycloakMain implements QuarkusApplication {
|
|||
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) {
|
||||
// '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);
|
||||
|
|
|
@ -161,8 +161,21 @@ public final class Help extends CommandLine.Help {
|
|||
PropertyMapper<?> mapper = getMapper(option.longestName());
|
||||
|
||||
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
|
||||
return !option.hidden();
|
||||
return !option.hidden() && !isDisabledMapper;
|
||||
}
|
||||
|
||||
boolean isUnsupportedOption = !PropertyMappers.isSupported(mapper);
|
||||
|
|
|
@ -17,8 +17,10 @@
|
|||
|
||||
package org.keycloak.quarkus.runtime.cli;
|
||||
|
||||
import static java.lang.String.format;
|
||||
import static java.util.Optional.ofNullable;
|
||||
import static java.util.stream.StreamSupport.stream;
|
||||
import static org.keycloak.quarkus.runtime.Environment.isRebuild;
|
||||
import static org.keycloak.quarkus.runtime.Environment.isRebuildCheck;
|
||||
import static org.keycloak.quarkus.runtime.Environment.isRebuilt;
|
||||
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.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.EnumMap;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
|
@ -56,11 +57,13 @@ import java.util.stream.Collectors;
|
|||
|
||||
import org.eclipse.microprofile.config.spi.ConfigSource;
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.common.profile.ProfileException;
|
||||
import org.keycloak.config.DeprecatedMetadata;
|
||||
import org.keycloak.config.Option;
|
||||
import org.keycloak.config.OptionCategory;
|
||||
import org.keycloak.quarkus.runtime.cli.command.AbstractCommand;
|
||||
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.Main;
|
||||
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.configuration.ConfigArgsConfigSource;
|
||||
import org.keycloak.quarkus.runtime.configuration.Configuration;
|
||||
import org.keycloak.quarkus.runtime.configuration.DisabledMappersInterceptor;
|
||||
import org.keycloak.quarkus.runtime.configuration.KcUnmatchedArgumentException;
|
||||
import org.keycloak.quarkus.runtime.configuration.PersistedConfigSource;
|
||||
import org.keycloak.quarkus.runtime.configuration.PropertyMappingInterceptor;
|
||||
import org.keycloak.quarkus.runtime.configuration.QuarkusPropertiesConfigSource;
|
||||
|
@ -94,6 +99,7 @@ public final class Picocli {
|
|||
private static class IncludeOptions {
|
||||
boolean includeRuntime;
|
||||
boolean includeBuildTime;
|
||||
boolean includeDisabled;
|
||||
}
|
||||
|
||||
private Picocli() {
|
||||
|
@ -101,29 +107,43 @@ public final class Picocli {
|
|||
|
||||
public static void parseAndRun(List<String> cliArgs) {
|
||||
CommandLine cmd = createCommandLine(cliArgs);
|
||||
|
||||
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);
|
||||
cmd.parseArgs(argArray); // process the cli args first to init the config file and perform validation
|
||||
|
||||
int exitCode;
|
||||
if (isRebuildCheck()) {
|
||||
exitCode = runReAugmentationIfNeeded(cliArgs, cmd);
|
||||
} catch (ParameterException ex) {
|
||||
} 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(ex, argArray);
|
||||
exitCode = cmd.getParameterExceptionHandler().handleParseException(parEx, args);
|
||||
} catch (Exception e) {
|
||||
ExecutionExceptionHandler errorHandler = new ExecutionExceptionHandler();
|
||||
errorHandler.error(cmd.getErr(), e.getMessage(), null);
|
||||
exitCode = ex.getCommandLine().getCommandSpec().exitCodeOnInvalidInput();
|
||||
}
|
||||
exitCode = parEx.getCommandLine().getCommandSpec().exitCodeOnInvalidInput();
|
||||
}
|
||||
exitOnFailure(exitCode, cmd);
|
||||
return;
|
||||
}
|
||||
|
||||
int exitCode = cmd.execute(argArray);
|
||||
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) {
|
||||
|
@ -157,6 +177,7 @@ public final class Picocli {
|
|||
}
|
||||
}
|
||||
if (requiresReAugmentation(currentCommandSpec)) {
|
||||
PropertyMappers.sanitizeDisabledMappers();
|
||||
exitCode = runReAugmentation(cliArgs, cmd);
|
||||
}
|
||||
|
||||
|
@ -279,13 +300,27 @@ public final class Picocli {
|
|||
return;
|
||||
}
|
||||
|
||||
final boolean disabledMappersInterceptorEnabled = DisabledMappersInterceptor.isEnabled(); // return to the state before the disable
|
||||
try {
|
||||
PropertyMappingInterceptor.disable(); // we don't want the mapped / transformed properties, we want what the user effectively supplied
|
||||
List<String> ignoredBuildTime = new ArrayList<>();
|
||||
List<String> ignoredRunTime = new ArrayList<>();
|
||||
Set<String> deprecatedInUse = new HashSet<>();
|
||||
DisabledMappersInterceptor.disable(); // we want all properties, even disabled ones
|
||||
|
||||
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()) {
|
||||
List<PropertyMapper<?>> mappers = new ArrayList<>();
|
||||
List<PropertyMapper<?>> mappers = new ArrayList<>(disabledMappers);
|
||||
Optional.ofNullable(PropertyMappers.getRuntimeMappers().get(category)).ifPresent(mappers::addAll);
|
||||
Optional.ofNullable(PropertyMappers.getBuildTimeMappers().get(category)).ifPresent(mappers::addAll);
|
||||
for (PropertyMapper<?> mapper : mappers) {
|
||||
|
@ -297,6 +332,20 @@ public final class Picocli {
|
|||
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) {
|
||||
ignoredBuildTime.add(mapper.getFrom());
|
||||
continue;
|
||||
|
@ -322,10 +371,17 @@ public final class Picocli {
|
|||
outputIgnoredProperties(ignoredRunTime, false, logger);
|
||||
}
|
||||
|
||||
if (!disabledBuildTime.isEmpty()) {
|
||||
outputDisabledProperties(disabledBuildTime, true, logger);
|
||||
} else if (!disabledRunTime.isEmpty()) {
|
||||
outputDisabledProperties(disabledRunTime, false, logger);
|
||||
}
|
||||
|
||||
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.");
|
||||
}
|
||||
} finally {
|
||||
DisabledMappersInterceptor.enable(disabledMappersInterceptorEnabled);
|
||||
PropertyMappingInterceptor.enable();
|
||||
}
|
||||
}
|
||||
|
@ -372,10 +428,36 @@ public final class Picocli {
|
|||
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) {
|
||||
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",
|
||||
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) {
|
||||
|
@ -536,6 +618,7 @@ public final class Picocli {
|
|||
}
|
||||
result.includeRuntime = abstractCommand.includeRuntime();
|
||||
result.includeBuildTime = abstractCommand.includeBuildTime();
|
||||
result.includeDisabled = cliArgs.contains(HelpAllMixin.HELP_ALL_OPTION);
|
||||
|
||||
if (!result.includeBuildTime && !result.includeRuntime) {
|
||||
return result;
|
||||
|
@ -548,14 +631,17 @@ public final class Picocli {
|
|||
}
|
||||
|
||||
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());
|
||||
|
||||
// set current parsed command
|
||||
Environment.setParsedCommand(ac);
|
||||
|
||||
if (!options.includeBuildTime && !options.includeRuntime) {
|
||||
return;
|
||||
}
|
||||
|
||||
addOptionsToCli(command, options.includeBuildTime, options.includeRuntime);
|
||||
addOptionsToCli(command, options);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -571,30 +657,48 @@ public final class Picocli {
|
|||
return null;
|
||||
}
|
||||
|
||||
private static void addOptionsToCli(CommandLine commandLine, boolean includeBuildTime, boolean includeRuntime) {
|
||||
Map<OptionCategory, List<PropertyMapper<?>>> mappers = new EnumMap<>(OptionCategory.class);
|
||||
private static void addOptionsToCli(CommandLine commandLine, IncludeOptions includeOptions) {
|
||||
final Map<OptionCategory, List<PropertyMapper<?>>> mappers = new EnumMap<>(OptionCategory.class);
|
||||
|
||||
if (includeRuntime) {
|
||||
if (includeOptions.includeRuntime) {
|
||||
mappers.putAll(PropertyMappers.getRuntimeMappers());
|
||||
|
||||
if (includeOptions.includeDisabled) {
|
||||
appendDisabledMappers(mappers, PropertyMappers.getDisabledRuntimeMappers());
|
||||
}
|
||||
}
|
||||
|
||||
if (includeBuildTime) {
|
||||
for (Map.Entry<OptionCategory, List<PropertyMapper<?>>> entry : PropertyMappers.getBuildTimeMappers()
|
||||
.entrySet()) {
|
||||
List<PropertyMapper<?>> result = new ArrayList<>(mappers.getOrDefault(entry.getKey(), Collections.emptyList()));
|
||||
if (includeOptions.includeBuildTime) {
|
||||
combinePropertyMappers(mappers, PropertyMappers.getBuildTimeMappers());
|
||||
|
||||
result.addAll(entry.getValue());
|
||||
|
||||
mappers.put(entry.getKey(), result);
|
||||
if (includeOptions.includeDisabled) {
|
||||
appendDisabledMappers(mappers, PropertyMappers.getDisabledBuildTimeMappers());
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
CommandSpec cSpec = commandLine.getCommandSpec();
|
||||
for(OptionCategory category : ((AbstractCommand) commandLine.getCommand()).getOptionCategories()) {
|
||||
for (OptionCategory category : ((AbstractCommand) commandLine.getCommand()).getOptionCategories()) {
|
||||
List<PropertyMapper<?>> mappersInCategory = propertyMappers.get(category);
|
||||
|
||||
if (mappersInCategory == null) {
|
||||
|
@ -607,11 +711,13 @@ public final class Picocli {
|
|||
.order(category.getOrder())
|
||||
.validate(false);
|
||||
|
||||
for (PropertyMapper<?> mapper: mappersInCategory) {
|
||||
final Set<String> alreadyPresentArgs = new HashSet<>();
|
||||
|
||||
for (PropertyMapper<?> mapper : mappersInCategory) {
|
||||
String name = mapper.getCliFormat();
|
||||
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.
|
||||
continue;
|
||||
}
|
||||
|
@ -638,6 +744,8 @@ public final class Picocli {
|
|||
optBuilder.type(String.class);
|
||||
}
|
||||
|
||||
alreadyPresentArgs.add(name);
|
||||
|
||||
argGroupBuilder.addArg(optBuilder.build());
|
||||
}
|
||||
|
||||
|
@ -667,6 +775,8 @@ public final class Picocli {
|
|||
.map(d -> " Default: " + d + ".")
|
||||
.ifPresent(transformedDesc::append);
|
||||
|
||||
mapper.getEnabledWhen().map(e -> format(" %s.", e)).ifPresent(transformedDesc::append);
|
||||
|
||||
// only fully deprecated options, not just deprecated values
|
||||
mapper.getDeprecatedMetadata()
|
||||
.filter(deprecatedMetadata -> deprecatedMetadata.getDeprecatedValues().isEmpty())
|
||||
|
@ -722,7 +832,7 @@ public final class Picocli {
|
|||
if (!arg.contains(ARG_KEY_VALUE_SEPARATOR)) {
|
||||
if (!iterator.hasNext()) {
|
||||
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;
|
||||
}
|
||||
|
|
|
@ -14,6 +14,10 @@ import picocli.CommandLine.Model.CommandSpec;
|
|||
import picocli.CommandLine.ParameterException;
|
||||
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;
|
||||
|
||||
public class ShortErrorMessageHandler implements IParameterExceptionHandler {
|
||||
|
@ -23,17 +27,30 @@ public class ShortErrorMessageHandler implements IParameterExceptionHandler {
|
|||
CommandLine cmd = ex.getCommandLine();
|
||||
PrintWriter writer = cmd.getErr();
|
||||
String errorMessage = ex.getMessage();
|
||||
String additionalSuggestion = null;
|
||||
|
||||
if (ex instanceof UnmatchedArgumentException) {
|
||||
UnmatchedArgumentException uae = (UnmatchedArgumentException) ex;
|
||||
|
||||
String[] unmatched = getUnmatchedPartsByOptionSeparator(uae,"=");
|
||||
String[] unmatched = getUnmatchedPartsByOptionSeparator(uae, "=");
|
||||
|
||||
String cliKey = unmatched[0];
|
||||
|
||||
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) {
|
||||
errorMessage = "Option: '" + cliKey + "' is not expected to contain whitespace, please remove any unnecessary quoting/escaping";
|
||||
} else {
|
||||
|
@ -42,12 +59,13 @@ public class ShortErrorMessageHandler implements IParameterExceptionHandler {
|
|||
} else {
|
||||
AbstractCommand command = cmd.getCommand();
|
||||
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 {
|
||||
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 {
|
||||
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();
|
||||
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);
|
||||
}
|
||||
|
||||
|
|
|
@ -69,4 +69,8 @@ public abstract class AbstractCommand {
|
|||
}
|
||||
|
||||
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.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.configuration.ConfigArgsConfigSource.getAllCliArgs;
|
||||
|
||||
import org.keycloak.config.ClassLoaderOptions;
|
||||
import org.keycloak.config.OptionCategory;
|
||||
import org.keycloak.quarkus.runtime.Environment;
|
||||
import org.keycloak.quarkus.runtime.Messages;
|
||||
import org.keycloak.quarkus.runtime.configuration.Configuration;
|
||||
import org.keycloak.quarkus.runtime.configuration.MicroProfileConfigProvider;
|
||||
|
||||
import io.quarkus.bootstrap.runner.QuarkusEntryPoint;
|
||||
import io.quarkus.bootstrap.runner.RunnerClassLoader;
|
||||
|
||||
import io.quarkus.runtime.configuration.ProfileManager;
|
||||
import io.smallrye.config.ConfigValue;
|
||||
import org.keycloak.utils.StringUtil;
|
||||
import picocli.CommandLine;
|
||||
import picocli.CommandLine.Command;
|
||||
|
||||
|
@ -84,7 +81,7 @@ public final class Build extends AbstractCommand implements Runnable {
|
|||
beforeReaugmentationOnWindows();
|
||||
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(), "\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 org.keycloak.config.OptionCategory;
|
||||
import org.keycloak.quarkus.runtime.configuration.mappers.ExportPropertyMappers;
|
||||
import picocli.CommandLine.Command;
|
||||
|
||||
import java.util.List;
|
||||
|
@ -42,6 +43,12 @@ public final class Export extends AbstractExportImportCommand implements Runnabl
|
|||
optionCategory != OptionCategory.IMPORT).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validateConfig() {
|
||||
ExportPropertyMappers.validateConfig();
|
||||
super.validateConfig();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return NAME;
|
||||
|
|
|
@ -23,10 +23,12 @@ import picocli.CommandLine;
|
|||
|
||||
public final class HelpAllMixin {
|
||||
|
||||
public static final String HELP_ALL_OPTION = "--help-all";
|
||||
|
||||
@CommandLine.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) {
|
||||
Help help = (Help) spec.commandLine().getHelp();
|
||||
help.setAllOptions(true);
|
||||
|
|
|
@ -20,6 +20,7 @@ package org.keycloak.quarkus.runtime.cli.command;
|
|||
import static org.keycloak.exportimport.ExportImportConfig.ACTION_IMPORT;
|
||||
|
||||
import org.keycloak.config.OptionCategory;
|
||||
import org.keycloak.quarkus.runtime.configuration.mappers.ImportPropertyMappers;
|
||||
import picocli.CommandLine.Command;
|
||||
|
||||
import java.util.List;
|
||||
|
@ -42,6 +43,12 @@ public final class Import extends AbstractExportImportCommand implements Runnabl
|
|||
optionCategory != OptionCategory.EXPORT).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validateConfig() {
|
||||
ImportPropertyMappers.validateConfig();
|
||||
super.validateConfig();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return NAME;
|
||||
|
|
|
@ -28,6 +28,7 @@ import io.smallrye.config.SmallRyeConfig;
|
|||
|
||||
import org.eclipse.microprofile.config.spi.ConfigProviderResolver;
|
||||
import org.eclipse.microprofile.config.spi.ConfigSource;
|
||||
import org.keycloak.config.Option;
|
||||
import org.keycloak.quarkus.runtime.Environment;
|
||||
import org.keycloak.quarkus.runtime.configuration.mappers.PropertyMapper;
|
||||
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() {
|
||||
return (SmallRyeConfig) ConfigProviderResolver.instance().getConfig();
|
||||
}
|
||||
|
@ -222,13 +239,6 @@ public final class Configuration {
|
|||
}
|
||||
|
||||
public static ConfigValue getCurrentBuiltTimeProperty(String name) {
|
||||
PersistedConfigSource persistedConfigSource = PersistedConfigSource.getInstance();
|
||||
|
||||
try {
|
||||
persistedConfigSource.enable(false);
|
||||
return getConfigValue(name);
|
||||
} finally {
|
||||
persistedConfigSource.enable(true);
|
||||
}
|
||||
return PersistedConfigSource.getInstance().runWithDisabled(() -> getConfigValue(name));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
* 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() {
|
||||
super(readProperties(), "", 200);
|
||||
|
@ -146,16 +146,24 @@ public final class PersistedConfigSource extends PropertiesConfigSource {
|
|||
return null;
|
||||
}
|
||||
|
||||
public void enable(boolean enabled) {
|
||||
if (enabled) {
|
||||
ENABLED.remove();
|
||||
} else {
|
||||
ENABLED.set(enabled);
|
||||
public void enable() {
|
||||
ENABLED.set(true);
|
||||
}
|
||||
|
||||
public void disable() {
|
||||
ENABLED.set(false);
|
||||
}
|
||||
|
||||
private boolean isEnabled() {
|
||||
Boolean result = ENABLED.get();
|
||||
return result == null ? true : result;
|
||||
return Boolean.TRUE.equals(ENABLED.get());
|
||||
}
|
||||
|
||||
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.ConfigValue;
|
||||
|
||||
import io.smallrye.config.Priorities;
|
||||
import jakarta.annotation.Priority;
|
||||
import org.apache.commons.collections4.iterators.FilterIterator;
|
||||
import org.keycloak.common.util.StringPropertyReplacer;
|
||||
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.
|
||||
*
|
||||
* <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 {
|
||||
|
||||
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.ConfigValue;
|
||||
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 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;
|
||||
|
||||
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() {
|
||||
}
|
||||
|
||||
public static PropertyMapper<?>[] getMappers() {
|
||||
return new PropertyMapper[] {
|
||||
fromOption(ExportOptions.FILE)
|
||||
.to("kc.spi-export-exporter")
|
||||
return new PropertyMapper[]{
|
||||
fromOption(EXPORTER_PLACEHOLDER)
|
||||
.to(EXPORTER_PROPERTY)
|
||||
.transformer(ExportPropertyMappers::transformExporter)
|
||||
.paramLabel("file")
|
||||
.build(),
|
||||
|
@ -49,43 +57,69 @@ final class ExportPropertyMappers {
|
|||
.build(),
|
||||
fromOption(ExportOptions.REALM)
|
||||
.to("kc.spi-export-single-file-realm-name")
|
||||
.isEnabled(ExportPropertyMappers::isSingleFileProvider)
|
||||
.paramLabel("realm")
|
||||
.build(),
|
||||
fromOption(ExportOptions.REALM)
|
||||
.to("kc.spi-export-dir-realm-name")
|
||||
.isEnabled(ExportPropertyMappers::isDirProvider)
|
||||
.paramLabel("realm")
|
||||
.build(),
|
||||
fromOption(ExportOptions.USERS)
|
||||
.to("kc.spi-export-dir-users-export-strategy")
|
||||
.isEnabled(ExportPropertyMappers::isDirProvider)
|
||||
.paramLabel("strategy")
|
||||
.build(),
|
||||
fromOption(ExportOptions.USERS_PER_FILE)
|
||||
.to("kc.spi-export-dir-users-per-file")
|
||||
.isEnabled(ExportPropertyMappers::isDirProvider)
|
||||
.paramLabel("number")
|
||||
.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) {
|
||||
ConfigValue exporter = context.proceed("kc.spi-export-exporter");
|
||||
ConfigValue exporter = context.proceed(EXPORTER_PROPERTY);
|
||||
if (exporter != null) {
|
||||
return Optional.of(exporter.getValue());
|
||||
}
|
||||
if (option.isPresent()) {
|
||||
return Optional.of("singleFile");
|
||||
}
|
||||
ConfigValue dirConfigValue = context.proceed("kc.spi-export-dir-dir");
|
||||
if (dirConfigValue != null && dirConfigValue.getValue() != null) {
|
||||
return Optional.of("dir");
|
||||
}
|
||||
ConfigValue dirValue = context.proceed("kc.dir");
|
||||
if (dirValue != null && dirValue.getValue() != null) {
|
||||
return Optional.of("dir");
|
||||
}
|
||||
if (System.getProperty(PROVIDER) == null) {
|
||||
throw new CommandLine.PicocliException("Must specify either --dir or --file options.");
|
||||
}
|
||||
return Optional.empty();
|
||||
|
||||
var file = Configuration.getOptionalValue("kc.spi-export-single-file-file").map(f -> SINGLE_FILE);
|
||||
var dir = Configuration.getOptionalValue("kc.spi-export-dir-dir")
|
||||
.or(() -> Configuration.getOptionalValue("kc.dir"))
|
||||
.map(f -> DIR);
|
||||
|
||||
// Only one option can be specified
|
||||
boolean xor = file.isPresent() ^ dir.isPresent();
|
||||
|
||||
return xor ? file.or(() -> dir) : Optional.empty();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -20,23 +20,30 @@ package org.keycloak.quarkus.runtime.configuration.mappers;
|
|||
import io.smallrye.config.ConfigSourceInterceptorContext;
|
||||
import io.smallrye.config.ConfigValue;
|
||||
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 picocli.CommandLine;
|
||||
import org.keycloak.quarkus.runtime.cli.PropertyException;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import static org.keycloak.exportimport.ExportImportConfig.PROVIDER;
|
||||
import static org.keycloak.quarkus.runtime.configuration.Configuration.getOptionalValue;
|
||||
import static org.keycloak.quarkus.runtime.configuration.mappers.PropertyMapper.fromOption;
|
||||
|
||||
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() {
|
||||
}
|
||||
|
||||
public static PropertyMapper<?>[] getMappers() {
|
||||
return new PropertyMapper[] {
|
||||
fromOption(ImportOptions.FILE)
|
||||
.to("kc.spi-import-importer")
|
||||
return new PropertyMapper[]{
|
||||
fromOption(IMPORTER_PLACEHOLDER)
|
||||
.to(IMPORTER_PROPERTY)
|
||||
.transformer(ImportPropertyMappers::transformImporter)
|
||||
.paramLabel("file")
|
||||
.build(),
|
||||
|
@ -51,14 +58,43 @@ final class ImportPropertyMappers {
|
|||
fromOption(ImportOptions.OVERRIDE)
|
||||
.to("kc.spi-import-single-file-strategy")
|
||||
.transformer(ImportPropertyMappers::transformOverride)
|
||||
.isEnabled(ImportPropertyMappers::isSingleFileProvider)
|
||||
.build(),
|
||||
fromOption(ImportOptions.OVERRIDE)
|
||||
.to("kc.spi-import-dir-strategy")
|
||||
.transformer(ImportPropertyMappers::transformOverride)
|
||||
.isEnabled(ImportPropertyMappers::isDirProvider)
|
||||
.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) {
|
||||
if (option.isPresent() && Boolean.parseBoolean(option.get())) {
|
||||
return Optional.of(Strategy.OVERWRITE_EXISTING.name());
|
||||
|
@ -68,25 +104,20 @@ final class ImportPropertyMappers {
|
|||
}
|
||||
|
||||
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) {
|
||||
return Optional.of(importer.getValue());
|
||||
}
|
||||
if (option.isPresent()) {
|
||||
return Optional.of("singleFile");
|
||||
}
|
||||
ConfigValue dirConfigValue = context.proceed("kc.spi-import-dir-dir");
|
||||
if (dirConfigValue != null && dirConfigValue.getValue() != null) {
|
||||
return Optional.of("dir");
|
||||
}
|
||||
ConfigValue dirValue = context.proceed("kc.dir");
|
||||
if (dirConfigValue != null && dirValue.getValue() != null) {
|
||||
return Optional.of("dir");
|
||||
}
|
||||
if (System.getProperty(PROVIDER) == null) {
|
||||
throw new CommandLine.PicocliException("Must specify either --dir or --file options.");
|
||||
}
|
||||
return Optional.empty();
|
||||
|
||||
var file = getOptionalValue("kc.spi-import-single-file-file").map(f -> SINGLE_FILE);
|
||||
var dir = getOptionalValue("kc.spi-import-dir-dir")
|
||||
.or(() -> getOptionalValue("kc.dir"))
|
||||
.map(f -> DIR);
|
||||
|
||||
// Only one option can be specified
|
||||
boolean xor = file.isPresent() ^ dir.isPresent();
|
||||
|
||||
return xor ? file.or(() -> dir) : Optional.empty();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ package org.keycloak.quarkus.runtime.configuration.mappers;
|
|||
|
||||
import static java.util.Optional.of;
|
||||
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.integration.QuarkusPlatform.addInitializationException;
|
||||
|
||||
|
@ -18,10 +19,16 @@ import org.keycloak.config.LoggingOptions;
|
|||
import org.keycloak.quarkus.runtime.Messages;
|
||||
|
||||
import io.smallrye.config.ConfigSourceInterceptorContext;
|
||||
import org.keycloak.quarkus.runtime.configuration.Configuration;
|
||||
|
||||
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() {
|
||||
PropertyMapper<?>[] defaultMappers = new PropertyMapper[]{
|
||||
|
@ -29,15 +36,18 @@ public final class LoggingPropertyMappers {
|
|||
.paramLabel("<handler>")
|
||||
.build(),
|
||||
fromOption(LoggingOptions.LOG_CONSOLE_OUTPUT)
|
||||
.isEnabled(LoggingPropertyMappers::isConsoleEnabled, CONSOLE_ENABLED_MSG)
|
||||
.to("quarkus.log.console.json")
|
||||
.paramLabel("output")
|
||||
.transformer(LoggingPropertyMappers::resolveLogOutput)
|
||||
.build(),
|
||||
fromOption(LoggingOptions.LOG_CONSOLE_FORMAT)
|
||||
.isEnabled(LoggingPropertyMappers::isConsoleEnabled, CONSOLE_ENABLED_MSG)
|
||||
.to("quarkus.log.console.format")
|
||||
.paramLabel("format")
|
||||
.build(),
|
||||
fromOption(LoggingOptions.LOG_CONSOLE_COLOR)
|
||||
.isEnabled(LoggingPropertyMappers::isConsoleEnabled, CONSOLE_ENABLED_MSG)
|
||||
.to("quarkus.log.console.color")
|
||||
.build(),
|
||||
fromOption(LoggingOptions.LOG_CONSOLE_ENABLED)
|
||||
|
@ -51,15 +61,18 @@ public final class LoggingPropertyMappers {
|
|||
.transformer(LoggingPropertyMappers.resolveLogHandler("file"))
|
||||
.build(),
|
||||
fromOption(LoggingOptions.LOG_FILE)
|
||||
.isEnabled(LoggingPropertyMappers::isFileEnabled, FILE_ENABLED_MSG)
|
||||
.to("quarkus.log.file.path")
|
||||
.paramLabel("file")
|
||||
.transformer(LoggingPropertyMappers::resolveFileLogLocation)
|
||||
.build(),
|
||||
fromOption(LoggingOptions.LOG_FILE_FORMAT)
|
||||
.isEnabled(LoggingPropertyMappers::isFileEnabled, FILE_ENABLED_MSG)
|
||||
.to("quarkus.log.file.format")
|
||||
.paramLabel("<format>")
|
||||
.build(),
|
||||
fromOption(LoggingOptions.LOG_FILE_OUTPUT)
|
||||
.isEnabled(LoggingPropertyMappers::isFileEnabled, FILE_ENABLED_MSG)
|
||||
.to("quarkus.log.file.json")
|
||||
.paramLabel("output")
|
||||
.transformer(LoggingPropertyMappers::resolveLogOutput)
|
||||
|
@ -82,52 +95,74 @@ public final class LoggingPropertyMappers {
|
|||
.transformer(LoggingPropertyMappers.resolveLogHandler("gelf"))
|
||||
.build(),
|
||||
fromOption(LoggingOptions.LOG_GELF_LEVEL)
|
||||
.isEnabled(LoggingPropertyMappers::isGelfEnabled, GELF_ENABLED_MSG)
|
||||
.to("quarkus.log.handler.gelf.level")
|
||||
.paramLabel("level")
|
||||
.build(),
|
||||
fromOption(LoggingOptions.LOG_GELF_HOST)
|
||||
.isEnabled(LoggingPropertyMappers::isGelfEnabled, GELF_ENABLED_MSG)
|
||||
.to("quarkus.log.handler.gelf.host")
|
||||
.paramLabel("hostname")
|
||||
.build(),
|
||||
fromOption(LoggingOptions.LOG_GELF_PORT)
|
||||
.isEnabled(LoggingPropertyMappers::isGelfEnabled, GELF_ENABLED_MSG)
|
||||
.to("quarkus.log.handler.gelf.port")
|
||||
.paramLabel("port")
|
||||
.build(),
|
||||
fromOption(LoggingOptions.LOG_GELF_VERSION)
|
||||
.isEnabled(LoggingPropertyMappers::isGelfEnabled, GELF_ENABLED_MSG)
|
||||
.to("quarkus.log.handler.gelf.version")
|
||||
.paramLabel("version")
|
||||
.build(),
|
||||
fromOption(LoggingOptions.LOG_GELF_INCLUDE_STACK_TRACE)
|
||||
.isEnabled(LoggingPropertyMappers::isGelfEnabled, GELF_ENABLED_MSG)
|
||||
.to("quarkus.log.handler.gelf.extract-stack-trace")
|
||||
.build(),
|
||||
fromOption(LoggingOptions.LOG_GELF_TIMESTAMP_FORMAT)
|
||||
.isEnabled(LoggingPropertyMappers::isGelfEnabled, GELF_ENABLED_MSG)
|
||||
.to("quarkus.log.handler.gelf.timestamp-pattern")
|
||||
.paramLabel("pattern")
|
||||
.build(),
|
||||
fromOption(LoggingOptions.LOG_GELF_FACILITY)
|
||||
.isEnabled(LoggingPropertyMappers::isGelfEnabled, GELF_ENABLED_MSG)
|
||||
.to("quarkus.log.handler.gelf.facility")
|
||||
.paramLabel("name")
|
||||
.build(),
|
||||
fromOption(LoggingOptions.LOG_GELF_MAX_MSG_SIZE)
|
||||
.isEnabled(LoggingPropertyMappers::isGelfEnabled, GELF_ENABLED_MSG)
|
||||
.to("quarkus.log.handler.gelf.maximum-message-size")
|
||||
.paramLabel("size")
|
||||
.build(),
|
||||
fromOption(LoggingOptions.LOG_GELF_INCLUDE_LOG_MSG_PARAMS)
|
||||
.isEnabled(LoggingPropertyMappers::isGelfEnabled, GELF_ENABLED_MSG)
|
||||
.to("quarkus.log.handler.gelf.include-log-message-parameters")
|
||||
.build(),
|
||||
fromOption(LoggingOptions.LOG_GELF_INCLUDE_LOCATION)
|
||||
.isEnabled(LoggingPropertyMappers::isGelfEnabled, GELF_ENABLED_MSG)
|
||||
.to("quarkus.log.handler.gelf.include-location")
|
||||
.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) {
|
||||
return (parentValue, context) -> {
|
||||
//we want to fall back to console to not have nothing shown up when wrong values are set.
|
||||
String consoleDependantErrorResult = handler.equals(LoggingOptions.DEFAULT_LOG_HANDLER.name()) ? Boolean.TRUE.toString() : Boolean.FALSE.toString();
|
||||
String handlers = parentValue.get();
|
||||
|
||||
if(handlers.isBlank()) {
|
||||
if (handlers.isBlank()) {
|
||||
addInitializationException(Messages.emptyValueForKey("log"));
|
||||
return of(consoleDependantErrorResult);
|
||||
}
|
||||
|
|
|
@ -27,6 +27,7 @@ import java.util.List;
|
|||
import java.util.Optional;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.BooleanSupplier;
|
||||
|
||||
import io.smallrye.config.ConfigSourceInterceptorContext;
|
||||
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.Environment;
|
||||
import org.keycloak.quarkus.runtime.configuration.MicroProfileConfigProvider;
|
||||
import org.keycloak.utils.StringUtil;
|
||||
|
||||
public class PropertyMapper<T> {
|
||||
|
||||
static PropertyMapper<?> IDENTITY = new PropertyMapper<>(
|
||||
new OptionBuilder<>(null, String.class).build(),
|
||||
null,
|
||||
() -> false,
|
||||
"",
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
|
@ -59,18 +63,23 @@ public class PropertyMapper<T> {
|
|||
|
||||
private final Option<T> option;
|
||||
private final String to;
|
||||
private BooleanSupplier enabled;
|
||||
private String enabledWhen;
|
||||
private final BiFunction<Optional<String>, ConfigSourceInterceptorContext, Optional<String>> mapper;
|
||||
private final String mapFrom;
|
||||
private final boolean mask;
|
||||
private final String paramLabel;
|
||||
private final String envVarFormat;
|
||||
private String cliFormat;
|
||||
private BiConsumer<PropertyMapper<T>, ConfigValue> validator;
|
||||
private final String cliFormat;
|
||||
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) {
|
||||
this.option = option;
|
||||
this.to = to == null ? getFrom() : to;
|
||||
this.enabled = enabled;
|
||||
this.enabledWhen = enabledWhen;
|
||||
this.mapper = mapper == null ? PropertyMapper::defaultTransformer : mapper;
|
||||
this.mapFrom = mapFrom;
|
||||
this.paramLabel = paramLabel;
|
||||
|
@ -148,15 +157,39 @@ public class PropertyMapper<T> {
|
|||
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() {
|
||||
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() {
|
||||
return this.option.getExpectedValues();
|
||||
|
@ -209,7 +242,11 @@ public class PropertyMapper<T> {
|
|||
|
||||
if (mapper == null || (mapFrom == null && name.equals(getFrom()))) {
|
||||
// no mapper set or requesting a property that does not depend on other property, just return the value from the config source
|
||||
return ConfigValue.builder().withName(name).withValue(value.orElse(null)).withConfigSourceName(configSourceName).build();
|
||||
return ConfigValue.builder()
|
||||
.withName(name)
|
||||
.withValue(value.orElse(null))
|
||||
.withConfigSourceName(configSourceName)
|
||||
.build();
|
||||
}
|
||||
|
||||
Optional<String> mappedValue = mapper.apply(value, context);
|
||||
|
@ -218,8 +255,12 @@ public class PropertyMapper<T> {
|
|||
return null;
|
||||
}
|
||||
|
||||
return ConfigValue.builder().withName(name).withValue(mappedValue.get()).withRawValue(value.orElse(null))
|
||||
.withConfigSourceName(configSourceName).build();
|
||||
return ConfigValue.builder()
|
||||
.withName(name)
|
||||
.withValue(mappedValue.get())
|
||||
.withRawValue(value.orElse(null))
|
||||
.withConfigSourceName(configSourceName)
|
||||
.build();
|
||||
}
|
||||
|
||||
private ConfigValue convertValue(ConfigValue configValue) {
|
||||
|
@ -237,8 +278,10 @@ public class PropertyMapper<T> {
|
|||
private BiFunction<Optional<String>, ConfigSourceInterceptorContext, Optional<String>> mapper;
|
||||
private String mapFrom = null;
|
||||
private boolean isMasked = false;
|
||||
private BooleanSupplier isEnabled = () -> true;
|
||||
private String enabledWhen = "";
|
||||
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) {
|
||||
this.option = option;
|
||||
|
@ -269,6 +312,17 @@ public class PropertyMapper<T> {
|
|||
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) {
|
||||
this.validator = validator;
|
||||
return this;
|
||||
|
@ -278,7 +332,7 @@ public class PropertyMapper<T> {
|
|||
if (paramLabel == null && Boolean.class.equals(option.getType())) {
|
||||
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();
|
||||
}
|
||||
|
||||
|
|
|
@ -3,23 +3,45 @@ package org.keycloak.quarkus.runtime.configuration.mappers;
|
|||
import io.smallrye.config.ConfigSourceInterceptorContext;
|
||||
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.OptionCategory;
|
||||
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.DisabledMappersInterceptor;
|
||||
import org.keycloak.quarkus.runtime.configuration.PersistedConfigSource;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.EnumMap;
|
||||
import java.util.EnumSet;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
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 static String VALUE_MASK = "*******";
|
||||
private static final MappersConfig MAPPERS = new MappersConfig();
|
||||
private static final Logger log = Logger.getLogger(PropertyMappers.class);
|
||||
|
||||
private PropertyMappers(){}
|
||||
|
||||
|
@ -44,8 +66,7 @@ public final class PropertyMappers {
|
|||
}
|
||||
|
||||
public static ConfigValue getValue(ConfigSourceInterceptorContext context, String name) {
|
||||
PropertyMapper<?> mapper = MAPPERS.getOrDefault(name, PropertyMapper.IDENTITY);
|
||||
return mapper.getConfigValue(name, context);
|
||||
return getMapperOrDefault(name, PropertyMapper.IDENTITY).getConfigValue(name, context);
|
||||
}
|
||||
|
||||
public static boolean isBuildTimeProperty(String name) {
|
||||
|
@ -53,7 +74,7 @@ public final class PropertyMappers {
|
|||
return true;
|
||||
}
|
||||
|
||||
PropertyMapper<?> mapper = MAPPERS.get(name);
|
||||
final PropertyMapper<?> mapper = getMapperOrDefault(name, null);
|
||||
boolean isBuildTimeProperty = mapper == null ? false : mapper.isBuildTime();
|
||||
|
||||
return isBuildTimeProperty
|
||||
|
@ -83,6 +104,27 @@ public final class PropertyMappers {
|
|||
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) {
|
||||
property = removeProfilePrefixIfNeeded(property);
|
||||
PropertyMapper<?> mapper = getMapper(property);
|
||||
|
@ -102,32 +144,95 @@ public final class PropertyMappers {
|
|||
return property;
|
||||
}
|
||||
|
||||
public static PropertyMapper<?> getMapper(String property) {
|
||||
if (property.startsWith("%")) {
|
||||
return MAPPERS.get(property.substring(property.indexOf('.') + 1));
|
||||
private static PropertyMapper<?> getMapperOrDefault(String property, PropertyMapper<?> defaultMapper) {
|
||||
final var mappers = MAPPERS.getOrDefault(property, Collections.emptyList());
|
||||
|
||||
return switch (mappers.size()) {
|
||||
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();
|
||||
}
|
||||
return MAPPERS.get(property);
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static Collection<PropertyMapper<?>> getMappers() {
|
||||
return MAPPERS.values();
|
||||
public static PropertyMapper<?> getMapper(String property) {
|
||||
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) {
|
||||
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);
|
||||
private Map<OptionCategory, List<PropertyMapper<?>>> runtimeTimeMappers = new EnumMap<>(OptionCategory.class);
|
||||
PropertyMapper<?> mapper = getDisabledBuildTimeMappers().get(property);
|
||||
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) {
|
||||
for (PropertyMapper<?> mapper : mappers) {
|
||||
super.put(mapper.getTo(), mapper);
|
||||
super.put(mapper.getFrom(), mapper);
|
||||
super.put(mapper.getCliFormat(), mapper);
|
||||
super.put(mapper.getEnvVarFormat(), mapper);
|
||||
addMapper(mapper);
|
||||
|
||||
if (mapper.isBuildTime()) {
|
||||
addMapperByStage(mapper, buildTimeMappers);
|
||||
|
@ -137,22 +242,68 @@ public final class PropertyMappers {
|
|||
}
|
||||
}
|
||||
|
||||
private void addMapperByStage(PropertyMapper<?> mapper, Map<OptionCategory, List<PropertyMapper<?>>> mappers) {
|
||||
mappers.computeIfAbsent(mapper.getCategory(),
|
||||
new Function<OptionCategory, List<PropertyMapper<?>>>() {
|
||||
@Override
|
||||
public List<PropertyMapper<?>> apply(OptionCategory c) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
}).add(mapper);
|
||||
private static void addMapperByStage(PropertyMapper<?> mapper, Map<OptionCategory, List<PropertyMapper<?>>> mappers) {
|
||||
mappers.computeIfAbsent(mapper.getCategory(), c -> new ArrayList<>()).add(mapper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PropertyMapper<?> put(String key, PropertyMapper<?> value) {
|
||||
if (containsKey(key)) {
|
||||
throw new IllegalArgumentException("Duplicated mapper for key [" + key + "]");
|
||||
public void addMapper(PropertyMapper<?> mapper) {
|
||||
handleMapper(mapper, this::add);
|
||||
}
|
||||
|
||||
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() {
|
||||
|
@ -162,6 +313,35 @@ public final class PropertyMappers {
|
|||
public Map<OptionCategory, List<PropertyMapper<?>>> getBuildTimeMappers() {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,3 +16,4 @@
|
|||
#
|
||||
|
||||
org.keycloak.quarkus.runtime.configuration.PropertyMappingInterceptor
|
||||
org.keycloak.quarkus.runtime.configuration.DisabledMappersInterceptor
|
|
@ -3,3 +3,7 @@ spi-hostname-default-frontend-url = ${keycloak.frontendUrl:http://filepropdefaul
|
|||
log-level=${SOME_LOG_LEVEL:warn}
|
||||
config-keystore=src/test/resources/keystore
|
||||
config-keystore-password=secret
|
||||
|
||||
quarkus.log.file.path=random/path
|
||||
|
||||
log-gelf-level=WARN
|
|
@ -87,7 +87,7 @@ public class OptionValidationTest {
|
|||
}
|
||||
|
||||
@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) {
|
||||
CLIResult cliResult = (CLIResult) result;
|
||||
assertEquals("Unknown option: '--db-pasword'\n" +
|
||||
|
@ -96,7 +96,7 @@ public class OptionValidationTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
@Launch({ "start", "--db postgres" })
|
||||
@Launch({"start", "--db postgres"})
|
||||
void failSingleParamWithSpace(LaunchResult result) {
|
||||
CLIResult cliResult = (CLIResult) result;
|
||||
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 = 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.assertNoMessage("Changes detected in configuration");
|
||||
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" })
|
||||
void testJsonFormatApplied(LaunchResult result) throws JsonProcessingException {
|
||||
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.assertStartedDevMode();
|
||||
}
|
||||
|
|
|
@ -20,43 +20,117 @@ package org.keycloak.it.cli.dist;
|
|||
import io.quarkus.test.junit.main.Launch;
|
||||
import io.quarkus.test.junit.main.LaunchResult;
|
||||
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.TestMethodOrder;
|
||||
import org.keycloak.it.junit5.extension.CLIResult;
|
||||
import org.keycloak.it.junit5.extension.DistributionTest;
|
||||
import org.keycloak.it.junit5.extension.RawDistOnly;
|
||||
import org.keycloak.it.junit5.extension.WithEnvVars;
|
||||
import org.keycloak.it.utils.KeycloakDistribution;
|
||||
|
||||
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;
|
||||
|
||||
@DistributionTest
|
||||
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
|
||||
public class OptionsDistTest {
|
||||
|
||||
@Test
|
||||
@Order(1)
|
||||
@Launch({"build", "--db=invalid"})
|
||||
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"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@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(2)
|
||||
@Launch({"start", "--test=invalid"})
|
||||
public void testServerDoesNotStartIfValidationFailDuringReAugStart(LaunchResult result) {
|
||||
assertEquals(1, result.getErrorStream().stream().filter(s -> s.contains("Unknown option: '--test'")).count());
|
||||
}
|
||||
|
||||
@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")
|
||||
public void testExpressionsInConfigFile(KeycloakDistribution distribution) {
|
||||
distribution.setEnvVar("MY_LOG_LEVEL", "debug");
|
||||
CLIResult result = distribution.run(CONFIG_FILE_LONG_NAME + "=" + Paths.get("src/test/resources/OptionsDistTest/keycloak.conf").toAbsolutePath().normalize(), "start-dev");
|
||||
result.assertMessage("DEBUG [org.keycloak");
|
||||
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", "--http-enabled=true", "--hostname-strict=false", "--optimized");
|
||||
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=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
|
||||
are: console, file, gelf (deprecated). Default: console.
|
||||
--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>
|
||||
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} %
|
||||
-5p [%c] (%t) %s%e%n.
|
||||
-5p [%c] (%t) %s%e%n. Available only when Console log handler is activated.
|
||||
--log-console-output <output>
|
||||
Set the log output to JSON or default (plain) unstructured logging. Possible
|
||||
values are: default, json. Default: default.
|
||||
--log-file <file> Set the log file path and filename. Default: data/log/keycloak.log.
|
||||
--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.
|
||||
values are: default, json. Default: default. Available only when Console log
|
||||
handler is activated.
|
||||
--log-level <category:level>
|
||||
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
|
||||
|
|
|
@ -74,47 +74,61 @@ Logging:
|
|||
--log <handler> Enable one or more log handlers in a comma-separated list. Possible values
|
||||
are: console, file, gelf (deprecated). Default: console.
|
||||
--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>
|
||||
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} %
|
||||
-5p [%c] (%t) %s%e%n.
|
||||
-5p [%c] (%t) %s%e%n. Available only when Console log handler is activated.
|
||||
--log-console-output <output>
|
||||
Set the log output to JSON or default (plain) unstructured logging. Possible
|
||||
values are: default, json. Default: default.
|
||||
--log-file <file> Set the log file path and filename. Default: data/log/keycloak.log.
|
||||
values are: default, json. Default: default. Available only when Console 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>
|
||||
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>
|
||||
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>
|
||||
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>
|
||||
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.
|
||||
Default: localhost. Available only when GELF is activated.
|
||||
--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>
|
||||
DEPRECATED. Include message parameters from the log event. Default: true.
|
||||
Available only when GELF is activated.
|
||||
--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.
|
||||
'StackTrace' field in the GELF output. Default: true. Available only when
|
||||
GELF is activated.
|
||||
--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.
|
||||
Default: INFO. Available only when GELF is activated.
|
||||
--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.
|
||||
GELF will submit the message in multiple chunks. Default: 8192. Available
|
||||
only when GELF is activated.
|
||||
--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>
|
||||
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>
|
||||
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
|
||||
|
|
|
@ -74,47 +74,16 @@ Logging:
|
|||
--log <handler> Enable one or more log handlers in a comma-separated list. Possible values
|
||||
are: console, file, gelf (deprecated). Default: console.
|
||||
--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>
|
||||
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} %
|
||||
-5p [%c] (%t) %s%e%n.
|
||||
-5p [%c] (%t) %s%e%n. Available only when Console log handler is activated.
|
||||
--log-console-output <output>
|
||||
Set the log output to JSON or default (plain) unstructured logging. Possible
|
||||
values are: default, json. Default: default.
|
||||
--log-file <file> Set the log file path and filename. Default: data/log/keycloak.log.
|
||||
--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.
|
||||
values are: default, json. Default: default. Available only when Console log
|
||||
handler is activated.
|
||||
--log-level <category:level>
|
||||
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
|
||||
|
|
|
@ -74,47 +74,61 @@ Logging:
|
|||
--log <handler> Enable one or more log handlers in a comma-separated list. Possible values
|
||||
are: console, file, gelf (deprecated). Default: console.
|
||||
--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>
|
||||
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} %
|
||||
-5p [%c] (%t) %s%e%n.
|
||||
-5p [%c] (%t) %s%e%n. Available only when Console log handler is activated.
|
||||
--log-console-output <output>
|
||||
Set the log output to JSON or default (plain) unstructured logging. Possible
|
||||
values are: default, json. Default: default.
|
||||
--log-file <file> Set the log file path and filename. Default: data/log/keycloak.log.
|
||||
values are: default, json. Default: default. Available only when Console 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>
|
||||
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>
|
||||
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>
|
||||
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>
|
||||
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.
|
||||
Default: localhost. Available only when GELF is activated.
|
||||
--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>
|
||||
DEPRECATED. Include message parameters from the log event. Default: true.
|
||||
Available only when GELF is activated.
|
||||
--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.
|
||||
'StackTrace' field in the GELF output. Default: true. Available only when
|
||||
GELF is activated.
|
||||
--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.
|
||||
Default: INFO. Available only when GELF is activated.
|
||||
--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.
|
||||
GELF will submit the message in multiple chunks. Default: 8192. Available
|
||||
only when GELF is activated.
|
||||
--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>
|
||||
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>
|
||||
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
|
||||
|
|
|
@ -238,47 +238,16 @@ Logging:
|
|||
--log <handler> Enable one or more log handlers in a comma-separated list. Possible values
|
||||
are: console, file, gelf (deprecated). Default: console.
|
||||
--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>
|
||||
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} %
|
||||
-5p [%c] (%t) %s%e%n.
|
||||
-5p [%c] (%t) %s%e%n. Available only when Console log handler is activated.
|
||||
--log-console-output <output>
|
||||
Set the log output to JSON or default (plain) unstructured logging. Possible
|
||||
values are: default, json. Default: default.
|
||||
--log-file <file> Set the log file path and filename. Default: data/log/keycloak.log.
|
||||
--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.
|
||||
values are: default, json. Default: default. Available only when Console log
|
||||
handler is activated.
|
||||
--log-level <category:level>
|
||||
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
|
||||
|
|
|
@ -238,47 +238,61 @@ Logging:
|
|||
--log <handler> Enable one or more log handlers in a comma-separated list. Possible values
|
||||
are: console, file, gelf (deprecated). Default: console.
|
||||
--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>
|
||||
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} %
|
||||
-5p [%c] (%t) %s%e%n.
|
||||
-5p [%c] (%t) %s%e%n. Available only when Console log handler is activated.
|
||||
--log-console-output <output>
|
||||
Set the log output to JSON or default (plain) unstructured logging. Possible
|
||||
values are: default, json. Default: default.
|
||||
--log-file <file> Set the log file path and filename. Default: data/log/keycloak.log.
|
||||
values are: default, json. Default: default. Available only when Console 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>
|
||||
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>
|
||||
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>
|
||||
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>
|
||||
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.
|
||||
Default: localhost. Available only when GELF is activated.
|
||||
--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>
|
||||
DEPRECATED. Include message parameters from the log event. Default: true.
|
||||
Available only when GELF is activated.
|
||||
--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.
|
||||
'StackTrace' field in the GELF output. Default: true. Available only when
|
||||
GELF is activated.
|
||||
--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.
|
||||
Default: INFO. Available only when GELF is activated.
|
||||
--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.
|
||||
GELF will submit the message in multiple chunks. Default: 8192. Available
|
||||
only when GELF is activated.
|
||||
--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>
|
||||
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>
|
||||
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
|
||||
|
|
|
@ -239,47 +239,16 @@ Logging:
|
|||
--log <handler> Enable one or more log handlers in a comma-separated list. Possible values
|
||||
are: console, file, gelf (deprecated). Default: console.
|
||||
--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>
|
||||
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} %
|
||||
-5p [%c] (%t) %s%e%n.
|
||||
-5p [%c] (%t) %s%e%n. Available only when Console log handler is activated.
|
||||
--log-console-output <output>
|
||||
Set the log output to JSON or default (plain) unstructured logging. Possible
|
||||
values are: default, json. Default: default.
|
||||
--log-file <file> Set the log file path and filename. Default: data/log/keycloak.log.
|
||||
--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.
|
||||
values are: default, json. Default: default. Available only when Console log
|
||||
handler is activated.
|
||||
--log-level <category:level>
|
||||
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
|
||||
|
|
|
@ -239,47 +239,61 @@ Logging:
|
|||
--log <handler> Enable one or more log handlers in a comma-separated list. Possible values
|
||||
are: console, file, gelf (deprecated). Default: console.
|
||||
--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>
|
||||
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} %
|
||||
-5p [%c] (%t) %s%e%n.
|
||||
-5p [%c] (%t) %s%e%n. Available only when Console log handler is activated.
|
||||
--log-console-output <output>
|
||||
Set the log output to JSON or default (plain) unstructured logging. Possible
|
||||
values are: default, json. Default: default.
|
||||
--log-file <file> Set the log file path and filename. Default: data/log/keycloak.log.
|
||||
values are: default, json. Default: default. Available only when Console 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>
|
||||
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>
|
||||
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>
|
||||
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>
|
||||
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.
|
||||
Default: localhost. Available only when GELF is activated.
|
||||
--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>
|
||||
DEPRECATED. Include message parameters from the log event. Default: true.
|
||||
Available only when GELF is activated.
|
||||
--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.
|
||||
'StackTrace' field in the GELF output. Default: true. Available only when
|
||||
GELF is activated.
|
||||
--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.
|
||||
Default: INFO. Available only when GELF is activated.
|
||||
--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.
|
||||
GELF will submit the message in multiple chunks. Default: 8192. Available
|
||||
only when GELF is activated.
|
||||
--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>
|
||||
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>
|
||||
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
|
||||
|
|
|
@ -177,47 +177,16 @@ Logging:
|
|||
--log <handler> Enable one or more log handlers in a comma-separated list. Possible values
|
||||
are: console, file, gelf (deprecated). Default: console.
|
||||
--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>
|
||||
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} %
|
||||
-5p [%c] (%t) %s%e%n.
|
||||
-5p [%c] (%t) %s%e%n. Available only when Console log handler is activated.
|
||||
--log-console-output <output>
|
||||
Set the log output to JSON or default (plain) unstructured logging. Possible
|
||||
values are: default, json. Default: default.
|
||||
--log-file <file> Set the log file path and filename. Default: data/log/keycloak.log.
|
||||
--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.
|
||||
values are: default, json. Default: default. Available only when Console log
|
||||
handler is activated.
|
||||
--log-level <category:level>
|
||||
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
|
||||
|
|
|
@ -177,47 +177,61 @@ Logging:
|
|||
--log <handler> Enable one or more log handlers in a comma-separated list. Possible values
|
||||
are: console, file, gelf (deprecated). Default: console.
|
||||
--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>
|
||||
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} %
|
||||
-5p [%c] (%t) %s%e%n.
|
||||
-5p [%c] (%t) %s%e%n. Available only when Console log handler is activated.
|
||||
--log-console-output <output>
|
||||
Set the log output to JSON or default (plain) unstructured logging. Possible
|
||||
values are: default, json. Default: default.
|
||||
--log-file <file> Set the log file path and filename. Default: data/log/keycloak.log.
|
||||
values are: default, json. Default: default. Available only when Console 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>
|
||||
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>
|
||||
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>
|
||||
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>
|
||||
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.
|
||||
Default: localhost. Available only when GELF is activated.
|
||||
--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>
|
||||
DEPRECATED. Include message parameters from the log event. Default: true.
|
||||
Available only when GELF is activated.
|
||||
--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.
|
||||
'StackTrace' field in the GELF output. Default: true. Available only when
|
||||
GELF is activated.
|
||||
--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.
|
||||
Default: INFO. Available only when GELF is activated.
|
||||
--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.
|
||||
GELF will submit the message in multiple chunks. Default: 8192. Available
|
||||
only when GELF is activated.
|
||||
--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>
|
||||
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>
|
||||
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
|
||||
|
|
|
@ -20,14 +20,23 @@ import java.util.Collection;
|
|||
|
||||
public class StringUtil {
|
||||
|
||||
/**
|
||||
* Returns true if string is null or blank
|
||||
*/
|
||||
public static boolean isBlank(String str) {
|
||||
return !(isNotBlank(str));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if string is not null and not blank
|
||||
*/
|
||||
public static boolean isNotBlank(String str) {
|
||||
return str != null && !"".equals(str.trim());
|
||||
return str != null && !str.isBlank();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if string is null or empty
|
||||
*/
|
||||
public static boolean isNullOrEmpty(String str) {
|
||||
return str == null || str.isEmpty();
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue