Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion common/src/main/java/org/keycloak/common/Profile.java
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,7 @@ public String getLabel() {

private static final Logger logger = Logger.getLogger(Profile.class);

private static Profile CURRENT;
private static volatile Profile CURRENT;

private final ProfileName profileName;

Expand Down
4 changes: 2 additions & 2 deletions docs/guides/templates/guide.adoc
Original file line number Diff line number Diff line change
@@ -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}
Expand Down Expand Up @@ -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>
4 changes: 4 additions & 0 deletions docs/guides/templates/options.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,40 +15,54 @@
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) {
Expand All @@ -62,6 +76,7 @@ public 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());

Expand All @@ -88,46 +103,92 @@ public List<OptionCategory> getCategories() {
.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;

public Option(String key, OptionCategory category, boolean build, String type, String description, String defaultValue, Iterable<String> expectedValues, DeprecatedMetadata deprecatedMetadata) {
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,
String enabledWhen,
DeprecatedMetadata deprecatedMetadata) {
this.key = key;
this.category = category;
this.build = build;
this.type = type;
this.description = description;
this.defaultValue = defaultValue;
this.expectedValues = StreamSupport.stream(expectedValues.spliterator(), false).collect(Collectors.toList());
this.enabledWhen = enabledWhen;
this.deprecated = deprecatedMetadata;
}

Expand Down Expand Up @@ -178,6 +239,11 @@ public List<String> getExpectedValues() {
return expectedValues;
}

public String getEnabledWhen() {
if (StringUtil.isBlank(enabledWhen)) return null;
return enabledWhen;
}

public DeprecatedMetadata getDeprecated() {
return deprecated;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,14 +44,10 @@ public ConfigSupportLevel getSupportLevel() {
}

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;
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,8 @@
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;
Expand Down Expand Up @@ -915,11 +917,11 @@ private void checkProviders(Spi spi,
}

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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
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 {
Expand All @@ -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() {
Expand Down Expand Up @@ -255,4 +259,19 @@ public synchronized static Profile getCurrentOrCreateFeatureProfile() {

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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@
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;
Expand Down Expand Up @@ -68,36 +70,32 @@ public static void main(String[] args) {
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;
}

if (cliArgs.isEmpty()) {
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;
Expand All @@ -107,6 +105,17 @@ public static void main(String[] args) {
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);
Expand Down
Loading