fix: moving nonserver defaults out of application.properties

closes: #42332

Signed-off-by: Steve Hawkins <shawkins@redhat.com>
This commit is contained in:
Steve Hawkins 2025-12-10 13:03:19 -05:00 committed by Martin Bartoš
parent 6ceaa2d391
commit eff97618ef
23 changed files with 49 additions and 33 deletions

View file

@ -32,6 +32,7 @@ public class Environment {
public static final String PROFILE = "kc.profile";
public static final String ENV_PROFILE = "KC_PROFILE";
public static final String DEV_PROFILE_VALUE = "dev";
public static final String NON_SERVER_MODE = "nonserver";
public static int getServerStartupTimeout() {
String timeout = System.getProperty("jboss.as.management.blocking.timeout");
@ -55,8 +56,12 @@ public class Environment {
// Otherwise try to auto-detect
for (Provider provider : Security.getProviders()) {
if (provider.getName().equals("BCFIPS")) continue; // Ignore BCFIPS provider for the detection as we may register it programatically
if (provider.getName().toUpperCase().contains("FIPS")) return true;
if (provider.getName().equals("BCFIPS")) {
continue; // Ignore BCFIPS provider for the detection as we may register it programatically
}
if (provider.getName().toUpperCase().contains("FIPS")) {
return true;
}
}
return false;
}
@ -74,4 +79,9 @@ public class Environment {
return System.getenv(ENV_PROFILE);
}
public static boolean isNonServerMode() {
return NON_SERVER_MODE.equalsIgnoreCase(Environment.getProfile());
}
}

View file

@ -48,7 +48,6 @@ public class CachingOptions {
.description("Defines the cache mechanism for high-availability. "
+ "By default in production mode, a 'ispn' cache is used to create a cluster between multiple server nodes. "
+ "By default in development mode, a 'local' cache disables clustering and is intended for development and testing purposes.")
.defaultValue(Mechanism.ispn)
.build();
public enum Stack {

View file

@ -42,7 +42,6 @@ public final class Environment {
public static final String KC_CONFIG_BUILT = "kc.config.built";
public static final String KC_TEST_REBUILD = "kc.test.rebuild";
private static final String KC_HOME_DIR = "kc.home.dir";
public static final String NON_SERVER_MODE = "nonserver";
public static final String PROFILE ="kc.profile";
public static final String ENV_PROFILE ="KC_PROFILE";
public static final String DATA_PATH = File.separator + "data";
@ -105,10 +104,6 @@ public final class Environment {
return Optional.ofNullable(org.keycloak.common.util.Environment.getProfile()).orElse("").equalsIgnoreCase(org.keycloak.common.util.Environment.DEV_PROFILE_VALUE);
}
public static boolean isNonServerMode() {
return NON_SERVER_MODE.equalsIgnoreCase(org.keycloak.common.util.Environment.getProfile());
}
public static boolean isWindows() {
return NetworkUtils.checkForWindows();
}

View file

@ -40,8 +40,8 @@ import io.quarkus.runtime.annotations.QuarkusMain;
import org.jboss.logging.Logger;
import picocli.CommandLine;
import static org.keycloak.common.util.Environment.isNonServerMode;
import static org.keycloak.quarkus.runtime.Environment.getKeycloakModeFromProfile;
import static org.keycloak.quarkus.runtime.Environment.isNonServerMode;
import static org.keycloak.quarkus.runtime.Environment.isTestLaunchMode;
/**

View file

@ -19,8 +19,8 @@ package org.keycloak.quarkus.runtime.cli.command;
import java.util.EnumSet;
import org.keycloak.common.util.Environment;
import org.keycloak.config.OptionCategory;
import org.keycloak.quarkus.runtime.Environment;
import org.keycloak.quarkus.runtime.integration.jaxrs.QuarkusKeycloakApplication;
import picocli.CommandLine;

View file

@ -11,6 +11,7 @@ import java.util.function.BooleanSupplier;
import org.keycloak.common.Profile;
import org.keycloak.config.CachingOptions;
import org.keycloak.config.CachingOptions.Mechanism;
import org.keycloak.config.Option;
import org.keycloak.infinispan.util.InfinispanUtils;
import org.keycloak.quarkus.runtime.Environment;
@ -36,6 +37,10 @@ final class CachingPropertyMappers implements PropertyMapperGrouping {
public List<PropertyMapper<?>> getPropertyMappers() {
List<PropertyMapper<?>> staticMappers = List.of(
fromOption(CachingOptions.CACHE)
.transformer(
(value, context) -> org.keycloak.common.util.Environment.isNonServerMode()
? Mechanism.local.name()
: Optional.ofNullable(value).orElse(Mechanism.ispn.name()))
.paramLabel("type")
.build(),
fromOption(CachingOptions.CACHE_STACK)

View file

@ -192,7 +192,7 @@ public final class HttpPropertyMappers implements PropertyMapperGrouping {
}
private static boolean isHttpEnabled(String value) {
if (Environment.isDevMode() || Environment.isNonServerMode()) {
if (Environment.isDevMode() || org.keycloak.common.util.Environment.isNonServerMode()) {
return true;
}
return Boolean.parseBoolean(value);

View file

@ -292,7 +292,7 @@ public class PropertyMapper<T> {
// fall back to the transformer when no mapper is explicitly specified in .mapFrom()
var theMapper = parentValue && parentMapper != null ? this.parentMapper : this.mapper;
// since our mapping logic assumes fully resolved values, we cannot reliably map if Expressions are disabled
if (Expressions.isEnabled() && theMapper != null && (!name.equals(getFrom()) || parentValue)) {
if (Expressions.isEnabled() && theMapper != null && (name.equals(getTo()) || parentValue)) {
mappedValue = theMapper.map(getNamedProperty().orElse(null), value, context);
mapped = true;
}

View file

@ -33,6 +33,7 @@ import jakarta.persistence.EntityManagerFactory;
import org.keycloak.ServerStartupError;
import org.keycloak.common.Version;
import org.keycloak.common.util.Environment;
import org.keycloak.config.DatabaseOptions;
import org.keycloak.config.database.Database;
import org.keycloak.connections.jpa.updater.JpaUpdaterProvider;
@ -46,7 +47,6 @@ import org.keycloak.models.dblock.DBLockProvider;
import org.keycloak.provider.ProviderConfigProperty;
import org.keycloak.provider.ProviderConfigurationBuilder;
import org.keycloak.provider.ServerInfoAwareProviderFactory;
import org.keycloak.quarkus.runtime.Environment;
import org.keycloak.quarkus.runtime.configuration.Configuration;
import io.quarkus.arc.Arc;
@ -303,8 +303,9 @@ public class QuarkusJpaConnectionProviderFactory extends AbstractJpaConnectionPr
private void checkMySQLWaitTimeout() {
String db = Configuration.getConfigValue(DatabaseOptions.DB).getValue();
Database.Vendor vendor = Database.getVendor(db).orElseThrow();
if (!(Database.Vendor.MYSQL == vendor || Database.Vendor.MARIADB == vendor))
if (!(Database.Vendor.MYSQL == vendor || Database.Vendor.MARIADB == vendor)) {
return;
}
try (Connection connection = getConnection();
Statement statement = connection.createStatement();

View file

@ -83,10 +83,7 @@ quarkus.http.limits.max-header-size=65535
%dev.kc.spi-theme--static-max-age=-1
# The default configuration when running import, export, bootstrap-admin
%nonserver.kc.http-enabled=true
%nonserver.quarkus.http.host-enabled=false
%nonserver.kc.hostname-strict=false
%nonserver.kc.cache=local
%nonserver.kc.spi-connections-jpa--quarkus--migration-strategy=validate
%nonserver.kc.spi-connections-jpa--quarkus--initialize-empty=true

View file

@ -526,8 +526,9 @@ public class ConfigurationTest extends AbstractConfigurationTest {
@Test
public void testResolvePropertyFromDefaultProfile() {
Environment.setProfile(Environment.NON_SERVER_MODE);
assertEquals("false", createConfig().getConfigValue("kc.hostname-strict").getValue());
Environment.setProfile(org.keycloak.common.util.Environment.NON_SERVER_MODE);
var config = createConfig();
assertEquals("local", config.getConfigValue("kc.cache").getValue());
Environment.setProfile("prod");
assertEquals("true", createConfig().getConfigValue("kc.spi-hostname-v2-hostname-strict").getValue());

View file

@ -53,6 +53,7 @@ public class ImportDistTest {
cliResult = dist.run("export", "--realm=master", "--dir=" + dir.getAbsolutePath());
cliResult.assertMessage("Export of realm 'master' requested.");
cliResult.assertMessage("Export finished successfully");
cliResult.assertNoMessage("local_addr");
// add a placeholder into the realm
ObjectMapper mapper = new ObjectMapper();
@ -62,11 +63,14 @@ public class ImportDistTest {
mapper.writer().writeValue(file, node);
dist.setEnvVar("REALM_ENABLED", "true");
dist.setEnvVar("KC_HOSTNAME_STRICT", "false");
dist.setEnvVar("KC_CACHE", "ispn");
cliResult = dist.run("import", "--dir=" + dir.getAbsolutePath());
cliResult.assertMessage("Realm 'master' imported");
cliResult.assertMessage("Import finished successfully");
cliResult.assertNoMessage("Changes detected in configuration");
cliResult.assertNoMessage("Listening on: http");
cliResult.assertNoMessage("local_addr");
cliResult = dist.run("import");
cliResult.assertError("Must specify either --dir or --file options.");

View file

@ -21,7 +21,7 @@ Cache:
mode, a 'ispn' cache is used to create a cluster between multiple server
nodes. By default in development mode, a 'local' cache disables clustering
and is intended for development and testing purposes. Possible values are:
ispn, local. Default: ispn.
ispn, local.
--cache-config-file <file>
Defines the file from which cache configuration should be loaded from. The
configuration file is relative to the 'conf/' directory.

View file

@ -21,7 +21,7 @@ Cache:
mode, a 'ispn' cache is used to create a cluster between multiple server
nodes. By default in development mode, a 'local' cache disables clustering
and is intended for development and testing purposes. Possible values are:
ispn, local. Default: ispn.
ispn, local.
--cache-config-file <file>
Defines the file from which cache configuration should be loaded from. The
configuration file is relative to the 'conf/' directory.

View file

@ -22,7 +22,7 @@ Cache:
mode, a 'ispn' cache is used to create a cluster between multiple server
nodes. By default in development mode, a 'local' cache disables clustering
and is intended for development and testing purposes. Possible values are:
ispn, local. Default: ispn.
ispn, local.
--cache-config-file <file>
Defines the file from which cache configuration should be loaded from. The
configuration file is relative to the 'conf/' directory.

View file

@ -22,7 +22,7 @@ Cache:
mode, a 'ispn' cache is used to create a cluster between multiple server
nodes. By default in development mode, a 'local' cache disables clustering
and is intended for development and testing purposes. Possible values are:
ispn, local. Default: ispn.
ispn, local.
--cache-config-file <file>
Defines the file from which cache configuration should be loaded from. The
configuration file is relative to the 'conf/' directory.

View file

@ -22,7 +22,7 @@ Cache:
mode, a 'ispn' cache is used to create a cluster between multiple server
nodes. By default in development mode, a 'local' cache disables clustering
and is intended for development and testing purposes. Possible values are:
ispn, local. Default: ispn.
ispn, local.
--cache-config-file <file>
Defines the file from which cache configuration should be loaded from. The
configuration file is relative to the 'conf/' directory.

View file

@ -22,7 +22,7 @@ Cache:
mode, a 'ispn' cache is used to create a cluster between multiple server
nodes. By default in development mode, a 'local' cache disables clustering
and is intended for development and testing purposes. Possible values are:
ispn, local. Default: ispn.
ispn, local.
--cache-config-file <file>
Defines the file from which cache configuration should be loaded from. The
configuration file is relative to the 'conf/' directory.

View file

@ -21,7 +21,7 @@ Cache:
mode, a 'ispn' cache is used to create a cluster between multiple server
nodes. By default in development mode, a 'local' cache disables clustering
and is intended for development and testing purposes. Possible values are:
ispn, local. Default: ispn.
ispn, local.
--cache-config-file <file>
Defines the file from which cache configuration should be loaded from. The
configuration file is relative to the 'conf/' directory.

View file

@ -21,7 +21,7 @@ Cache:
mode, a 'ispn' cache is used to create a cluster between multiple server
nodes. By default in development mode, a 'local' cache disables clustering
and is intended for development and testing purposes. Possible values are:
ispn, local. Default: ispn.
ispn, local.
--cache-config-file <file>
Defines the file from which cache configuration should be loaded from. The
configuration file is relative to the 'conf/' directory.

View file

@ -19,7 +19,7 @@ Cache:
mode, a 'ispn' cache is used to create a cluster between multiple server
nodes. By default in development mode, a 'local' cache disables clustering
and is intended for development and testing purposes. Possible values are:
ispn, local. Default: ispn.
ispn, local.
--cache-config-file <file>
Defines the file from which cache configuration should be loaded from. The
configuration file is relative to the 'conf/' directory.

View file

@ -19,7 +19,7 @@ Cache:
mode, a 'ispn' cache is used to create a cluster between multiple server
nodes. By default in development mode, a 'local' cache disables clustering
and is intended for development and testing purposes. Possible values are:
ispn, local. Default: ispn.
ispn, local.
--cache-config-file <file>
Defines the file from which cache configuration should be loaded from. The
configuration file is relative to the 'conf/' directory.

View file

@ -23,6 +23,7 @@ import java.util.Optional;
import org.keycloak.Config;
import org.keycloak.common.Profile;
import org.keycloak.common.util.Environment;
import org.keycloak.models.KeycloakSession;
import org.keycloak.provider.EnvironmentDependentProviderFactory;
import org.keycloak.urls.HostnameProvider;
@ -34,9 +35,9 @@ import org.jboss.logging.Logger;
* @author Vaclav Muzikar <vmuzikar@redhat.com>
*/
public class HostnameV2ProviderFactory implements HostnameProviderFactory, EnvironmentDependentProviderFactory {
private static final Logger LOGGER = Logger.getLogger(HostnameV2ProviderFactory.class);
private static final String INVALID_HOSTNAME = "Provided hostname is neither a plain hostname nor a valid URL";
private String hostname;
private URI hostnameUrl;
@ -45,6 +46,9 @@ public class HostnameV2ProviderFactory implements HostnameProviderFactory, Envir
@Override
public void init(Config.Scope config) {
if (Environment.isNonServerMode()) {
return;
}
// Strict mode is used just for enforcing that hostname is set
boolean strictMode = config.getBoolean("hostname-strict", false);
@ -68,7 +72,7 @@ public class HostnameV2ProviderFactory implements HostnameProviderFactory, Envir
Optional.ofNullable(config.get("hostname-admin")).ifPresent(h ->
adminUrl = validateAndCreateUri(h, "Provided hostname-admin is not a valid URL"));
if (adminUrl != null && hostnameUrl == null) {
throw new IllegalArgumentException("hostname must be set to a URL when hostname-admin is set");
}
@ -83,7 +87,7 @@ public class HostnameV2ProviderFactory implements HostnameProviderFactory, Envir
throw new IllegalArgumentException("hostname-backchannel-dynamic must be set to false if hostname is not provided as full URL");
}
}
private void validateAndSetHostname(String hostname) {
URI result;
try {