diff --git a/distribution/server-x-dist/assembly.xml b/distribution/server-x-dist/assembly.xml index 2c47edc2b0..fcf9596486 100755 --- a/distribution/server-x-dist/assembly.xml +++ b/distribution/server-x-dist/assembly.xml @@ -104,6 +104,10 @@ target/keycloak-quarkus-server/cluster-default.xml conf + + target/keycloak-quarkus-server-app/META-INF/keycloak.properties + conf + diff --git a/distribution/server-x-dist/pom.xml b/distribution/server-x-dist/pom.xml index df4c4c72c1..9947610236 100755 --- a/distribution/server-x-dist/pom.xml +++ b/distribution/server-x-dist/pom.xml @@ -70,6 +70,12 @@ jar target/keycloak-quarkus-server + + org.keycloak + keycloak-quarkus-server-app + jar + target/keycloak-quarkus-server-app + diff --git a/distribution/server-x-dist/src/main/content/conf/README.md b/distribution/server-x-dist/src/main/content/conf/README.md new file mode 100644 index 0000000000..2075398ff2 --- /dev/null +++ b/distribution/server-x-dist/src/main/content/conf/README.md @@ -0,0 +1,3 @@ +# Configure the server + +Use files in this directory to configure the server \ No newline at end of file diff --git a/distribution/server-x-dist/src/main/content/conf/keycloak.properties b/distribution/server-x-dist/src/main/content/conf/keycloak.properties deleted file mode 100644 index d55cc50ec8..0000000000 --- a/distribution/server-x-dist/src/main/content/conf/keycloak.properties +++ /dev/null @@ -1,12 +0,0 @@ -# Default and non-production grade database vendor -db=h2-file - -# Default, and insecure, and non-production grade configuration for the development profile -%dev.http.enabled=true -%dev.db.username = sa -%dev.db.password = keycloak - -# Logging configuration. INFO is the default level for most of the categories -#quarkus.log.level = DEBUG -quarkus.log.category."org.jboss.resteasy.resteasy_jaxrs.i18n".level=WARN -quarkus.log.category."org.infinispan.transaction.lookup.JBossStandaloneJTAManagerLookup".level=WARN diff --git a/quarkus/deployment/src/main/java/org/keycloak/quarkus/deployment/KeycloakProcessor.java b/quarkus/deployment/src/main/java/org/keycloak/quarkus/deployment/KeycloakProcessor.java index 30422a89cb..57eb2b8b1a 100644 --- a/quarkus/deployment/src/main/java/org/keycloak/quarkus/deployment/KeycloakProcessor.java +++ b/quarkus/deployment/src/main/java/org/keycloak/quarkus/deployment/KeycloakProcessor.java @@ -102,7 +102,7 @@ class KeycloakProcessor { * * @param recorder */ - @Record(ExecutionTime.STATIC_INIT) + @Record(ExecutionTime.RUNTIME_INIT) @BuildStep void configureProviders(KeycloakRecorder recorder) { Profile.setInstance(recorder.createProfile()); diff --git a/quarkus/runtime/src/main/java/org/keycloak/configuration/Database.java b/quarkus/runtime/src/main/java/org/keycloak/configuration/Database.java new file mode 100644 index 0000000000..7d08979d70 --- /dev/null +++ b/quarkus/runtime/src/main/java/org/keycloak/configuration/Database.java @@ -0,0 +1,123 @@ +/* + * Copyright 2020 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. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.keycloak.configuration; + +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import java.util.function.Function; + +class Database { + + private static Map DATABASES = new HashMap<>(); + + static { + for (Vendor vendor : Vendor.values()) { + DATABASES.put(vendor.name().toLowerCase(), vendor); + + for (String alias : vendor.aliases) { + DATABASES.put(alias, vendor); + } + } + } + + static boolean isSupported(String alias) { + return DATABASES.containsKey(alias); + } + + static Optional getDefaultUrl(String alias) { + Vendor vendor = DATABASES.get(alias); + + if (vendor == null) { + return Optional.empty(); + } + + return Optional.of(vendor.defaultUrl.apply(alias)); + } + + static Optional getDriver(String alias) { + Vendor vendor = DATABASES.get(alias); + + if (vendor == null) { + return Optional.empty(); + } + + return Optional.of(vendor.driver); + } + + static Optional getDialect(String alias) { + Vendor vendor = DATABASES.get(alias); + + if (vendor == null) { + return Optional.empty(); + } + + return Optional.of(vendor.dialect.apply(alias)); + } + + private enum Vendor { + H2("org.h2.jdbcx.JdbcDataSource", "io.quarkus.hibernate.orm.runtime.dialect.QuarkusH2Dialect", + new Function() { + @Override + public String apply(String alias) { + if ("h2-file".equalsIgnoreCase(alias)) { + return "jdbc:h2:file:${kc.home.dir:${kc.db.url.path:~}}/${kc.data.dir:data}/keycloakdb${kc.db.url.properties:;;AUTO_SERVER=TRUE}"; + } + return "jdbc:h2:mem:keycloakdb${kc.db.url.properties:}"; + } + }, "h2-mem", "h2-file"), + MYSQL("com.mysql.cj.jdbc.MysqlXADataSource", "org.hibernate.dialect.MySQL8Dialect", + "jdbc:mysql://${kc.db.url.host:localhost}/${kc.db.url.database:keycloak}${kc.db.url.properties:}"), + MARIADB("org.mariadb.jdbc.MySQLDataSource", "org.hibernate.dialect.MariaDBDialect", + "jdbc:mariadb://${kc.db.url.host:localhost}/${kc.db.url.database:keycloak}${kc.db.url.properties:}"), + POSTGRES("org.postgresql.xa.PGXADataSource", new Function() { + @Override + public String apply(String alias) { + if ("postgres-95".equalsIgnoreCase(alias)) { + return "io.quarkus.hibernate.orm.runtime.dialect.QuarkusPostgreSQL95Dialect"; + } + return "io.quarkus.hibernate.orm.runtime.dialect.QuarkusPostgreSQL10Dialect"; + } + }, "jdbc:postgresql://${kc.db.url.host:localhost}/${kc.db.url.database:keycloak}${kc.db.url.properties:}", + "postgres-95", "postgres-10"); + + final String driver; + final Function dialect; + final Function defaultUrl; + final String[] aliases; + + Vendor(String driver, String dialect, String defaultUrl, String... aliases) { + this(driver, (alias) -> dialect, (alias) -> defaultUrl, aliases); + } + + Vendor(String driver, String dialect, Function defaultUrl, String... aliases) { + this(driver, (alias) -> dialect, defaultUrl, aliases); + } + + Vendor(String driver, Function dialect, String defaultUrl, String... aliases) { + this(driver, dialect, (alias) -> defaultUrl, aliases); + } + + Vendor(String driver, Function dialect, Function defaultUrl, String... aliases) { + this.driver = driver; + this.dialect = dialect; + this.defaultUrl = defaultUrl; + this.aliases = aliases; + } + } +} diff --git a/quarkus/runtime/src/main/java/org/keycloak/configuration/KeycloakPropertiesConfigSource.java b/quarkus/runtime/src/main/java/org/keycloak/configuration/KeycloakPropertiesConfigSource.java index f516ac4e6c..f730f39397 100644 --- a/quarkus/runtime/src/main/java/org/keycloak/configuration/KeycloakPropertiesConfigSource.java +++ b/quarkus/runtime/src/main/java/org/keycloak/configuration/KeycloakPropertiesConfigSource.java @@ -17,15 +17,12 @@ package org.keycloak.configuration; -import java.io.BufferedReader; import java.io.Closeable; import java.io.File; import java.io.FileNotFoundException; import java.io.IOError; import java.io.IOException; import java.io.InputStream; -import java.io.InputStreamReader; -import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.NoSuchFileException; import java.nio.file.Path; @@ -62,13 +59,9 @@ public abstract class KeycloakPropertiesConfigSource extends PropertiesConfigSou return Collections.emptyMap(); } try (Closeable ignored = is) { - try (InputStreamReader isr = new InputStreamReader(is, StandardCharsets.UTF_8)) { - try (BufferedReader br = new BufferedReader(isr)) { - final Properties properties = new Properties(); - properties.load(br); - return transform((Map) (Map) properties); - } - } + Properties properties = new Properties(); + properties.load(is); + return transform((Map) (Map) properties); } catch (IOException e) { throw new IOError(e); } diff --git a/quarkus/runtime/src/main/java/org/keycloak/configuration/PropertyMappers.java b/quarkus/runtime/src/main/java/org/keycloak/configuration/PropertyMappers.java index 5b608d076a..a5b790766f 100644 --- a/quarkus/runtime/src/main/java/org/keycloak/configuration/PropertyMappers.java +++ b/quarkus/runtime/src/main/java/org/keycloak/configuration/PropertyMappers.java @@ -53,8 +53,7 @@ public final class PropertyMappers { Boolean enabled = Boolean.valueOf(value); ConfigValue proxy = context.proceed(MicroProfileConfigProvider.NS_KEYCLOAK_PREFIX + "proxy"); - if ("dev".equalsIgnoreCase(ProfileManager.getActiveProfile()) || - (proxy != null && "edge".equalsIgnoreCase(proxy.getValue()))) { + if (Environment.isDevMode() || (proxy != null && "edge".equalsIgnoreCase(proxy.getValue()))) { enabled = true; } @@ -119,74 +118,17 @@ public final class PropertyMappers { } private static void configureDatabasePropertyMappers() { - createBuildTimeProperty("db", "quarkus.hibernate-orm.dialect", (db, context) -> { - switch (db.toLowerCase()) { - case "h2-file": - case "h2-mem": - return "io.quarkus.hibernate.orm.runtime.dialect.QuarkusH2Dialect"; - case "mariadb": - return "org.hibernate.dialect.MariaDBDialect"; - case "mysql": - return "org.hibernate.dialect.MySQL8Dialect"; - case "postgres-95": - return "io.quarkus.hibernate.orm.runtime.dialect.QuarkusPostgreSQL95Dialect"; - case "postgres": // shorthand for the recommended postgres version - case "postgres-10": - return "io.quarkus.hibernate.orm.runtime.dialect.QuarkusPostgreSQL10Dialect"; - } - return null; - }, null); - create("db", "quarkus.datasource.jdbc.driver", (db, context) -> { - switch (db.toLowerCase()) { - case "h2-file": - case "h2-mem": - return "org.h2.jdbcx.JdbcDataSource"; - case "mariadb": - return "org.mariadb.jdbc.MySQLDataSource"; - case "mysql": - return "com.mysql.cj.jdbc.MysqlXADataSource"; - case "postgres": - case "postgres-95": - case "postgres-10": - return "org.postgresql.xa.PGXADataSource"; - } - return null; - }, null); - create("db", "quarkus.datasource.db-kind", (db, context) -> { - switch (db.toLowerCase()) { - case "h2-file": - case "h2-mem": - return "h2"; - case "mariadb": - return "mariadb"; - case "mysql": - return "mysql"; - case "postgres": - case "postgres-95": - case "postgres-10": - return "postgresql"; + createBuildTimeProperty("db", "quarkus.hibernate-orm.dialect", (db, context) -> Database.getDialect(db).orElse(null), null); + create("db", "quarkus.datasource.jdbc.driver", (db, context) -> Database.getDriver(db).orElse(null), null); + createBuildTimeProperty("db", "quarkus.datasource.db-kind", (db, context) -> { + if (Database.isSupported(db)) { + return db; } addInitializationException(invalidDatabaseVendor(db, "h2-file", "h2-mem", "mariadb", "mysql", "postgres", "postgres-95", "postgres-10")); return "h2"; }, "The database vendor. Possible values are: h2-mem, h2-file, mariadb, mysql, postgres95, postgres10."); create("db", "quarkus.datasource.jdbc.transactions", (db, context) -> "xa", null); - create("db.url", "db", "quarkus.datasource.jdbc.url", (value, context) -> { - switch (value.toLowerCase()) { - case "h2-file": - return "jdbc:h2:file:${kc.home.dir:${kc.db.url.path:~}}/${kc.data.dir:data}/keycloakdb${kc.db.url.properties:;;AUTO_SERVER=TRUE}"; - case "h2-mem": - return "jdbc:h2:mem:keycloakdb${kc.db.url.properties:}"; - case "mariadb": - return "jdbc:mariadb://${kc.db.url.host:localhost}/${kc.db.url.database:keycloak}${kc.db.url.properties:}"; - case "postgres": - case "postgres-95": - case "postgres-10": - return "jdbc:postgresql://${kc.db.url.host:localhost}/${kc.db.url.database:keycloak}${kc.db.url.properties:}"; - case "mysql": - return "jdbc:mysql://${kc.db.url.host:localhost}/${kc.db.url.database:keycloak}${kc.db.url.properties:}"; - } - return value; - }, "The database JDBC URL. If not provided a default URL is set based on the selected database vendor. For instance, if using 'postgres', the JDBC URL would be 'jdbc:postgresql://localhost/keycloak'. The host, database and properties can be overridden by setting the following system properties, respectively: -Dkc.db.url.host, -Dkc.db.url.database, -Dkc.db.url.properties."); + create("db.url", "db", "quarkus.datasource.jdbc.url", (value, context) -> Database.getDefaultUrl(value).orElse(value), "The database JDBC URL. If not provided a default URL is set based on the selected database vendor. For instance, if using 'postgres', the JDBC URL would be 'jdbc:postgresql://localhost/keycloak'. The host, database and properties can be overridden by setting the following system properties, respectively: -Dkc.db.url.host, -Dkc.db.url.database, -Dkc.db.url.properties."); create("db.username", "quarkus.datasource.username", "The database username."); create("db.password", "quarkus.datasource.password", "The database password", true); create("db.schema", "quarkus.datasource.schema", "The database schema."); @@ -212,11 +154,6 @@ public final class PropertyMappers { .anyMatch(entry -> entry.getValue().getFrom().equals(name) && entry.getValue().isBuildTime()); } - public static boolean isSupported(String name) { - return PropertyMapper.MAPPERS.entrySet().stream() - .anyMatch(entry -> toCLIFormat(entry.getValue().getFrom()).equals(name)); - } - public static String toCLIFormat(String name) { if (name.indexOf('.') == -1) { return name; diff --git a/quarkus/runtime/src/main/java/org/keycloak/provider/quarkus/QuarkusCacheManagerProvider.java b/quarkus/runtime/src/main/java/org/keycloak/provider/quarkus/QuarkusCacheManagerProvider.java index 12b41d3d25..d9c1fe3f9a 100644 --- a/quarkus/runtime/src/main/java/org/keycloak/provider/quarkus/QuarkusCacheManagerProvider.java +++ b/quarkus/runtime/src/main/java/org/keycloak/provider/quarkus/QuarkusCacheManagerProvider.java @@ -17,14 +17,9 @@ package org.keycloak.provider.quarkus; -import java.io.BufferedReader; -import java.io.FileNotFoundException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.nio.charset.StandardCharsets; +import java.net.URL; import java.nio.file.Path; import java.nio.file.Paths; -import java.util.stream.Collectors; import org.infinispan.commons.util.FileLookupFactory; import org.infinispan.configuration.parsing.ConfigurationBuilderHolder; @@ -45,8 +40,7 @@ public final class QuarkusCacheManagerProvider implements ManagedCacheManagerPro @Override public C getCacheManager(Config.Scope config) { try { - String configurationAsString = loadConfigurationToString(config); - ConfigurationBuilderHolder builder = new ParserRegistry().parse(configurationAsString); + ConfigurationBuilderHolder builder = new ParserRegistry().parse(loadConfiguration(config)); if (builder.getNamedConfigurationBuilders().get("sessions").clustering().cacheMode().isClustered()) { configureTransportStack(config, builder); @@ -58,17 +52,12 @@ public final class QuarkusCacheManagerProvider implements ManagedCacheManagerPro } } - private String loadConfigurationToString(Config.Scope config) throws FileNotFoundException { - BufferedReader configurationReader = new BufferedReader(new InputStreamReader(loadConfiguration(config), StandardCharsets.UTF_8)); - return configurationReader.lines().collect(Collectors.joining(System.lineSeparator())); - } - - private InputStream loadConfiguration(Config.Scope config) throws FileNotFoundException { + private URL loadConfiguration(Config.Scope config) { String pathPrefix; String homeDir = Environment.getHomeDir(); if (homeDir == null) { - log.warn("Keycloak home directory not set."); + log.warn("Keycloak home directory not set"); pathPrefix = ""; } else { pathPrefix = homeDir + "/conf/"; @@ -78,18 +67,24 @@ public final class QuarkusCacheManagerProvider implements ManagedCacheManagerPro String configFile = config.get("configFile"); if (configFile != null) { Path configPath = Paths.get(pathPrefix + configFile); + String path; if (configPath.toFile().exists()) { - log.infof("Loading cache configuration from %s", configPath); - return FileLookupFactory.newInstance() - .lookupFileStrict(configPath.toUri(), Thread.currentThread().getContextClassLoader()); + path = configPath.toFile().getAbsolutePath(); } else { - log.infof("Loading cache configuration from %s", configPath); - return FileLookupFactory.newInstance() - .lookupFileStrict(configPath.getFileName().toString(), Thread.currentThread().getContextClassLoader()); + path = configPath.getFileName().toString(); } + + log.infof("Loading cluster configuration from %s", configPath); + URL url = FileLookupFactory.newInstance().lookupFileLocation(path, Thread.currentThread().getContextClassLoader()); + + if (url == null) { + throw new IllegalArgumentException("Could not load cluster configuration file at [" + configPath + "]"); + } + + return url; } else { - throw new IllegalStateException("Option 'configFile' needs to be specified"); + throw new IllegalArgumentException("Option 'configFile' needs to be specified"); } } diff --git a/quarkus/runtime/src/main/java/org/keycloak/util/Environment.java b/quarkus/runtime/src/main/java/org/keycloak/util/Environment.java index a4dcf84aac..0b2912f569 100644 --- a/quarkus/runtime/src/main/java/org/keycloak/util/Environment.java +++ b/quarkus/runtime/src/main/java/org/keycloak/util/Environment.java @@ -79,4 +79,8 @@ public final class Environment { public static SmallRyeConfig getConfig() { return KeycloakRecorder.getConfig(); } + + public static boolean isDevMode() { + return "dev".equalsIgnoreCase(getProfile()); + } } diff --git a/quarkus/server/src/main/resources/META-INF/keycloak.properties b/quarkus/server/src/main/resources/META-INF/keycloak.properties index 93c0d102d5..1e9124c065 100644 --- a/quarkus/server/src/main/resources/META-INF/keycloak.properties +++ b/quarkus/server/src/main/resources/META-INF/keycloak.properties @@ -5,4 +5,9 @@ db=h2-file %dev.http.enabled=true %dev.db.username = sa %dev.db.password = keycloak -%dev.cluster=local \ No newline at end of file +%dev.cluster=local + +# Logging configuration. INFO is the default level for most of the categories +#quarkus.log.level = DEBUG +quarkus.log.category."org.jboss.resteasy.resteasy_jaxrs.i18n".level=WARN +quarkus.log.category."org.infinispan.transaction.lookup.JBossStandaloneJTAManagerLookup".level=WARN \ No newline at end of file