task: clarifying home dir unset logic (#43904)

closes: #43903

Signed-off-by: Steve Hawkins <shawkins@redhat.com>
This commit is contained in:
Steven Hawkins
2025-11-05 02:45:06 -05:00
committed by GitHub
parent e321f5ab23
commit a04d5d7b5e
16 changed files with 56 additions and 167 deletions

View File

@@ -48,6 +48,7 @@ import io.quarkus.narayana.jta.runtime.TransactionManagerBuildTimeConfig;
import io.quarkus.narayana.jta.runtime.TransactionManagerBuildTimeConfig.UnsafeMultipleLastResourcesMode;
import io.quarkus.resteasy.reactive.server.spi.MethodScannerBuildItem;
import io.quarkus.resteasy.reactive.server.spi.PreExceptionMapperHandlerBuildItem;
import io.quarkus.runtime.LaunchMode;
import io.quarkus.runtime.configuration.ConfigurationException;
import io.quarkus.vertx.http.deployment.FilterBuildItem;
import io.quarkus.vertx.http.deployment.HttpRootPathBuildItem;
@@ -997,7 +998,7 @@ class KeycloakProcessor {
URL url = descriptorsUrls.nextElement();
List<ScriptProviderDescriptor> descriptors = getScriptProviderDescriptorsFromJarFile(url);
if (!Environment.isDistribution()) {
if (LaunchMode.current().isDevOrTest() || Environment.getHomeDir().isEmpty()) {
// script providers are only loaded from classpath when running embedded
descriptors = new ArrayList<>(descriptors);
descriptors.addAll(getScriptProviderDescriptorsFromClassPath(url));

View File

@@ -54,36 +54,24 @@ public final class Environment {
return Boolean.getBoolean("quarkus.launch.rebuild");
}
public static String getHomeDir() {
return System.getProperty(KC_HOME_DIR);
public static Optional<String> getHomeDir() {
return Optional.ofNullable(System.getProperty(KC_HOME_DIR));
}
public static Path getHomePath() {
String homeDir = getHomeDir();
if (homeDir != null) {
return Paths.get(homeDir);
}
return null;
public static Optional<Path> getHomePath() {
return getHomeDir().map(Paths::get);
}
public static String getDataDir() {
return getHomeDir() + DATA_PATH;
public static Optional<String> getDataDir() {
return getHomeDir().map(p -> p.concat(DATA_PATH));
}
public static String getDefaultThemeRootDir() {
return getHomeDir() + DEFAULT_THEMES_PATH;
public static Optional<String> getDefaultThemeRootDir() {
return getHomeDir().map(p -> p.concat(DEFAULT_THEMES_PATH));
}
public static Path getProvidersPath() {
Path homePath = Environment.getHomePath();
if (homePath != null) {
return homePath.resolve("providers");
}
return null;
public static Optional<Path> getProvidersPath() {
return Environment.getHomePath().map(p -> p.resolve("providers"));
}
public static String getCommand() {
@@ -128,7 +116,7 @@ public final class Environment {
}
public static Map<String, File> getProviderFiles() {
Path providersPath = Environment.getProvidersPath();
Path providersPath = Environment.getProvidersPath().orElse(null);
if (providersPath == null) {
return Collections.emptyMap();
@@ -186,13 +174,6 @@ public final class Environment {
return profile;
}
public static boolean isDistribution() {
if (LaunchMode.current().isDevOrTest()) {
return false;
}
return getHomeDir() != null;
}
public static boolean isRebuildCheck() {
return Boolean.getBoolean(KC_CONFIG_REBUILD_CHECK);
}
@@ -226,10 +207,6 @@ public final class Environment {
return profile;
}
public static void removeHomeDir() {
System.getProperties().remove(KC_HOME_DIR);
}
public static void setRebuild() {
System.setProperty("quarkus.launch.rebuild", "true");
}

View File

@@ -88,9 +88,9 @@ public class KeycloakRecorder {
String[] truststores = Configuration.getOptionalKcValue(TruststoreOptions.TRUSTSTORE_PATHS.getKey())
.map(s -> s.split(",")).orElse(new String[0]);
String dataDir = Environment.getDataDir();
Optional<String> dataDir = Environment.getDataDir();
File truststoresDir = Optional.ofNullable(Environment.getHomePath()).map(path -> path.resolve("conf").resolve("truststores").toFile()).orElse(null);
File truststoresDir = Environment.getHomePath().map(p -> p.resolve("conf").resolve("truststores").toFile()).orElse(null);
if (truststoresDir != null && truststoresDir.exists() && Optional.ofNullable(truststoresDir.list()).map(a -> a.length).orElse(0) > 0) {
truststores = Stream.concat(Stream.of(truststoresDir.getAbsolutePath()), Stream.of(truststores)).toArray(String[]::new);
@@ -98,7 +98,7 @@ public class KeycloakRecorder {
return; // nothing to configure, we'll just use the system default
}
TruststoreBuilder.setSystemTruststore(truststores, true, dataDir);
TruststoreBuilder.setSystemTruststore(truststores, true, dataDir.orElseThrow());
}
public void configureLiquibase(Map<String, List<String>> services) {

View File

@@ -22,7 +22,7 @@ import static org.keycloak.config.DatabaseOptions.DB;
import static org.keycloak.quarkus.runtime.Environment.getHomePath;
import static org.keycloak.quarkus.runtime.Environment.isDevProfile;
import io.quarkus.runtime.LaunchMode;
import java.util.Optional;
import org.keycloak.quarkus.runtime.Environment;
import org.keycloak.quarkus.runtime.Messages;
@@ -30,13 +30,11 @@ import org.keycloak.quarkus.runtime.configuration.Configuration;
import org.keycloak.quarkus.runtime.configuration.PersistedConfigSource;
import io.quarkus.bootstrap.runner.RunnerClassLoader;
import io.quarkus.runtime.LaunchMode;
import io.smallrye.config.ConfigValue;
import picocli.CommandLine;
import picocli.CommandLine.Command;
import java.util.Optional;
@Command(name = Build.NAME,
header = "Creates a new and optimized server image.",
description = {
@@ -143,7 +141,7 @@ public final class Build extends AbstractCommand {
private void cleanTempResources() {
if (!LaunchMode.current().isDevOrTest()) {
// only needed for dev/testing purposes
Optional.ofNullable(getHomePath()).ifPresent(path -> path.resolve("quarkus-artifact.properties").toFile().delete());
getHomePath().ifPresent(path -> path.resolve("quarkus-artifact.properties").toFile().delete());
}
}

View File

@@ -35,10 +35,7 @@ public final class ImportRealmMixin {
paramLabel = NO_PARAM_LABEL,
arity = "0")
public void setImportRealm(boolean importRealm) {
File importDir = Environment.getHomePath().resolve("data").resolve("import").toFile();
if (importDir.exists()) {
ExportImportConfig.setDir(importDir.getAbsolutePath());
}
Environment.getHomePath().map(p -> p.resolve("data").resolve("import").toFile()).filter(File::exists)
.map(File::getAbsolutePath).ifPresent(ExportImportConfig::setDir);
}
}

View File

@@ -89,15 +89,9 @@ public class KeycloakPropertiesConfigSource extends AbstractLocationConfigSource
}
if (filePath == null) {
String homeDir = Environment.getHomeDir();
if (homeDir != null) {
File file = Paths.get(homeDir, "conf", KeycloakPropertiesConfigSource.KEYCLOAK_CONF_FILE).toFile();
if (file.exists()) {
filePath = file.getAbsolutePath();
}
}
filePath = Environment.getHomeDir()
.map(f -> Paths.get(f, "conf", KeycloakPropertiesConfigSource.KEYCLOAK_CONF_FILE).toFile())
.filter(File::exists).map(File::getAbsolutePath).orElse(null);
}
if (filePath == null) {

View File

@@ -114,7 +114,7 @@ public final class PersistedConfigSource extends PropertiesConfigSource {
}
private static InputStream loadPersistedConfig() {
Path homePath = Environment.getHomePath();
Path homePath = Environment.getHomePath().orElse(null);
if (homePath == null) {
return null;
@@ -172,7 +172,7 @@ public final class PersistedConfigSource extends PropertiesConfigSource {
}
public void saveDryRunProperties() throws FileNotFoundException, IOException {
Path path = Environment.getHomePath().resolve("lib").resolve("dryRun.properties");
Path path = Environment.getHomePath().orElseThrow().resolve("lib").resolve("dryRun.properties");
var properties = Picocli.getNonPersistedBuildTimeOptions();
try (FileOutputStream fos = new FileOutputStream(path.toFile())) {
properties.store(fos, null);
@@ -180,7 +180,7 @@ public final class PersistedConfigSource extends PropertiesConfigSource {
}
public void useDryRunProperties() {
Path path = Environment.getHomePath().resolve("lib").resolve("dryRun.properties");
Path path = Environment.getHomePath().orElseThrow().resolve("lib").resolve("dryRun.properties");
if (Files.exists(path)) {
Properties properties = new Properties();
try (FileInputStream fis = new FileInputStream(path.toFile())) {

View File

@@ -22,7 +22,6 @@ import static org.keycloak.quarkus.runtime.configuration.MicroProfileConfigProvi
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
@@ -44,20 +43,6 @@ public final class QuarkusPropertiesConfigSource extends AbstractLocationConfigS
private static final String FILE_NAME = "quarkus.properties";
public static final String NAME = "KcQuarkusPropertiesConfigSource";
public static Path getConfigurationFile() {
String homeDir = Environment.getHomeDir();
if (homeDir != null) {
File file = Paths.get(homeDir, "conf", FILE_NAME).toFile();
if (file.exists()) {
return file.toPath();
}
}
return null;
}
@Override
protected String[] getFileExtensions() {
return new String[] { "properties" };
@@ -74,11 +59,9 @@ public final class QuarkusPropertiesConfigSource extends AbstractLocationConfigS
public synchronized List<ConfigSource> getConfigSources(final ClassLoader classLoader) {
List<ConfigSource> configSources = new ArrayList<>();
Path configFile = getConfigurationFile();
if (configFile != null) {
configSources.addAll(loadConfigSources(configFile.toUri().toString(), KeycloakPropertiesConfigSource.PROPERTIES_FILE_ORDINAL, classLoader));
}
Environment.getHomeDir().map(p -> Paths.get(p, "conf", FILE_NAME).toFile()).filter(File::exists)
.ifPresent(configFile -> configSources.addAll(loadConfigSources(configFile.toPath().toUri().toString(),
KeycloakPropertiesConfigSource.PROPERTIES_FILE_ORDINAL, classLoader)));
return configSources;
}

View File

@@ -69,7 +69,7 @@ final class CachingPropertyMappers implements PropertyMapperGrouping {
.to("kc.spi-jgroups-mtls--default--enabled")
.isEnabled(CachingPropertyMappers::getDefaultMtlsEnabled, "a TCP based cache-stack is used")
.build(),
fromOption(CachingOptions.CACHE_EMBEDDED_MTLS_KEYSTORE.withRuntimeSpecificDefault(getDefaultKeystorePathValue()))
fromOption(CachingOptions.CACHE_EMBEDDED_MTLS_KEYSTORE.withRuntimeSpecificDefault(getConfPathValue("cache-mtls-keystore.p12")))
.paramLabel("file")
.to("kc.spi-jgroups-mtls--default--keystore-file")
.isEnabled(() -> Configuration.isTrue(CachingOptions.CACHE_EMBEDDED_MTLS_ENABLED), "property '%s' is enabled".formatted(CachingOptions.CACHE_EMBEDDED_MTLS_ENABLED.getKey()))
@@ -82,7 +82,7 @@ final class CachingPropertyMappers implements PropertyMapperGrouping {
.isEnabled(() -> Configuration.isTrue(CachingOptions.CACHE_EMBEDDED_MTLS_ENABLED), "property '%s' is enabled".formatted(CachingOptions.CACHE_EMBEDDED_MTLS_ENABLED.getKey()))
.validator(value -> checkOptionPresent(CachingOptions.CACHE_EMBEDDED_MTLS_KEYSTORE_PASSWORD, CachingOptions.CACHE_EMBEDDED_MTLS_KEYSTORE))
.build(),
fromOption(CachingOptions.CACHE_EMBEDDED_MTLS_TRUSTSTORE.withRuntimeSpecificDefault(getDefaultTruststorePathValue()))
fromOption(CachingOptions.CACHE_EMBEDDED_MTLS_TRUSTSTORE.withRuntimeSpecificDefault(getConfPathValue("cache-mtls-truststore.p12")))
.paramLabel("file")
.to("kc.spi-jgroups-mtls--default--truststore-file")
.isEnabled(() -> Configuration.isTrue(CachingOptions.CACHE_EMBEDDED_MTLS_ENABLED), "property '%s' is enabled".formatted(CachingOptions.CACHE_EMBEDDED_MTLS_ENABLED.getKey()))
@@ -204,39 +204,12 @@ final class CachingPropertyMappers implements PropertyMapperGrouping {
}
private static String resolveConfigFile(String value, ConfigSourceInterceptorContext context) {
String homeDir = Environment.getHomeDir();
return homeDir == null ?
value :
homeDir + (homeDir.endsWith(File.separator) ? "" : File.separator) + "conf" + File.separator + value;
return Environment.getHomeDir().map(f -> Paths.get(f, "conf", value).toString()).orElse(null);
}
private static String getDefaultKeystorePathValue() {
String homeDir = Environment.getHomeDir();
if (homeDir != null) {
File file = Paths.get(homeDir, "conf", "cache-mtls-keystore.p12").toFile();
if (file.exists()) {
return file.getAbsolutePath();
}
}
return null;
}
private static String getDefaultTruststorePathValue() {
String homeDir = Environment.getHomeDir();
if (homeDir != null) {
File file = Paths.get(homeDir, "conf", "cache-mtls-truststore.p12").toFile();
if (file.exists()) {
return file.getAbsolutePath();
}
}
return null;
private static String getConfPathValue(String file) {
return Environment.getHomeDir().map(f -> Paths.get(f, "conf", file).toFile()).filter(File::exists)
.map(File::getAbsolutePath).orElse(null);
}
private static PropertyMapper<?> maxCountOpt(String cacheName, BooleanSupplier isEnabled, String enabledWhen) {

View File

@@ -202,17 +202,8 @@ public final class HttpPropertyMappers implements PropertyMapperGrouping {
}
private static File getDefaultKeystorePathValue() {
String homeDir = Environment.getHomeDir();
if (homeDir != null) {
File file = Paths.get(homeDir, "conf", "server.keystore").toFile();
if (file.exists()) {
return file;
}
}
return null;
return Environment.getHomeDir().map(f -> Paths.get(f, "conf", "server.keystore").toFile()).filter(File::exists)
.orElse(null);
}
private static String resolveKeyStoreType(String value,

View File

@@ -19,7 +19,6 @@ package org.keycloak.quarkus.runtime.integration;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.concurrent.atomic.AtomicBoolean;
@@ -60,34 +59,23 @@ public class QuarkusPlatform implements PlatformProvider {
@Override
public File getTmpDirectory() {
if (tmpDir == null) {
String homeDir = Environment.getHomeDir();
String dataDir = Environment.getDataDir().orElse(null);
File tmpDir;
if (homeDir == null) {
// Should happen just in the unit tests
if (dataDir == null) {
// Should happen just in non-script launch scenarios
try {
// Use "tmp" directory in case it points to the "target" directory (which is usually the case with quarkus unit tests)
// Trying to use "target" subdirectory to avoid the situation when separate subdirectory will be created in the "/tmp" for each build and hence "/tmp" directory being swamped with many subdirectories
String tmpDirProp = System.getProperty("java.io.tmpdir");
if (tmpDirProp == null || !tmpDirProp.endsWith("target")) {
// Fallback to "target" inside "user.dir"
String userDirProp = System.getProperty("user.dir");
if (userDirProp != null) {
File userDir = new File(userDirProp, "target");
if (userDir.exists()) {
tmpDirProp = userDir.getAbsolutePath();
}
}
tmpDir = Path.of(System.getProperty("java.io.tmpdir"), "keycloak-quarkus-tmp").toFile();
if (tmpDir.exists()) {
org.apache.commons.io.FileUtils.deleteDirectory(tmpDir);
}
if (tmpDir.mkdirs()) {
tmpDir.deleteOnExit();
}
// Finally fallback to system tmp directory. Always create dedicated directory for current user
Path path = tmpDirProp != null ? Files.createTempDirectory(new File(tmpDirProp).toPath(), "keycloak-quarkus-tmp") :
Files.createTempDirectory("keycloak-quarkus-tmp");
tmpDir = path.toFile();
} catch (IOException ioex) {
throw new RuntimeException("It was not possible to create temporary directory keycloak-quarkus-tmp", ioex);
}
} else {
String dataDir = Environment.getDataDir();
tmpDir = new File(dataDir, "tmp");
tmpDir.mkdirs();
}

View File

@@ -33,6 +33,6 @@ public class QuarkusBlacklistPasswordPolicyProviderFactory extends BlacklistPass
@Override
public String getDefaultBlacklistsBasePath() {
return Environment.getDataDir() + File.separator + PASSWORD_BLACKLISTS_FOLDER;
return Environment.getDataDir().map(d -> d + File.separator + PASSWORD_BLACKLISTS_FOLDER).orElse(null);
}
}

View File

@@ -9,7 +9,7 @@ import org.keycloak.theme.ThemeProvider;
import org.keycloak.theme.ThemeProviderFactory;
import java.io.File;
import java.util.Objects;
import java.util.Optional;
public class QuarkusFolderThemeProviderFactory implements ThemeProviderFactory {
@@ -53,14 +53,7 @@ public class QuarkusFolderThemeProviderFactory implements ThemeProviderFactory {
* @throws RuntimeException when filesystem path is not accessible
*/
private File getThemeRootDirWithFallback(String rootDirFromConfig) {
File themeRootDir;
themeRootDir = new File(Objects.requireNonNullElseGet(rootDirFromConfig, Environment::getDefaultThemeRootDir));
if (!themeRootDir.exists()) {
return null;
}
return themeRootDir;
return Optional.ofNullable(rootDirFromConfig).or(Environment::getDefaultThemeRootDir).map(File::new)
.filter(File::exists).orElse(null);
}
}

View File

@@ -30,7 +30,6 @@ import java.util.Properties;
import java.util.function.Function;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.keycloak.Config;
import org.keycloak.common.Profile;
@@ -90,11 +89,6 @@ public abstract class AbstractConfigurationTest {
resetConfiguration();
}
@AfterClass
public static void afterAll() {
Environment.removeHomeDir();
}
protected static Config.Scope initConfig(String... scope) {
Config.init(new MicroProfileConfigProvider(createConfig()));
return Config.scope(scope);

View File

@@ -270,7 +270,7 @@ public class ConfigurationTest extends AbstractConfigurationTest {
assertEquals(H2Dialect.class.getName(), config.getConfigValue("kc.db-dialect").getValue());
assertEquals(Driver.class.getName(), config.getConfigValue("quarkus.datasource.jdbc.driver").getValue());
assertEquals("jdbc:h2:file:" + Environment.getHomeDir() + "/data/h2/keycloakdb;NON_KEYWORDS=VALUE;DB_CLOSE_ON_EXIT=FALSE;DB_CLOSE_DELAY=0", config.getConfigValue("quarkus.datasource.jdbc.url").getValue());
assertEquals("jdbc:h2:file:" + Environment.getHomeDir().orElseThrow() + "/data/h2/keycloakdb;NON_KEYWORDS=VALUE;DB_CLOSE_ON_EXIT=FALSE;DB_CLOSE_DELAY=0", config.getConfigValue("quarkus.datasource.jdbc.url").getValue());
ConfigArgsConfigSource.setCliArgs("--db=dev-mem");
config = createConfig();
@@ -403,7 +403,7 @@ public class ConfigurationTest extends AbstractConfigurationTest {
@Test
public void testNestedDatabaseProperties() {
SmallRyeConfig config = createConfig();
assertEquals("jdbc:h2:file:"+Environment.getHomeDir()+"/data/keycloakdb", config.getConfigValue("quarkus.datasource.foo").getValue());
assertEquals("jdbc:h2:file:"+Environment.getHomeDir().orElseThrow()+"/data/keycloakdb", config.getConfigValue("quarkus.datasource.foo").getValue());
Assert.assertEquals("foo-def-suffix", config.getConfigValue("quarkus.datasource.bar").getValue());
@@ -423,7 +423,7 @@ public class ConfigurationTest extends AbstractConfigurationTest {
@Test
public void testClusterConfig() {
// Cluster enabled by default, but disabled for the "dev" profile
String conf = Environment.getHomeDir() + File.separator + "conf" + File.separator;
String conf = Environment.getHomeDir().orElseThrow() + File.separator + "conf" + File.separator;
Assert.assertEquals(conf + "cache-ispn.xml", cacheEmbeddedConfiguration().get(DefaultCacheEmbeddedConfigProviderFactory.CONFIG));
// If explicitly set, then it is always used regardless of the profile

View File

@@ -188,7 +188,7 @@ public class DatasourcesConfigurationTest extends AbstractConfigurationTest {
ConfigArgsConfigSource.setCliArgs("--db-kind-asdf=dev-file", "--db-url-properties-asdf=;DB_CLOSE_ON_EXIT=true");
initConfig();
assertExternalConfig(Map.of(
"quarkus.datasource.\"asdf\".jdbc.url", "jdbc:h2:file:" + Environment.getHomeDir() + "/data/h2-asdf/keycloakdb-asdf;DB_CLOSE_ON_EXIT=true;NON_KEYWORDS=VALUE;DB_CLOSE_DELAY=0",
"quarkus.datasource.\"asdf\".jdbc.url", "jdbc:h2:file:" + Environment.getHomeDir().orElseThrow() + "/data/h2-asdf/keycloakdb-asdf;DB_CLOSE_ON_EXIT=true;NON_KEYWORDS=VALUE;DB_CLOSE_DELAY=0",
"quarkus.datasource.\"asdf\".db-kind", "h2"
));
onAfter();
@@ -283,7 +283,7 @@ public class DatasourcesConfigurationTest extends AbstractConfigurationTest {
@Test
public void nestedDatasourceProperties() {
initConfig();
assertExternalConfig("quarkus.datasource.foo", "jdbc:h2:file:" + Environment.getHomeDir() + "/data/keycloakdb");
assertExternalConfig("quarkus.datasource.foo", "jdbc:h2:file:" + Environment.getHomeDir().orElseThrow() + "/data/keycloakdb");
assertExternalConfig("quarkus.datasource.bar", "foo-def-suffix");
System.setProperty("kc.prop5", "val5");