mirror of
https://github.com/keycloak/keycloak.git
synced 2026-01-10 15:32:05 -03:30
Make cache-remote-host enabled when needed
Closes #34536 Signed-off-by: Pedro Ruivo <pruivo@redhat.com>
This commit is contained in:
parent
b82ec62eb7
commit
0bbe568d4f
@ -46,6 +46,7 @@ import java.util.Properties;
|
||||
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;
|
||||
@ -164,7 +165,7 @@ public class Picocli {
|
||||
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);
|
||||
@ -219,7 +220,7 @@ public class Picocli {
|
||||
// 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);
|
||||
@ -238,7 +239,7 @@ public class Picocli {
|
||||
// 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 -> {});
|
||||
}
|
||||
@ -259,7 +260,7 @@ public class Picocli {
|
||||
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));
|
||||
@ -327,13 +328,13 @@ public class Picocli {
|
||||
*
|
||||
* @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) {
|
||||
@ -350,6 +351,7 @@ public class Picocli {
|
||||
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) {
|
||||
@ -369,7 +371,14 @@ public class Picocli {
|
||||
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;
|
||||
}
|
||||
|
||||
@ -409,6 +418,9 @@ public class Picocli {
|
||||
}
|
||||
}
|
||||
|
||||
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)));
|
||||
@ -510,25 +522,21 @@ public class Picocli {
|
||||
}
|
||||
|
||||
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);
|
||||
@ -622,7 +630,7 @@ public class Picocli {
|
||||
protected PrintWriter getErrWriter() {
|
||||
return new PrintWriter(System.err, true);
|
||||
}
|
||||
|
||||
|
||||
protected PrintWriter getOutWriter() {
|
||||
return new PrintWriter(System.out, true);
|
||||
}
|
||||
@ -749,7 +757,7 @@ public class Picocli {
|
||||
} 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);
|
||||
@ -791,6 +799,7 @@ public class Picocli {
|
||||
.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()
|
||||
@ -854,19 +863,19 @@ public class Picocli {
|
||||
|
||||
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);
|
||||
@ -885,7 +894,7 @@ public class Picocli {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
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)
|
||||
|
||||
@ -1,8 +1,5 @@
|
||||
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;
|
||||
@ -10,15 +7,22 @@ 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() + "'";
|
||||
|
||||
@ -66,6 +70,8 @@ final class CachingPropertyMappers {
|
||||
.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)
|
||||
@ -76,10 +82,12 @@ final class CachingPropertyMappers {
|
||||
.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(),
|
||||
@ -152,4 +160,14 @@ final class CachingPropertyMappers {
|
||||
.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()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -66,7 +66,9 @@ public class PropertyMapper<T> {
|
||||
null,
|
||||
false,
|
||||
null,
|
||||
null) {
|
||||
null,
|
||||
() -> false,
|
||||
"") {
|
||||
@Override
|
||||
public ConfigValue getConfigValue(String name, ConfigSourceInterceptorContext context) {
|
||||
return context.proceed(name);
|
||||
@ -86,12 +88,14 @@ public class PropertyMapper<T> {
|
||||
private final String cliFormat;
|
||||
private final BiConsumer<PropertyMapper<T>, ConfigValue> validator;
|
||||
private final String description;
|
||||
private final BooleanSupplier required;
|
||||
private final String requiredWhen;
|
||||
|
||||
PropertyMapper(Option<T> option, String to, BooleanSupplier enabled, String enabledWhen,
|
||||
BiFunction<String, ConfigSourceInterceptorContext, String> mapper,
|
||||
String mapFrom, BiFunction<String, ConfigSourceInterceptorContext, String> parentMapper,
|
||||
String mapFrom, BiFunction<String, ConfigSourceInterceptorContext, String> parentMapper,
|
||||
String paramLabel, boolean mask, BiConsumer<PropertyMapper<T>, ConfigValue> validator,
|
||||
String description) {
|
||||
String description, BooleanSupplier required, String requiredWhen) {
|
||||
this.option = option;
|
||||
this.to = to == null ? getFrom() : to;
|
||||
this.enabled = enabled;
|
||||
@ -101,6 +105,8 @@ public class PropertyMapper<T> {
|
||||
this.paramLabel = paramLabel;
|
||||
this.mask = mask;
|
||||
this.cliFormat = toCliFormat(option.getKey());
|
||||
this.required = required;
|
||||
this.requiredWhen = requiredWhen;
|
||||
this.envVarFormat = toEnvVarFormat(getFrom());
|
||||
this.validator = validator;
|
||||
this.description = description;
|
||||
@ -132,7 +138,7 @@ public class PropertyMapper<T> {
|
||||
// if the property we want to map depends on another one, we use the value from the other property to call the mapper
|
||||
config = Configuration.getKcConfigValue(mapFrom);
|
||||
parentValue = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (config != null && config.getValue() != null) {
|
||||
config = transformValue(name, config, context, parentValue);
|
||||
@ -142,11 +148,11 @@ public class PropertyMapper<T> {
|
||||
.withValue(defaultValue).withRawValue(defaultValue).build(),
|
||||
context, false);
|
||||
}
|
||||
|
||||
|
||||
if (config != null) {
|
||||
return config;
|
||||
}
|
||||
|
||||
|
||||
// now try any defaults from quarkus
|
||||
return context.proceed(name);
|
||||
}
|
||||
@ -173,6 +179,16 @@ public class PropertyMapper<T> {
|
||||
this.enabledWhen = enabledWhen;
|
||||
}
|
||||
|
||||
public boolean isRequired() {
|
||||
return required.getAsBoolean();
|
||||
}
|
||||
|
||||
public Optional<String> getRequiredWhen() {
|
||||
return Optional.of(requiredWhen)
|
||||
.filter(StringUtil::isNotBlank)
|
||||
.map(e -> "Required when " + e);
|
||||
}
|
||||
|
||||
public Class<T> getType() {
|
||||
return this.option.getType();
|
||||
}
|
||||
@ -242,25 +258,25 @@ public class PropertyMapper<T> {
|
||||
private ConfigValue transformValue(String name, ConfigValue configValue, ConfigSourceInterceptorContext context, boolean parentValue) {
|
||||
String value = configValue.getValue();
|
||||
String mappedValue = value;
|
||||
|
||||
|
||||
boolean mapped = false;
|
||||
var theMapper = parentValue ? this.parentMapper : this.mapper;
|
||||
if (theMapper != null && (!name.equals(getFrom()) || parentValue)) {
|
||||
mappedValue = theMapper.apply(value, context);
|
||||
mapped = true;
|
||||
}
|
||||
|
||||
|
||||
// defaults and values from transformers may not have been subject to expansion
|
||||
if ((mapped || configValue.getConfigSourceName() == null) && mappedValue != null && Expressions.isEnabled() && mappedValue.contains("$")) {
|
||||
mappedValue = new ExpressionConfigSourceInterceptor().getValue(
|
||||
new ContextWrapper(context, new ConfigValueBuilder().withName(name).withValue(mappedValue).build()),
|
||||
name).getValue();
|
||||
}
|
||||
|
||||
|
||||
if (value == null && mappedValue == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
if (!mapped && name.equals(configValue.getName())) {
|
||||
return configValue;
|
||||
}
|
||||
@ -318,6 +334,8 @@ public class PropertyMapper<T> {
|
||||
private String paramLabel;
|
||||
private BiConsumer<PropertyMapper<T>, ConfigValue> validator = (mapper, value) -> mapper.validateValues(value, mapper::validateExpectedValues);
|
||||
private String description;
|
||||
private BooleanSupplier isRequired = () -> false;
|
||||
private String requiredWhen = "";
|
||||
|
||||
public Builder(Option<T> option) {
|
||||
this.option = option;
|
||||
@ -373,6 +391,29 @@ public class PropertyMapper<T> {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets this option as required when the {@link BooleanSupplier} returns {@code true}.
|
||||
* <p>
|
||||
* The {@code enableWhen} parameter is a message to show with the error message.
|
||||
* <p>
|
||||
* This check is only run in runtime mode.
|
||||
*/
|
||||
public Builder<T> isRequired(BooleanSupplier isRequired, String requiredWhen) {
|
||||
this.requiredWhen = Objects.requireNonNull(requiredWhen);
|
||||
assert !requiredWhen.endsWith(".");
|
||||
return isRequired(isRequired);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets this option as required when the {@link BooleanSupplier} returns {@code true}.
|
||||
* <p>
|
||||
* This check is only run in runtime mode.
|
||||
*/
|
||||
public Builder<T> isRequired(BooleanSupplier isRequired) {
|
||||
this.isRequired = Objects.requireNonNull(isRequired);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the validator, overwriting the current one.
|
||||
*/
|
||||
@ -384,7 +425,7 @@ public class PropertyMapper<T> {
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
public Builder<T> addValidator(BiConsumer<PropertyMapper<T>, ConfigValue> validator) {
|
||||
var current = this.validator;
|
||||
this.validator = (mapper, value) -> {
|
||||
@ -403,10 +444,10 @@ public class PropertyMapper<T> {
|
||||
};
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Similar to {@link #enabledWhen}, but uses the condition as a validator that is added to the current one. This allows the option
|
||||
* to appear in help.
|
||||
* to appear in help.
|
||||
* @return
|
||||
*/
|
||||
public Builder<T> addValidateEnabled(BooleanSupplier isEnabled, String enabledWhen) {
|
||||
@ -423,7 +464,7 @@ public class PropertyMapper<T> {
|
||||
if (paramLabel == null && Boolean.class.equals(option.getType())) {
|
||||
paramLabel = Boolean.TRUE + "|" + Boolean.FALSE;
|
||||
}
|
||||
return new PropertyMapper<T>(option, to, isEnabled, enabledWhen, mapper, mapFrom, parentMapper, paramLabel, isMasked, validator, description);
|
||||
return new PropertyMapper<>(option, to, isEnabled, enabledWhen, mapper, mapFrom, parentMapper, paramLabel, isMasked, validator, description, isRequired, requiredWhen);
|
||||
}
|
||||
}
|
||||
|
||||
@ -436,7 +477,7 @@ public class PropertyMapper<T> {
|
||||
validator.accept(this, value);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public boolean isList() {
|
||||
return getOption().getType() == java.util.List.class;
|
||||
}
|
||||
@ -453,8 +494,11 @@ public class PropertyMapper<T> {
|
||||
if (!result.isEmpty()) {
|
||||
result.append(".\n");
|
||||
}
|
||||
result.append("Invalid value for multivalued option " + getOptionAndSourceMessage(configValue)
|
||||
+ ": list value '" + v + "' should not have leading nor trailing whitespace");
|
||||
result.append("Invalid value for multivalued option ")
|
||||
.append(getOptionAndSourceMessage(configValue))
|
||||
.append(": list value '")
|
||||
.append(v)
|
||||
.append("' should not have leading nor trailing whitespace");
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
@ -466,7 +510,7 @@ public class PropertyMapper<T> {
|
||||
result.append(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (!result.isEmpty()) {
|
||||
throw new PropertyException(result.toString());
|
||||
}
|
||||
@ -488,7 +532,7 @@ public class PropertyMapper<T> {
|
||||
ShortErrorMessageHandler.getExpectedValuesMessage(expectedValues)));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
String getOptionAndSourceMessage(ConfigValue configValue) {
|
||||
if (isCliOption(configValue)) {
|
||||
return String.format("'%s'", this.getCliFormat());
|
||||
|
||||
@ -17,6 +17,8 @@
|
||||
|
||||
package org.keycloak.it.cli.dist;
|
||||
|
||||
import java.nio.file.Paths;
|
||||
|
||||
import io.quarkus.test.junit.main.Launch;
|
||||
import io.quarkus.test.junit.main.LaunchResult;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
@ -30,8 +32,6 @@ 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.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.keycloak.quarkus.runtime.cli.command.Main.CONFIG_FILE_LONG_NAME;
|
||||
@ -129,4 +129,68 @@ public class OptionsDistTest {
|
||||
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-level, --log-file-format, --log-file-output, --log-level")).count());
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(10)
|
||||
@Launch({"start-dev", "--cache-remote-host=localhost"})
|
||||
public void testCacheRemoteHostWithoutMultiSite(LaunchResult result) {
|
||||
assertErrorStreamContains(result, "cache-remote-host available only when feature 'multi-site', 'clusterless' or 'cache-embedded-remote-store' is set");
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(11)
|
||||
@Launch({"start-dev", "--cache-remote-port=11222"})
|
||||
public void testCacheRemotePortWithoutCacheRemoteHost(LaunchResult result) {
|
||||
assertDisabledDueToMissingRemoteHost(result, "--cache-remote-port");
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(12)
|
||||
@Launch({"start-dev", "--cache-remote-username=user"})
|
||||
public void testCacheRemoteUsernameWithoutCacheRemoteHost(LaunchResult result) {
|
||||
assertDisabledDueToMissingRemoteHost(result, "--cache-remote-username");
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(13)
|
||||
@Launch({"start-dev", "--cache-remote-password=pass"})
|
||||
public void testCacheRemotePasswordWithoutCacheRemoteHost(LaunchResult result) {
|
||||
assertDisabledDueToMissingRemoteHost(result, "--cache-remote-password");
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(14)
|
||||
@Launch({"start-dev", "--cache-remote-tls-enabled=false"})
|
||||
public void testCacheRemoteTlsEnabledWithoutCacheRemoteHost(LaunchResult result) {
|
||||
assertDisabledDueToMissingRemoteHost(result, "--cache-remote-tls-enabled");
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(15)
|
||||
@Launch({"start-dev", "--features=multi-site"})
|
||||
public void testMultiSiteWithoutCacheRemoteHost(LaunchResult result) {
|
||||
assertErrorStreamContains(result, "- cache-remote-host: Required when feature 'multi-site' or 'clusterless' is set.");
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(16)
|
||||
@Launch({"start-dev", "--features=multi-site", "--cache-remote-host=localhost", "--cache-remote-username=user"})
|
||||
public void testCacheRemoteUsernameWithoutCacheRemotePassword(LaunchResult result) {
|
||||
assertErrorStreamContains(result, "The option 'cache-remote-password' is required when 'cache-remote-username' is set.");
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(17)
|
||||
@Launch({"start-dev", "--features=multi-site", "--cache-remote-host=localhost", "--cache-remote-password=secret"})
|
||||
public void testCacheRemotePasswordWithoutCacheRemoteUsername(LaunchResult result) {
|
||||
assertErrorStreamContains(result, "The option 'cache-remote-username' is required when 'cache-remote-password' is set.");
|
||||
}
|
||||
|
||||
private static void assertDisabledDueToMissingRemoteHost(LaunchResult result, String option) {
|
||||
assertErrorStreamContains(result, "Disabled option: '%s'. Available only when remote host is set".formatted(option));
|
||||
}
|
||||
|
||||
private static void assertErrorStreamContains(LaunchResult result, String msg) {
|
||||
assertTrue(result.getErrorStream().stream().anyMatch(s -> s.contains(msg)));
|
||||
}
|
||||
}
|
||||
|
||||
@ -69,6 +69,9 @@ Cache:
|
||||
specified via XML file (see 'cache-config-file' option.). If the option is
|
||||
specified, 'cache-remote-username' and 'cache-remote-password' are required
|
||||
as well and the related configuration in XML file should not be present.
|
||||
Available only when feature 'multi-site', 'clusterless' or
|
||||
'cache-embedded-remote-store' is set. Required when feature 'multi-site' or
|
||||
'clusterless' is set.
|
||||
|
||||
Config:
|
||||
|
||||
@ -351,4 +354,4 @@ Bootstrap Admin:
|
||||
Do NOT start the server using this command when deploying to production.
|
||||
|
||||
Use 'kc.sh start-dev --help-all' to list all available options, including build
|
||||
options.
|
||||
options.
|
||||
|
||||
@ -72,6 +72,9 @@ Cache:
|
||||
specified via XML file (see 'cache-config-file' option.). If the option is
|
||||
specified, 'cache-remote-username' and 'cache-remote-password' are required
|
||||
as well and the related configuration in XML file should not be present.
|
||||
Available only when feature 'multi-site', 'clusterless' or
|
||||
'cache-embedded-remote-store' is set. Required when feature 'multi-site' or
|
||||
'clusterless' is set.
|
||||
--cache-remote-password <password>
|
||||
The password for the authentication to the remote server for the remote store.
|
||||
It replaces the 'password' attribute of 'digest' tag of the configuration
|
||||
@ -504,4 +507,4 @@ Bootstrap Admin:
|
||||
Do NOT start the server using this command when deploying to production.
|
||||
|
||||
Use 'kc.sh start-dev --help-all' to list all available options, including build
|
||||
options.
|
||||
options.
|
||||
|
||||
@ -70,6 +70,9 @@ Cache:
|
||||
specified via XML file (see 'cache-config-file' option.). If the option is
|
||||
specified, 'cache-remote-username' and 'cache-remote-password' are required
|
||||
as well and the related configuration in XML file should not be present.
|
||||
Available only when feature 'multi-site', 'clusterless' or
|
||||
'cache-embedded-remote-store' is set. Required when feature 'multi-site' or
|
||||
'clusterless' is set.
|
||||
--cache-stack <stack>
|
||||
Define the default stack to use for cluster communication and node discovery.
|
||||
Possible values are: tcp, udp, jdbc-ping, jdbc-ping-udp, kubernetes, ec2
|
||||
@ -361,4 +364,4 @@ By default, this command tries to update the server configuration by running a
|
||||
$ kc.sh start '--optimized'
|
||||
|
||||
By doing that, the server should start faster based on any previous
|
||||
configuration you have set when manually running the 'build' command.
|
||||
configuration you have set when manually running the 'build' command.
|
||||
|
||||
@ -73,6 +73,9 @@ Cache:
|
||||
specified via XML file (see 'cache-config-file' option.). If the option is
|
||||
specified, 'cache-remote-username' and 'cache-remote-password' are required
|
||||
as well and the related configuration in XML file should not be present.
|
||||
Available only when feature 'multi-site', 'clusterless' or
|
||||
'cache-embedded-remote-store' is set. Required when feature 'multi-site' or
|
||||
'clusterless' is set.
|
||||
--cache-remote-password <password>
|
||||
The password for the authentication to the remote server for the remote store.
|
||||
It replaces the 'password' attribute of 'digest' tag of the configuration
|
||||
@ -509,4 +512,4 @@ By default, this command tries to update the server configuration by running a
|
||||
$ kc.sh start '--optimized'
|
||||
|
||||
By doing that, the server should start faster based on any previous
|
||||
configuration you have set when manually running the 'build' command.
|
||||
configuration you have set when manually running the 'build' command.
|
||||
|
||||
@ -70,6 +70,9 @@ Cache:
|
||||
specified via XML file (see 'cache-config-file' option.). If the option is
|
||||
specified, 'cache-remote-username' and 'cache-remote-password' are required
|
||||
as well and the related configuration in XML file should not be present.
|
||||
Available only when feature 'multi-site', 'clusterless' or
|
||||
'cache-embedded-remote-store' is set. Required when feature 'multi-site' or
|
||||
'clusterless' is set.
|
||||
--cache-stack <stack>
|
||||
Define the default stack to use for cluster communication and node discovery.
|
||||
Possible values are: tcp, udp, jdbc-ping, jdbc-ping-udp, kubernetes, ec2
|
||||
@ -303,4 +306,4 @@ By default, this command tries to update the server configuration by running a
|
||||
$ kc.sh start '--optimized'
|
||||
|
||||
By doing that, the server should start faster based on any previous
|
||||
configuration you have set when manually running the 'build' command.
|
||||
configuration you have set when manually running the 'build' command.
|
||||
|
||||
@ -73,6 +73,9 @@ Cache:
|
||||
specified via XML file (see 'cache-config-file' option.). If the option is
|
||||
specified, 'cache-remote-username' and 'cache-remote-password' are required
|
||||
as well and the related configuration in XML file should not be present.
|
||||
Available only when feature 'multi-site', 'clusterless' or
|
||||
'cache-embedded-remote-store' is set. Required when feature 'multi-site' or
|
||||
'clusterless' is set.
|
||||
--cache-remote-password <password>
|
||||
The password for the authentication to the remote server for the remote store.
|
||||
It replaces the 'password' attribute of 'digest' tag of the configuration
|
||||
@ -437,4 +440,4 @@ By default, this command tries to update the server configuration by running a
|
||||
$ kc.sh start '--optimized'
|
||||
|
||||
By doing that, the server should start faster based on any previous
|
||||
configuration you have set when manually running the 'build' command.
|
||||
configuration you have set when manually running the 'build' command.
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user