Skip to content

Commit

Permalink
Make cache-remote-host enabled when needed
Browse files Browse the repository at this point in the history
Closes keycloak#34536

Signed-off-by: Pedro Ruivo <pruivo@redhat.com>
  • Loading branch information
pruivo authored Nov 11, 2024
1 parent b82ec62 commit 0bbe568
Show file tree
Hide file tree
Showing 10 changed files with 213 additions and 60 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
import java.util.Set;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Function;

import org.keycloak.common.profile.ProfileException;
import org.keycloak.config.DeprecatedMetadata;
Expand Down Expand Up @@ -164,7 +165,7 @@ private CommandLine createCommandLineForCommand(List<String> cliArgs, List<Comma
if (currentSpec != null) {
CommandLine commandLine = currentSpec.commandLine();
addCommandOptions(cliArgs, commandLine);

if (commandLine != null && commandLine.getCommand() instanceof AbstractCommand ac) {
// set current parsed command
Environment.setParsedCommand(ac);
Expand Down Expand Up @@ -219,7 +220,7 @@ private int runReAugmentationIfNeeded(List<String> cliArgs, CommandLine cmd, Com
// TODO: ensure that the config has not yet been initialized
// - there's currently no good way to do that directly on ConfigProviderResolver
initProfile(cliArgs, currentCommandName);

if (requiresReAugmentation(currentCommand)) {
PropertyMappers.sanitizeDisabledMappers();
exitCode = runReAugmentation(cliArgs, cmd);
Expand All @@ -238,7 +239,7 @@ protected void initProfile(List<String> cliArgs, String currentCommandName) {
// override from the cli if specified
parseConfigArgs(cliArgs, (k, v) -> {
if (k.equals(Main.PROFILE_SHORT_NAME) || k.equals(Main.PROFILE_LONG_NAME)) {
Environment.setProfile(v);
Environment.setProfile(v);
}
}, ignored -> {});
}
Expand All @@ -259,7 +260,7 @@ private static boolean requiresReAugmentation(CommandLine cmdCommand) {
return true; // no build yet
}
var current = getNonPersistedBuildTimeOptions();

// everything but the optimized value must match
String key = Configuration.KC_OPTIMIZED;
Optional.ofNullable(rawPersistedProperties.get(key)).ifPresentOrElse(value -> current.put(key, value), () -> current.remove(key));
Expand Down Expand Up @@ -327,13 +328,13 @@ private static boolean wasBuildEverRun() {
*
* @param cliArgs
* @param abstractCommand
* @param outWriter
* @param outWriter
*/
public static void validateConfig(List<String> cliArgs, AbstractCommand abstractCommand, PrintWriter outWriter) {
if (cliArgs.contains(OPTIMIZED_BUILD_OPTION_LONG) && !wasBuildEverRun()) {
throw new PropertyException(Messages.optimizedUsedForFirstStartup());
}

IncludeOptions options = getIncludeOptions(cliArgs, abstractCommand, abstractCommand.getName());

if (!options.includeBuildTime && !options.includeRuntime) {
Expand All @@ -350,6 +351,7 @@ public static void validateConfig(List<String> cliArgs, AbstractCommand abstract
final Set<String> disabledBuildTime = new HashSet<>();
final Set<String> disabledRunTime = new HashSet<>();
final Set<String> deprecatedInUse = new HashSet<>();
final Set<String> missingOption = new HashSet<>();

final Set<PropertyMapper<?>> disabledMappers = new HashSet<>();
if (options.includeBuildTime) {
Expand All @@ -369,7 +371,14 @@ public static void validateConfig(List<String> cliArgs, AbstractCommand abstract
ConfigValue configValue = Configuration.getConfigValue(mapper.getFrom());
String configValueStr = configValue.getValue();

if (configValueStr == null || !isUserModifiable(configValue)) {
// don't consider missing or anything below standard env properties
if (configValueStr == null) {
if (Environment.isRuntimeMode() && mapper.isEnabled() && mapper.isRequired()) {
handleRequired(missingOption, mapper);
}
continue;
}
if (!isUserModifiable(configValue)) {
continue;
}

Expand Down Expand Up @@ -409,6 +418,9 @@ public static void validateConfig(List<String> cliArgs, AbstractCommand abstract
}
}

if (!missingOption.isEmpty()) {
throw new PropertyException("The following options are required: \n%s".formatted(String.join("\n", missingOption)));
}
if (!ignoredBuildTime.isEmpty()) {
throw new PropertyException(format("The following build time options have values that differ from what is persisted - the new values will NOT be used until another build is run: %s\n",
String.join(", ", ignoredBuildTime)));
Expand Down Expand Up @@ -510,25 +522,21 @@ private static void handleDeprecated(Set<String> deprecatedInUse, PropertyMapper
}

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());
}
handleMessage(disabledInUse, mapper, PropertyMapper::getEnabledWhen);
}

private static void handleRequired(Set<String> requiredOptions, PropertyMapper<?> mapper) {
handleMessage(requiredOptions, mapper, PropertyMapper::getRequiredWhen);
}

private static void handleMessage(Set<String> messages, PropertyMapper<?> mapper, Function<PropertyMapper<?>, Optional<String>> retrieveMessage) {
var optionName = mapper.getOption().getKey();
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());
retrieveMessage.apply(mapper).ifPresent(msg -> sb.append(": ").append(msg).append("."));
messages.add(sb.toString());
}

private static void warn(String text, PrintWriter outwriter) {
ColorScheme defaultColorScheme = picocli.CommandLine.Help.defaultColorScheme(Help.Ansi.AUTO);
outwriter.println(defaultColorScheme.apply("WARNING: ", Arrays.asList(Style.fg_yellow, Style.bold)) + text);
Expand Down Expand Up @@ -622,7 +630,7 @@ public <K> K create(Class<K> cls) throws Exception {
protected PrintWriter getErrWriter() {
return new PrintWriter(System.err, true);
}

protected PrintWriter getOutWriter() {
return new PrintWriter(System.out, true);
}
Expand Down Expand Up @@ -749,7 +757,7 @@ public Iterator<String> iterator() {
} else if (mapper.getType().isEnum()) {
// prevent the auto-conversion that picocli does
// we validate the expected values later
optBuilder.type(String.class);
optBuilder.type(String.class);
}
} else {
optBuilder.type(String.class);
Expand Down Expand Up @@ -791,6 +799,7 @@ private static String getDecoratedOptionDescription(PropertyMapper<?> mapper) {
.ifPresent(transformedDesc::append);

mapper.getEnabledWhen().map(e -> format(" %s.", e)).ifPresent(transformedDesc::append);
mapper.getRequiredWhen().map(e -> format(" %s.", e)).ifPresent(transformedDesc::append);

// only fully deprecated options, not just deprecated values
mapper.getDeprecatedMetadata()
Expand Down Expand Up @@ -854,19 +863,19 @@ public static List<String> parseArgs(String[] rawArgs) throws PropertyException

private static void checkChangesInBuildOptionsDuringAutoBuild(PrintWriter out) {
StringBuilder options = new StringBuilder();

var current = getNonPersistedBuildTimeOptions();
var persisted = Configuration.getRawPersistedProperties();

// TODO: order is not well defined here

current.forEach((key, value) -> {
String persistedValue = persisted.get(key);
if (!value.equals(persistedValue)) {
optionChanged(options, (String)key, persistedValue, (String)value);
}
});

persisted.forEach((key, value) -> {
if (current.get(key) == null) {
optionChanged(options, key, value, null);
Expand All @@ -885,7 +894,7 @@ private static void checkChangesInBuildOptionsDuringAutoBuild(PrintWriter out) {
);
}
}

private static void optionChanged(StringBuilder options, String key, String oldValue, String newValue) {
// the assumption here is that no build time options need mask handling
boolean isIgnored = !key.startsWith(MicroProfileConfigProvider.NS_KEYCLOAK_PREFIX)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,24 +1,28 @@
package org.keycloak.quarkus.runtime.configuration.mappers;

import static org.keycloak.quarkus.runtime.configuration.Configuration.getOptionalKcValue;
import static org.keycloak.quarkus.runtime.configuration.mappers.PropertyMapper.fromOption;

import java.io.File;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.function.BooleanSupplier;

import io.smallrye.config.ConfigSourceInterceptorContext;
import org.keycloak.common.Profile;
import org.keycloak.config.CachingOptions;
import org.keycloak.config.Option;
import org.keycloak.infinispan.util.InfinispanUtils;
import org.keycloak.quarkus.runtime.Environment;
import org.keycloak.quarkus.runtime.cli.PropertyException;

import io.smallrye.config.ConfigSourceInterceptorContext;
import static org.keycloak.quarkus.runtime.configuration.Configuration.getOptionalKcValue;
import static org.keycloak.quarkus.runtime.configuration.mappers.PropertyMapper.fromOption;

final class CachingPropertyMappers {

private static final String REMOTE_HOST_SET = "remote host is set";
private static final String MULTI_SITE_OR_EMBEDDED_REMOTE_FEATURE_SET = "feature '%s', '%s' or '%s' is set".formatted(Profile.Feature.MULTI_SITE.getKey(), Profile.Feature.CLUSTERLESS.getKey(), Profile.Feature.CACHE_EMBEDDED_REMOTE_STORE.getKey());
private static final String MULTI_SITE_FEATURE_SET = "feature '%s' or '%s' is set".formatted(Profile.Feature.MULTI_SITE.getKey(), Profile.Feature.CLUSTERLESS.getKey());

private static final String CACHE_STACK_SET_TO_ISPN = "'cache' type is set to '" + CachingOptions.Mechanism.ispn.name() + "'";

Expand Down Expand Up @@ -66,6 +70,8 @@ public static PropertyMapper<?>[] getClusteringPropertyMappers() {
.build(),
fromOption(CachingOptions.CACHE_REMOTE_HOST)
.paramLabel("hostname")
.addValidateEnabled(CachingPropertyMappers::isRemoteCacheHostEnabled, MULTI_SITE_OR_EMBEDDED_REMOTE_FEATURE_SET)
.isRequired(InfinispanUtils::isRemoteInfinispan, MULTI_SITE_FEATURE_SET)
.build(),
fromOption(CachingOptions.CACHE_REMOTE_PORT)
.isEnabled(CachingPropertyMappers::remoteHostSet, CachingPropertyMappers.REMOTE_HOST_SET)
Expand All @@ -76,10 +82,12 @@ public static PropertyMapper<?>[] getClusteringPropertyMappers() {
.build(),
fromOption(CachingOptions.CACHE_REMOTE_USERNAME)
.isEnabled(CachingPropertyMappers::remoteHostSet, CachingPropertyMappers.REMOTE_HOST_SET)
.validator((value) -> validateCachingOptionIsPresent(CachingOptions.CACHE_REMOTE_USERNAME, CachingOptions.CACHE_REMOTE_PASSWORD))
.paramLabel("username")
.build(),
fromOption(CachingOptions.CACHE_REMOTE_PASSWORD)
.isEnabled(CachingPropertyMappers::remoteHostSet, CachingPropertyMappers.REMOTE_HOST_SET)
.validator((value) -> validateCachingOptionIsPresent(CachingOptions.CACHE_REMOTE_PASSWORD, CachingOptions.CACHE_REMOTE_USERNAME))
.paramLabel("password")
.isMasked(true)
.build(),
Expand Down Expand Up @@ -152,4 +160,14 @@ private static PropertyMapper<?> maxCountOpt(String cacheName, BooleanSupplier i
.paramLabel("max-count")
.build();
}

private static boolean isRemoteCacheHostEnabled() {
return InfinispanUtils.isRemoteInfinispan() || Profile.isFeatureEnabled(Profile.Feature.CACHE_EMBEDDED_REMOTE_STORE);
}

private static void validateCachingOptionIsPresent(Option<?> optionSet, Option<?> optionRequired) {
if (getOptionalKcValue(optionRequired).isEmpty()) {
throw new PropertyException("The option '%s' is required when '%s' is set.".formatted(optionRequired.getKey(), optionSet.getKey()));
}
}
}
Loading

0 comments on commit 0bbe568

Please sign in to comment.