enhance: adding the ability to get the root config from a Scope

closes: #36268

Signed-off-by: Steve Hawkins <shawkins@redhat.com>
This commit is contained in:
Steven Hawkins 2025-08-05 12:51:33 -04:00 committed by GitHub
parent d3d217e074
commit 11924e6473
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 58 additions and 32 deletions

View File

@ -208,6 +208,11 @@ public class Config {
throw new UnsupportedOperationException("Not implemented");
}
@Override
public Scope root() {
return new SystemPropertiesScope("keycloak.");
}
}
/**
@ -243,5 +248,13 @@ public class Config {
*/
@Deprecated
Set<String> getPropertyNames();
/**
* Root {@link Scope} for global options. The key format should match exactly what
* is expected to appear in the main configuration file - e.g. metrics-enabled, db, etc.
*
* @return a {@link Scope} with access to global configuration properties.
*/
Scope root();
}
}

View File

@ -73,7 +73,6 @@ public class DefaultCacheEmbeddedConfigProviderFactory implements CacheEmbeddedC
// Configuration
public static final String CONFIG = "configFile";
private static final String METRICS = "metricsEnabled";
public static final String TRACING = "tracingEnabled";
private static final String HISTOGRAMS = "metricsHistogramsEnabled";
public static final String STACK = "stack";
@ -119,7 +118,6 @@ public class DefaultCacheEmbeddedConfigProviderFactory implements CacheEmbeddedC
var builder = ProviderConfigurationBuilder.create();
Util.copyFromOption(builder, CONFIG, "file", ProviderConfigProperty.STRING_TYPE, CachingOptions.CACHE_CONFIG_FILE, false);
Util.copyFromOption(builder, HISTOGRAMS, "enabled", ProviderConfigProperty.BOOLEAN_TYPE, CachingOptions.CACHE_METRICS_HISTOGRAMS_ENABLED, false);
Util.copyFromOption(builder, METRICS, "enabled", ProviderConfigProperty.BOOLEAN_TYPE, MetricsOptions.INFINISPAN_METRICS_ENABLED, false);
Stream.concat(Arrays.stream(LOCAL_CACHE_NAMES), Arrays.stream(CLUSTERED_MAX_COUNT_CACHES))
.forEach(name -> Util.copyFromOption(builder, CacheConfigurator.maxCountConfigKey(name), "max-count", ProviderConfigProperty.INTEGER_TYPE, CachingOptions.maxCountOption(name), false));
createTopologyProperties(builder);
@ -220,7 +218,7 @@ public class DefaultCacheEmbeddedConfigProviderFactory implements CacheEmbeddedC
private static void configureMetrics(Config.Scope keycloakConfig, ConfigurationBuilderHolder holder) {
//metrics are disabled by default (check MetricsOptions class)
if (keycloakConfig.getBoolean(METRICS, Boolean.FALSE)) {
if (keycloakConfig.root().getBoolean(MetricsOptions.METRICS_ENABLED.getKey(), Boolean.FALSE)) {
logger.debug("Enabling Infinispan metrics");
var builder = holder.getGlobalConfigurationBuilder();
builder.addModule(MicrometerMeterRegisterConfigurationBuilder.class)

View File

@ -9,17 +9,4 @@ public class MetricsOptions {
.defaultValue(Boolean.FALSE)
.build();
public static final Option<Boolean> PASSWORD_VALIDATION_COUNTER_ENABLED = new OptionBuilder<>("metrics-password-validation-counter-enabled", Boolean.class)
.category(OptionCategory.METRICS)
.description("If the server should publish counter metric for number of password validations performed.")
.defaultValue(Boolean.TRUE)
.hidden() // This is intended to be enabled all the time when global metrics are enabled, therefore this option is hidden
.build();
public static final Option<Boolean> INFINISPAN_METRICS_ENABLED = new OptionBuilder<>("infinispan-metrics-enabled", Boolean.class)
.category(OptionCategory.METRICS)
.description("If Infinispan metrics should be collected and exposed.")
.defaultValue(Boolean.FALSE)
.hidden() // Intentional, Infinispan metrics are enabled when '--metrics-enabled' are enabled.
.build();
}

View File

@ -26,6 +26,7 @@ import java.util.stream.StreamSupport;
import org.eclipse.microprofile.config.ConfigProvider;
import org.keycloak.Config;
import org.keycloak.Config.Scope;
public class MicroProfileConfigProvider implements Config.ConfigProvider {
@ -132,11 +133,19 @@ public class MicroProfileConfigProvider implements Config.ConfigProvider {
}
private <T> T getValue(String key, Class<T> clazz, T defaultValue) {
if (NS_KEYCLOAK_PREFIX.equals(separatorPrefix)) {
return config.getOptionalValue(separatorPrefix.concat(key), clazz).orElse(defaultValue);
}
String dashCase = toDashCase(key);
return config.getOptionalValue(separatorPrefix.concat(dashCase), clazz)
.or(() -> config.getOptionalValue(prefix.concat(dashCase), clazz)).orElse(defaultValue);
}
@Override
public Scope root() {
return new MicroProfileScope(NS_KEYCLOAK_PREFIX);
}
}
}

View File

@ -16,15 +16,6 @@ final class MetricsPropertyMappers {
return new PropertyMapper[] {
fromOption(MetricsOptions.METRICS_ENABLED)
.to("quarkus.micrometer.enabled")
.build(),
fromOption(MetricsOptions.PASSWORD_VALIDATION_COUNTER_ENABLED)
.to("kc.spi-credential--keycloak-password--metrics-enabled")
.isEnabled(MetricsPropertyMappers::metricsEnabled, "metrics are enabled")
.build(),
fromOption(MetricsOptions.INFINISPAN_METRICS_ENABLED)
.mapFrom(MetricsOptions.METRICS_ENABLED)
.to("kc.spi-cache-embedded--default--metrics-enabled")
.isEnabled(MetricsPropertyMappers::metricsEnabled, "metrics are enabled")
.build()
};
}

View File

@ -61,7 +61,11 @@ public class ConfigurationTest extends AbstractConfigurationTest {
putEnvVar("KC_SPI_CAMEL_CASE_SCOPE_CAMEL_CASE_PROP", "foobar");
initConfig();
String value = Config.scope("camelCaseScope").get("camelCaseProp");
assertEquals(value, "foobar");
assertEquals("foobar", value);
// root should be at kc - users are not expected to obtain spi options this way
value = Config.scope().root().get("spi-camel-case-scope-camel-case-prop");
assertEquals("foobar", value);
}
@Test
@ -69,7 +73,11 @@ public class ConfigurationTest extends AbstractConfigurationTest {
putEnvVar("KC_SPI_CAMEL_CASE_SCOPE__CAMEL_CASE_PROP", "foobar");
initConfig();
String value = Config.scope("camelCaseScope").get("camelCaseProp");
assertEquals(value, "foobar");
assertEquals("foobar", value);
// root should be at kc - users are not expected to obtain spi options this way
value = Config.scope().root().get("spi-camel-case-scope--camel-case-prop");
assertEquals("foobar", value);
}
@Test

View File

@ -1,13 +1,13 @@
/*
* Copyright 2021 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.
@ -123,4 +123,9 @@ public class ComponentModelScope implements Scope {
return componentConfig;
}
@Override
public Scope root() {
return this.origScope.root();
}
}

View File

@ -38,7 +38,7 @@ public interface ProviderFactory<T extends Provider> {
T create(KeycloakSession session);
/**
* Only called once when the factory is first created. This config is pulled from keycloak_server.json
* Only called once when the factory is first created.
*
* @param config
*/

View File

@ -20,6 +20,7 @@ import io.micrometer.core.instrument.Counter;
import io.micrometer.core.instrument.Meter;
import io.micrometer.core.instrument.Metrics;
import org.keycloak.Config;
import org.keycloak.config.MetricsOptions;
import org.keycloak.models.KeycloakSession;
import org.keycloak.provider.ProviderConfigProperty;
import org.keycloak.provider.ProviderConfigurationBuilder;
@ -61,7 +62,7 @@ public class PasswordCredentialProviderFactory implements CredentialProviderFact
@Override
public void init(Config.Scope config) {
metricsEnabled = config.getBoolean("metrics-enabled", false);
metricsEnabled = config.root().getBoolean(MetricsOptions.METRICS_ENABLED.getKey(), false);
if (metricsEnabled) {
meterProvider = Counter.builder(LOGIN_PASSWORD_VERIFY_METER_NAME)
.description(LOGIN_PASSWORD_VERIFY_METER_DESCRIPTION)

View File

@ -20,7 +20,10 @@ package org.keycloak.utils;
import java.util.Set;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.NullNode;
import org.keycloak.Config;
import org.keycloak.Config.Scope;
import org.keycloak.common.util.StringPropertyReplacer;
import org.keycloak.common.util.SystemEnvProperties;
@ -192,6 +195,11 @@ public class JsonConfigProvider implements Config.ConfigProvider {
throw new UnsupportedOperationException("Not implemented");
}
@Override
public Scope root() {
return new JsonScope(NullNode.getInstance());
}
}
}

View File

@ -4,6 +4,7 @@ import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.keycloak.Config;
import org.keycloak.Config.Scope;
import org.keycloak.models.KeycloakSession;
import org.keycloak.services.resteasy.ResteasyKeycloakSession;
import org.keycloak.services.resteasy.ResteasyKeycloakSessionFactory;
@ -136,6 +137,11 @@ public class PlainTextVaultProviderFactoryTest {
public Set<String> getPropertyNames() {
throw new UnsupportedOperationException("not implemented");
}
@Override
public Scope root() {
throw new UnsupportedOperationException("not implemented");
}
}
}