diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b8132bc1ff..b371a7fc75 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,6 +2,9 @@ name: Keycloak CI on: [push, pull_request] +env: + DEFAULT_JDK_VERSION: 11 + jobs: build: name: Build @@ -10,7 +13,7 @@ jobs: - uses: actions/checkout@v2 - uses: actions/setup-java@v1 with: - java-version: 1.8 + java-version: ${{ env.DEFAULT_JDK_VERSION }} - name: Update maven settings run: mkdir -p ~/.m2 ; cp .github/settings.xml ~/.m2/ - name: Cache Maven packages @@ -24,7 +27,7 @@ jobs: - name: Build Keycloak run: | - mvn clean install -nsu -B -e -DskipTests -Pquarkus,distribution + mvn clean install -nsu -B -e -DskipTests -Pdistribution mvn clean install -nsu -B -e -f testsuite/integration-arquillian/servers/auth-server -Pauth-server-quarkus mvn clean install -nsu -B -e -f testsuite/integration-arquillian/servers/auth-server -Pauth-server-wildfly mvn clean install -nsu -B -e -f testsuite/integration-arquillian/servers/auth-server -Pauth-server-undertow @@ -53,7 +56,7 @@ jobs: - uses: actions/checkout@v2 - uses: actions/setup-java@v1 with: - java-version: 1.8 + java-version: ${{ env.DEFAULT_JDK_VERSION }} - name: Update maven settings run: mkdir -p ~/.m2 ; cp .github/settings.xml ~/.m2/ - name: Cache Maven packages @@ -94,7 +97,7 @@ jobs: - uses: actions/checkout@v2 - uses: actions/setup-java@v1 with: - java-version: 1.8 + java-version: ${{ env.DEFAULT_JDK_VERSION }} - name: Update maven settings run: mkdir -p ~/.m2 ; cp .github/settings.xml ~/.m2/ - name: Cache Maven packages @@ -160,7 +163,7 @@ jobs: - uses: actions/setup-java@v1 with: - java-version: 1.8 + java-version: ${{ env.DEFAULT_JDK_VERSION }} - name: Update maven settings run: mkdir -p ~/.m2 ; cp .github/settings.xml ~/.m2/ @@ -224,7 +227,7 @@ jobs: - uses: actions/setup-java@v1 if: ${{ github.event_name != 'pull_request' || env.GIT_DIFF != 0 }} with: - java-version: 1.8 + java-version: ${{ env.DEFAULT_JDK_VERSION }} - name: Update maven settings if: ${{ github.event_name != 'pull_request' || env.GIT_DIFF != 0 }} run: mkdir -p ~/.m2 ; cp .github/settings.xml ~/.m2/ @@ -263,7 +266,7 @@ jobs: - uses: actions/setup-java@v1 if: ${{ github.event_name != 'pull_request' || env.GIT_DIFF != 0 }} with: - java-version: 1.8 + java-version: ${{ env.DEFAULT_JDK_VERSION }} - name: Update maven settings if: ${{ github.event_name != 'pull_request' || env.GIT_DIFF != 0 }} run: mkdir -p ~/.m2 ; cp .github/settings.xml ~/.m2/ @@ -336,7 +339,7 @@ jobs: name: keycloak-artifacts.zip - uses: actions/setup-java@v1 with: - java-version: 1.8 + java-version: ${{ env.DEFAULT_JDK_VERSION }} - name: Update maven settings run: mkdir -p ~/.m2 ; cp .github/settings.xml ~/.m2/ - name: Run Quarkus cluster tests @@ -374,7 +377,7 @@ jobs: - uses: actions/setup-java@v1 with: - java-version: 1.8 + java-version: 8 - name: Update maven settings run: mkdir -p ~/.m2 ; cp .github/settings.xml ~/.m2/ - name: Cache Maven packages diff --git a/distribution/pom.xml b/distribution/pom.xml index 7eaffa02c2..617bda6534 100755 --- a/distribution/pom.xml +++ b/distribution/pom.xml @@ -78,8 +78,9 @@ - community + quarkus + [11,) !product diff --git a/docs/building.md b/docs/building.md index 9565a39800..1a6e34ff5a 100644 --- a/docs/building.md +++ b/docs/building.md @@ -51,6 +51,10 @@ Classes from `org.keycloak.testsuite.*` packages aren't suitable to be used in p --- +### Building Quarkus Distribution + +Please, take a look at this [documentation](../quarkus/README.md). + ## Starting Keycloak To start Keycloak during development first build as specified above, then run: diff --git a/pom.xml b/pom.xml index 7ed85bbc92..90e3a46db5 100644 --- a/pom.xml +++ b/pom.xml @@ -1897,6 +1897,16 @@ ${wildfly.version} ${project.version} + + + + quarkus + + [11,) + + !product + + quarkus diff --git a/quarkus/README.md b/quarkus/README.md index ed97cda755..d9b32bf3bf 100644 --- a/quarkus/README.md +++ b/quarkus/README.md @@ -13,12 +13,22 @@ The module holds the codebase to run Keycloak on top of [Quarkus](https://quarku ├── The server itself, only responsible for generating the server artifacts ``` +## Activating the Module + +When build from the project root directory, this module is only enabled if the installed JDK is 11 or newer. + ## Building +Ensure you have JDK 11 (or newer) installed. + To build the module and produce the artifacts to run a server: mvn -f ../pom.xml clean install -DskipTestsuite -DskipExamples -DskipTests +If you already built Keycloak artifacts using JDK 8, you should be able to skip the previous step and just build this module as follows: + + mvn clean install + ### Building the Distribution To build the module as well as the distribution packages: @@ -27,12 +37,16 @@ To build the module as well as the distribution packages: The distribution packages (ZIP and TAR) should be available at [../distribution/server-x-dist](../distribution/server-x-dist/target). +Alternatively, you can also build the distribution directly by running the following command: + + mvn -f ../distribution/server-x-dist/pom.xml clean install + ## Running By default, the HTTP port is disabled and you need to provide the key material to configure HTTPS. If you want to enable the HTTP port, run the server in development mode as follows: - java -jar server/target/lib/quarkus-run.jar --profile=dev + java -jar server/target/lib/quarkus-run.jar start-dev ## Contributing @@ -40,17 +54,19 @@ the HTTP port, run the server in development mode as follows: To run the server in development mode: - cd server - mvn compile quarkus:dev - + mvn -f server/pom.xml compile quarkus:dev + You should be able to attach your debugger to port `5005`. -Changes to files such as `server/src/main/resources` or `server/src/main/resources/META-INF/keycloak.properties` should -be recognized automatically when running in development mode. +For debugging the build steps, you can suspend the JVM by running: -However, considering that there is no real code in the `server` module (but from `runtime` and its dependencies), changes you make to -dependencies (e.g: services, model, etc) won't be reflected into the running server. However, you can still leverage the -hot reload capabilities from your IDE to make changes at runtime. + mvn -f server/pom.xml -Dsuspend=true compile quarkus:dev -NOTE: We need to improve DevX and figure out why changes to dependencies are not being recognized when running tests or running -Quarkus Dev Mode. +When running using `quarkus:dev` you should be able to do live coding whenever code changes within the `server` module. Changes you make to transient dependencies from the server extension (e.g: services, model, etc) won't be reflected into the running server. However, you can still leverage the hot swapping capabilities from your IDE to make changes at runtime. + +NOTE: Although still very handy during development, there are some limitations when running in dev mode that +blocks us to leverage all the capabilities from Quarkus dev mode. For instance, hot-reload of transient dependencies from the server extension (e.g.: keycloak-* dependencies) does not work. More improvements should be expected to improve the experience. + +NOTE: When developing custom providers, you should be able to benefit from live coding as long as you keep changes within the `server` module. + +Alternatively, you can run the server in development mode from your IDE. For that, run the `org.keycloak.quarkus._private.IDELauncher` main class. \ No newline at end of file diff --git a/quarkus/deployment/pom.xml b/quarkus/deployment/pom.xml index ddbee37955..263514bd8e 100644 --- a/quarkus/deployment/pom.xml +++ b/quarkus/deployment/pom.xml @@ -58,10 +58,6 @@ io.quarkus quarkus-jdbc-mysql-deployment - - io.quarkus - quarkus-vertx-web-deployment - io.quarkus quarkus-bootstrap-core 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 afe956a144..287430a4f8 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 @@ -50,8 +50,11 @@ import java.util.jar.JarFile; import io.quarkus.deployment.IsDevelopment; import io.quarkus.deployment.builditem.HotDeploymentWatchedFileBuildItem; import io.quarkus.deployment.builditem.IndexDependencyBuildItem; +import io.quarkus.deployment.builditem.LaunchModeBuildItem; +import io.quarkus.deployment.builditem.StaticInitConfigSourceProviderBuildItem; import io.quarkus.hibernate.orm.deployment.HibernateOrmConfig; import io.quarkus.resteasy.server.common.deployment.ResteasyDeploymentCustomizerBuildItem; +import io.quarkus.runtime.LaunchMode; import io.quarkus.smallrye.health.runtime.SmallRyeHealthHandler; import io.quarkus.vertx.http.deployment.RouteBuildItem; import io.vertx.core.Handler; @@ -85,6 +88,7 @@ import org.keycloak.provider.ProviderFactory; import org.keycloak.provider.ProviderManager; import org.keycloak.provider.Spi; import org.keycloak.provider.quarkus.QuarkusRequestFilter; +import org.keycloak.provider.quarkus.dev.QuarkusDevRequestFilter; import org.keycloak.quarkus.KeycloakRecorder; import io.quarkus.deployment.annotations.BuildProducer; @@ -191,6 +195,16 @@ class KeycloakProcessor { recorder.configSessionFactory(factories, defaultProviders, preConfiguredProviders, Environment.isRebuild()); } + /** + * Register the custom {@link org.eclipse.microprofile.config.spi.ConfigSource} implementations. + * + * @param configSources + */ + @BuildStep + void configureConfigSources(BuildProducer configSources) { + configSources.produce(new StaticInitConfigSourceProviderBuildItem(KeycloakConfigSourceProvider.class.getName())); + } + /** *

Make the build time configuration available at runtime so that the server can run without having to specify some of * the properties again. @@ -253,8 +267,15 @@ class KeycloakProcessor { } @BuildStep - void initializeFilter(BuildProducer filters) { - filters.produce(new FilterBuildItem(new QuarkusRequestFilter(),FilterBuildItem.AUTHORIZATION - 10)); + void initializeFilter(BuildProducer filters, LaunchModeBuildItem launchModeBuildItem) { + QuarkusRequestFilter filter = new QuarkusRequestFilter(); + LaunchMode launchMode = launchModeBuildItem.getLaunchMode(); + + if (launchMode.isDevOrTest()) { + filter = new QuarkusDevRequestFilter(); + } + + filters.produce(new FilterBuildItem(filter,FilterBuildItem.AUTHORIZATION - 10)); } /** @@ -283,10 +304,10 @@ class KeycloakProcessor { metricsHandler = new NotFoundHandler(); } - routes.produce(new RouteBuildItem(DEFAULT_HEALTH_ENDPOINT, healthHandler)); - routes.produce(new RouteBuildItem(DEFAULT_HEALTH_ENDPOINT.concat("/live"), healthHandler)); - routes.produce(new RouteBuildItem(DEFAULT_HEALTH_ENDPOINT.concat("/ready"), healthHandler)); - routes.produce(new RouteBuildItem(KeycloakMetricsHandler.DEFAULT_METRICS_ENDPOINT, metricsHandler)); + routes.produce(RouteBuildItem.builder().route(DEFAULT_HEALTH_ENDPOINT).handler(healthHandler).build()); + routes.produce(RouteBuildItem.builder().route(DEFAULT_HEALTH_ENDPOINT.concat("/live")).handler(healthHandler).build()); + routes.produce(RouteBuildItem.builder().route(DEFAULT_HEALTH_ENDPOINT.concat("/ready")).handler(healthHandler).build()); + routes.produce(RouteBuildItem.builder().route(KeycloakMetricsHandler.DEFAULT_METRICS_ENDPOINT).handler(metricsHandler).build()); } @BuildStep @@ -310,7 +331,7 @@ class KeycloakProcessor { private Map, Map>> loadFactories( Map preConfiguredProviders) { - loadConfig(); + Config.init(new MicroProfileConfigProvider()); BuildClassLoader providerClassLoader = new BuildClassLoader(); ProviderManager pm = new ProviderManager(KeycloakDeploymentInfo.create().services(), providerClassLoader); Map, Map>> factories = new HashMap<>(); @@ -485,18 +506,6 @@ class KeycloakProcessor { } } - protected void loadConfig() { - ServiceLoader loader = ServiceLoader.load(ConfigProviderFactory.class, KeycloakApplication.class.getClassLoader()); - - try { - ConfigProviderFactory factory = loader.iterator().next(); - logger.debugv("ConfigProvider: {0}", factory.getClass().getName()); - Config.init(factory.create().orElseThrow(() -> new RuntimeException("Failed to load Keycloak configuration"))); - } catch (NoSuchElementException e) { - throw new RuntimeException("No valid ConfigProvider found"); - } - } - private boolean isMetricsEnabled() { return Configuration.getOptionalBooleanValue(MicroProfileConfigProvider.NS_KEYCLOAK_PREFIX.concat("metrics.enabled")).orElse(false); } diff --git a/quarkus/deployment/src/test/resources/application.properties b/quarkus/deployment/src/test/resources/application.properties index 6aab7835e4..2b2f40dea2 100644 --- a/quarkus/deployment/src/test/resources/application.properties +++ b/quarkus/deployment/src/test/resources/application.properties @@ -2,4 +2,5 @@ quarkus.http.root-path=/ quarkus.application.name=Keycloak quarkus.banner.enabled=false -quarkus.resteasy.ignore-application-classes=true \ No newline at end of file +quarkus.resteasy.ignore-application-classes=true +quarkus.arc.ignored-split-packages=org.keycloak.* \ No newline at end of file diff --git a/quarkus/pom.xml b/quarkus/pom.xml index b6761dd4e2..1898dd0e81 100755 --- a/quarkus/pom.xml +++ b/quarkus/pom.xml @@ -31,21 +31,37 @@ pom - 1.13.3.Final - 4.5.9.Final - 2.12.1 + + 2.2.2.Final + + + 4.7.0.Final + 2.12.5 ${jackson.version} - 5.4.29.Final - 8.0.24 - 42.2.20 - 4.6.1 - 1.28 - 3.0.0-M5 - 1.5.4.Final-format-001 - 1.8 + 5.6.0.Beta1 + 8.0.26 + 42.2.23 + 3.0 + 1.5.4.Final-format-001 + + + 2.0.1.Final + 1.4.1.SP1 + 1.8.3 + UTF-8 - 1.8 - true + 3.8.1 + 11 + 11 + 11 + + 3.0.0-M5 @@ -58,12 +74,7 @@ import - - - org.hibernate - hibernate-core - ${hibernate.version} - + org.infinispan infinispan-core @@ -79,45 +90,74 @@ infinispan-client-hotrod ${infinispan.version} + + - org.wildfly.common - wildfly-common - ${wildfly.common.format.version} + org.jboss.spec.javax.xml.bind + jboss-jaxb-api_2.3_spec + ${org.jboss.spec.javax.xml.bind.jboss-jaxb-api_2.3_spec.version} + + + com.sun.xml.messaging.saaj + saaj-impl + ${sun.saaj-impl.version} - * - * + javax.xml.soap + saaj-api + + + org.jvnet.mimepull + mimepull + + + org.jvnet.staxex + stax-ex - org.yaml - snakeyaml - ${snakeyaml.version} - - - mysql - mysql-connector-java - ${mysql.driver.version} - - - org.postgresql - postgresql - ${postgresql.driver.version} + org.jvnet.staxex + stax-ex + ${org.jvnet.staxex.version} - org.checkerframework - checker-qual + javax.xml.stream + stax-api + + + javax.activation + activation + + + + + + org.apache.maven.plugins + maven-compiler-plugin + ${maven.compiler.plugin.version} + + + + - deployment runtime + deployment server + + + + jboss-public-repository + Jboss Public + https://repository.jboss.org/nexus/content/groups/public/ + + diff --git a/quarkus/runtime/pom.xml b/quarkus/runtime/pom.xml index ac4b4c630a..e01e4e05a3 100644 --- a/quarkus/runtime/pom.xml +++ b/quarkus/runtime/pom.xml @@ -12,6 +12,7 @@ Keycloak Quarkus Server Extension keycloak-quarkus-server + Keycloak Server @@ -76,7 +77,6 @@ info.picocli picocli - ${picocli.version} @@ -461,6 +461,19 @@ org.infinispan infinispan-jboss-marshalling + + org.jboss.spec.javax.xml.bind + jboss-jaxb-api_2.3_spec + + + com.sun.xml.messaging.saaj + saaj-impl + + + org.jvnet.staxex + stax-ex + + junit junit diff --git a/quarkus/runtime/src/main/java/org/keycloak/cli/KeycloakMain.java b/quarkus/runtime/src/main/java/org/keycloak/cli/KeycloakMain.java index 972b170921..983e15971b 100644 --- a/quarkus/runtime/src/main/java/org/keycloak/cli/KeycloakMain.java +++ b/quarkus/runtime/src/main/java/org/keycloak/cli/KeycloakMain.java @@ -22,6 +22,7 @@ import static org.keycloak.cli.Picocli.error; import static org.keycloak.cli.Picocli.getCliArgs; import static org.keycloak.cli.Picocli.parseConfigArgs; import static org.keycloak.util.Environment.getProfileOrDefault; +import static org.keycloak.util.Environment.isDevMode; import java.io.PrintWriter; import java.util.Arrays; @@ -51,7 +52,10 @@ public class KeycloakMain { if (cliArgs.length == 0) { // no arguments, just start the server start(Collections.emptyList(), new PrintWriter(System.err)); - System.exit(CommandLine.ExitCode.OK); + if (!isDevMode()) { + System.exit(CommandLine.ExitCode.OK); + } + return; } // parse arguments and execute any of the configured commands @@ -63,9 +67,20 @@ public class KeycloakMain { } private static void start(List cliArgs, PrintWriter errorWriter) { - Quarkus.run(null, (integer, throwable) -> { - error(cliArgs, errorWriter, String.format("Failed to start server using profile (%s).", getProfileOrDefault("none")), throwable.getCause()); - }); + try { + Quarkus.run(null, (integer, cause) -> { + if (cause != null) { + error(cliArgs, errorWriter, + String.format("Failed to start server using profile (%s)", getProfileOrDefault("none")), + cause.getCause()); + } + }); + } catch (Throwable cause) { + error(cliArgs, errorWriter, + String.format("Unexpected error when starting the server using profile (%s)", getProfileOrDefault("none")), + cause.getCause()); + } + Quarkus.waitForExit(); } @@ -96,6 +111,10 @@ public class KeycloakMain { System.exit(cmd.getCommandSpec().exitCodeOnExecutionException()); } - System.exit(cmd.execute(cliArgs.toArray(new String[cliArgs.size()]))); + int exitCode = cmd.execute(cliArgs.toArray(new String[cliArgs.size()])); + + if (!isDevMode()) { + System.exit(exitCode); + } } } diff --git a/quarkus/runtime/src/main/java/org/keycloak/cli/ShowConfigCommand.java b/quarkus/runtime/src/main/java/org/keycloak/cli/ShowConfigCommand.java index c5a92775e6..75c819e779 100644 --- a/quarkus/runtime/src/main/java/org/keycloak/cli/ShowConfigCommand.java +++ b/quarkus/runtime/src/main/java/org/keycloak/cli/ShowConfigCommand.java @@ -114,7 +114,12 @@ public final class ShowConfigCommand { @Override public boolean test(String s) { ConfigValue configValue = getConfigValue(s); - return configValue.getConfigSourceName().equals(PersistedConfigSource.NAME); + + if (configValue == null) { + return false; + } + + return PersistedConfigSource.NAME.equals(configValue.getConfigSourceName()); } }) .filter(property -> filterByGroup(property)) diff --git a/quarkus/runtime/src/main/java/org/keycloak/configuration/KeycloakConfigSourceProvider.java b/quarkus/runtime/src/main/java/org/keycloak/configuration/KeycloakConfigSourceProvider.java index a97e09eadd..eeb78af97f 100644 --- a/quarkus/runtime/src/main/java/org/keycloak/configuration/KeycloakConfigSourceProvider.java +++ b/quarkus/runtime/src/main/java/org/keycloak/configuration/KeycloakConfigSourceProvider.java @@ -105,7 +105,7 @@ public class KeycloakConfigSourceProvider implements ConfigSourceProvider { String homeDir = Environment.getHomeDir(); if (homeDir == null) { - return Paths.get(Platform.getPlatform().getTmpDirectory().toString(), PersistedConfigSource.KEYCLOAK_PROPERTIES); + return Paths.get(System.getProperty("java.io.tmpdir"), PersistedConfigSource.KEYCLOAK_PROPERTIES); } return Paths.get(homeDir, "conf", PersistedConfigSource.KEYCLOAK_PROPERTIES); diff --git a/quarkus/runtime/src/main/java/org/keycloak/configuration/SysPropConfigSource.java b/quarkus/runtime/src/main/java/org/keycloak/configuration/SysPropConfigSource.java index 25057321a0..94675d7f22 100644 --- a/quarkus/runtime/src/main/java/org/keycloak/configuration/SysPropConfigSource.java +++ b/quarkus/runtime/src/main/java/org/keycloak/configuration/SysPropConfigSource.java @@ -18,6 +18,7 @@ package org.keycloak.configuration; import java.util.Map; +import java.util.Set; import java.util.TreeMap; import org.eclipse.microprofile.config.spi.ConfigSource; @@ -44,6 +45,11 @@ public class SysPropConfigSource implements ConfigSource { return properties; } + @Override + public Set getPropertyNames() { + return properties.keySet(); + } + public String getValue(final String propertyName) { return System.getProperty(propertyName); } diff --git a/quarkus/runtime/src/main/java/org/keycloak/connections/jpa/QuarkusJpaConnectionProviderFactory.java b/quarkus/runtime/src/main/java/org/keycloak/connections/jpa/QuarkusJpaConnectionProviderFactory.java index 2c8867c3d1..bc098549db 100644 --- a/quarkus/runtime/src/main/java/org/keycloak/connections/jpa/QuarkusJpaConnectionProviderFactory.java +++ b/quarkus/runtime/src/main/java/org/keycloak/connections/jpa/QuarkusJpaConnectionProviderFactory.java @@ -51,6 +51,7 @@ import org.keycloak.Config; import org.keycloak.ServerStartupError; import org.keycloak.common.Version; import org.keycloak.connections.jpa.updater.JpaUpdaterProvider; +import org.keycloak.exportimport.ExportImportConfig; import org.keycloak.connections.jpa.updater.liquibase.conn.LiquibaseConnectionProvider; import org.keycloak.connections.jpa.util.JpaUtils; import org.keycloak.exportimport.ExportImportManager; @@ -152,14 +153,14 @@ public final class QuarkusJpaConnectionProviderFactory implements JpaConnectionP session.close(); } - if (initSchema) { + if (initSchema || ExportImportConfig.ACTION_EXPORT.equals(ExportImportConfig.getAction())) { runJobInTransaction(factory, this::initSchemaOrExport); } } @Override public Connection getConnection() { - SessionFactoryImpl entityManagerFactory = SessionFactoryImpl.class.cast(emf); + SessionFactoryImpl entityManagerFactory = emf.unwrap(SessionFactoryImpl.class); try { return entityManagerFactory.getJdbcServices().getBootstrapJdbcConnectionAccess().obtainConnection(); diff --git a/quarkus/runtime/src/main/java/org/keycloak/provider/quarkus/ResteasyVertxProvider.java b/quarkus/runtime/src/main/java/org/keycloak/provider/quarkus/ResteasyVertxProvider.java index d4310f91ec..ffb3dbf1d7 100644 --- a/quarkus/runtime/src/main/java/org/keycloak/provider/quarkus/ResteasyVertxProvider.java +++ b/quarkus/runtime/src/main/java/org/keycloak/provider/quarkus/ResteasyVertxProvider.java @@ -31,7 +31,13 @@ public class ResteasyVertxProvider implements ResteasyProvider { R data = ResteasyContext.getContextData(type); if (data == null) { - return (R) ResteasyContext.getContextData(RoutingContext.class).data().get(type.getName()); + RoutingContext contextData = ResteasyContext.getContextData(RoutingContext.class); + + if (contextData == null) { + return null; + } + + return (R) contextData.data().get(type.getName()); } return data; diff --git a/quarkus/runtime/src/main/java/org/keycloak/provider/quarkus/dev/QuarkusDevRequestFilter.java b/quarkus/runtime/src/main/java/org/keycloak/provider/quarkus/dev/QuarkusDevRequestFilter.java new file mode 100644 index 0000000000..1b172b7636 --- /dev/null +++ b/quarkus/runtime/src/main/java/org/keycloak/provider/quarkus/dev/QuarkusDevRequestFilter.java @@ -0,0 +1,20 @@ +package org.keycloak.provider.quarkus.dev; + +import io.vertx.ext.web.RoutingContext; +import org.keycloak.provider.quarkus.QuarkusRequestFilter; + +/** + * @author Pedro Igor + */ +public class QuarkusDevRequestFilter extends QuarkusRequestFilter { + + @Override + public void handle(RoutingContext context) { + if (context.request().uri().startsWith("/q/")) { + // do not go through Keycloak request filter if serving Quarkus resources such as dev console + context.next(); + return; + } + super.handle(context); + } +} diff --git a/quarkus/runtime/src/main/java/org/keycloak/quarkus/KeycloakRecorder.java b/quarkus/runtime/src/main/java/org/keycloak/quarkus/KeycloakRecorder.java index e8c2bba3ce..244eb93610 100644 --- a/quarkus/runtime/src/main/java/org/keycloak/quarkus/KeycloakRecorder.java +++ b/quarkus/runtime/src/main/java/org/keycloak/quarkus/KeycloakRecorder.java @@ -19,6 +19,7 @@ package org.keycloak.quarkus; import static org.keycloak.configuration.Configuration.getBuiltTimeProperty; import static org.keycloak.configuration.Configuration.getConfig; +import static org.keycloak.configuration.Configuration.getConfigValue; import java.util.List; import java.util.Map; @@ -135,17 +136,21 @@ public class KeycloakRecorder { if (!StreamSupport.stream(getConfig().getPropertyNames().spliterator(), false) .filter(new Predicate() { @Override - public boolean test(String s) { - ConfigValue configValue = getConfig().getConfigValue(s); + public boolean test(String propertyName) { + ConfigValue configValue = getConfigValue(propertyName); - return configValue.getConfigSourceName().equals(PersistedConfigSource.NAME); + if (configValue == null) { + return false; + } + + return PersistedConfigSource.NAME.equals(configValue.getSourceName()); } }) .anyMatch(new Predicate() { @Override - public boolean test(String s) { + public boolean test(String propertyName) { return PropertyMappers.canonicalFormat(finalPropertyName) - .equalsIgnoreCase(PropertyMappers.canonicalFormat(s)); + .equalsIgnoreCase(PropertyMappers.canonicalFormat(propertyName)); } })) { String prop = "--" + cliNameFormat.substring(3) + "=" + value.getValue(); diff --git a/quarkus/runtime/src/main/java/org/keycloak/services/health/KeycloakReadyHealthCheck.java b/quarkus/runtime/src/main/java/org/keycloak/services/health/KeycloakReadyHealthCheck.java index 0574fc2a95..4ddfd59664 100644 --- a/quarkus/runtime/src/main/java/org/keycloak/services/health/KeycloakReadyHealthCheck.java +++ b/quarkus/runtime/src/main/java/org/keycloak/services/health/KeycloakReadyHealthCheck.java @@ -59,7 +59,7 @@ public class KeycloakReadyHealthCheck extends DataSourceHealthCheck { long invalidCount = agroalDataSource.getMetrics().invalidCount(); if (activeCount < 1 || invalidCount > 0) { HealthCheckResponse activeCheckResult = super.call(); - if (activeCheckResult.getState() == HealthCheckResponse.State.DOWN) { + if (activeCheckResult.getStatus() == HealthCheckResponse.Status.DOWN) { builder.down(); Instant failingTime = failingSince.updateAndGet(this::createInstanceIfNeeded); builder.withData("Failing since", DATE_FORMATTER.format(failingTime)); 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 e535617b8b..109a84dc67 100644 --- a/quarkus/runtime/src/main/java/org/keycloak/util/Environment.java +++ b/quarkus/runtime/src/main/java/org/keycloak/util/Environment.java @@ -19,6 +19,8 @@ package org.keycloak.util; import java.util.Optional; +import io.quarkus.runtime.LaunchMode; +import io.quarkus.runtime.configuration.ProfileManager; import org.apache.commons.lang3.SystemUtils; import org.keycloak.configuration.Configuration; @@ -80,7 +82,12 @@ public final class Environment { } public static boolean isDevMode() { - return "dev".equalsIgnoreCase(getProfile()); + if ("dev".equalsIgnoreCase(getProfile())) { + return true; + } + + // if running in quarkus:dev mode + return ProfileManager.getLaunchMode() == LaunchMode.DEVELOPMENT; } public static boolean isWindows() { diff --git a/quarkus/runtime/src/main/resources/META-INF/quarkus-extension.yaml b/quarkus/runtime/src/main/resources/META-INF/quarkus-extension.yaml index 2698f85fe2..c85de7aa2a 100644 --- a/quarkus/runtime/src/main/resources/META-INF/quarkus-extension.yaml +++ b/quarkus/runtime/src/main/resources/META-INF/quarkus-extension.yaml @@ -1,4 +1,5 @@ --- +artifact: ${project.groupId}:${project.artifactId}:${project.version} name: "Keycloak" metadata: keywords: diff --git a/quarkus/runtime/src/test/java/org/keycloak/provider/quarkus/ConfigurationTest.java b/quarkus/runtime/src/test/java/org/keycloak/provider/quarkus/ConfigurationTest.java index 7474dfeb3c..1a1dc5e38b 100644 --- a/quarkus/runtime/src/test/java/org/keycloak/provider/quarkus/ConfigurationTest.java +++ b/quarkus/runtime/src/test/java/org/keycloak/provider/quarkus/ConfigurationTest.java @@ -26,6 +26,7 @@ import java.util.Map; import java.util.Properties; import io.quarkus.hibernate.orm.runtime.dialect.QuarkusH2Dialect; +import io.quarkus.runtime.LaunchMode; import io.smallrye.config.SmallRyeConfig; import org.eclipse.microprofile.config.ConfigProvider; import org.eclipse.microprofile.config.spi.ConfigProviderResolver; @@ -272,6 +273,6 @@ public class ConfigurationTest { private SmallRyeConfig createConfig() { KeycloakConfigSourceProvider.reload(); - return ConfigUtils.configBuilder(true, true).build(); + return ConfigUtils.configBuilder(true, LaunchMode.NORMAL).build(); } } diff --git a/quarkus/server/pom.xml b/quarkus/server/pom.xml index e57e54df90..ea10695d29 100644 --- a/quarkus/server/pom.xml +++ b/quarkus/server/pom.xml @@ -19,20 +19,6 @@ org.keycloak keycloak-quarkus-server - - - - org.keycloak - keycloak-quarkus-server-deployment - compile - - - * - * - - - diff --git a/quarkus/server/src/main/java/org/keycloak/quarkus/_private/IDELauncher.java b/quarkus/server/src/main/java/org/keycloak/quarkus/_private/IDELauncher.java new file mode 100644 index 0000000000..64766b36bd --- /dev/null +++ b/quarkus/server/src/main/java/org/keycloak/quarkus/_private/IDELauncher.java @@ -0,0 +1,39 @@ +package org.keycloak.quarkus._private; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import io.quarkus.runtime.Quarkus; +import org.keycloak.util.Environment; + +/** + *

This main class should be used to start the server in dev mode for development purposes. By running this class, + * developers should be able to mimic any server behavior and configuration as if they were using the CLI. + * + *

There are some limitations during development such as: + * + *

    + *
  • Transient dependencies from the keycloak server extension (runtime module) are not eligible for hot-reload
  • + *
  • Code changes such as changing the structure of classes (e.g.: new/change methods) should still require a JVM restart
  • + *
+ * + *

Despite the limitations, it should be possible to debug the extension (e.g.: deployment steps) as well as perform changes at runtime + * without having to restart the JVM. + * + * @author Pedro Igor + */ +public class IDELauncher { + + public static void main(String[] args) { + List devArgs = new ArrayList<>(); + + devArgs.addAll(Arrays.asList(args)); + + if (devArgs.isEmpty()) { + devArgs.add("start-dev"); + } + + Quarkus.run(devArgs.toArray(new String[devArgs.size()])); + } +} diff --git a/quarkus/server/src/main/resources/META-INF/keycloak.properties b/quarkus/server/src/main/resources/META-INF/keycloak.properties index 99881c49f0..55585939d5 100644 --- a/quarkus/server/src/main/resources/META-INF/keycloak.properties +++ b/quarkus/server/src/main/resources/META-INF/keycloak.properties @@ -1,14 +1,10 @@ # Default and non-production grade database vendor db=h2-file +db.username = sa +db.password = keycloak -# Default, and insecure, and non-production grade configuration for the development profile -%dev.http.enabled=true -%dev.db.username = sa -%dev.db.password = keycloak -%dev.cluster=local -%dev.spi.theme.cache-themes=false -%dev.spi.theme.cache-templates=false -%dev.spi.theme.static-max-age=-1 +# Insecure requests are disabled by default +http.enabled=false # Metrics and healthcheck are disabled by default metrics.enabled=false @@ -16,6 +12,13 @@ metrics.enabled=false # Themes spi.theme.folder.dir=${kc.home.dir:}/themes +# Default, and insecure, and non-production grade configuration for the development profile +%dev.http.enabled=true +%dev.cluster=local +%dev.spi.theme.cache-themes=false +%dev.spi.theme.cache-templates=false +%dev.spi.theme.static-max-age=-1 + # 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 diff --git a/quarkus/server/src/main/resources/application.properties b/quarkus/server/src/main/resources/application.properties index e5a595bd94..aabcb3fefc 100644 --- a/quarkus/server/src/main/resources/application.properties +++ b/quarkus/server/src/main/resources/application.properties @@ -20,3 +20,6 @@ quarkus.transaction-manager.default-transaction-timeout=300 # application classes are no longer supported by resteasy extension quarkus.resteasy.ignore-application-classes=true +# Ignore split packages for Keycloak related packages +quarkus.arc.ignored-split-packages=org.keycloak.* + diff --git a/testsuite/integration-arquillian/servers/auth-server/pom.xml b/testsuite/integration-arquillian/servers/auth-server/pom.xml index 802fd490bb..e5099541e2 100644 --- a/testsuite/integration-arquillian/servers/auth-server/pom.xml +++ b/testsuite/integration-arquillian/servers/auth-server/pom.xml @@ -42,7 +42,7 @@ - quarkus + auth-server-quarkus quarkus diff --git a/testsuite/integration-arquillian/servers/auth-server/quarkus/ant/configure.xml b/testsuite/integration-arquillian/servers/auth-server/quarkus/ant/configure.xml index 026e748d18..4e0296a02e 100644 --- a/testsuite/integration-arquillian/servers/auth-server/quarkus/ant/configure.xml +++ b/testsuite/integration-arquillian/servers/auth-server/quarkus/ant/configure.xml @@ -6,6 +6,7 @@ + diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/containers/KeycloakQuarkusServerDeployableContainer.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/containers/KeycloakQuarkusServerDeployableContainer.java index 0d6eee7159..90f03014dc 100644 --- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/containers/KeycloakQuarkusServerDeployableContainer.java +++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/containers/KeycloakQuarkusServerDeployableContainer.java @@ -126,7 +126,7 @@ public class KeycloakQuarkusServerDeployableContainer implements DeployableConta } if (isReaugmentBeforeStart()) { - List commands = new ArrayList<>(Arrays.asList("./kc.sh", "config", "-Dquarkus.http.root-path=/auth")); + List commands = new ArrayList<>(Arrays.asList("./kc.sh", "config", "-Dquarkus.http.root-path=/auth", "--http-enabled=true")); addAdditionalCommands(commands); diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/AbstractAdminTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/AbstractAdminTest.java index ba4f5cb030..e5fb1a4c36 100755 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/AbstractAdminTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/AbstractAdminTest.java @@ -34,6 +34,7 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; +import com.fasterxml.jackson.core.type.TypeReference; /** * This class adapts the functionality from the old testsuite to make tests @@ -100,4 +101,12 @@ public abstract class AbstractAdminTest extends AbstractTestRealmKeycloakTest { } } + public static T loadJson(InputStream is, TypeReference type) { + try { + return JsonSerialization.readValue(is, type); + } catch (IOException e) { + throw new RuntimeException("Failed to parse json", e); + } + } + } diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cli/admin/KcAdmSessionTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cli/admin/KcAdmSessionTest.java index 11fe86cdfb..16434f9535 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cli/admin/KcAdmSessionTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cli/admin/KcAdmSessionTest.java @@ -1,5 +1,6 @@ package org.keycloak.testsuite.cli.admin; +import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.node.ObjectNode; import org.junit.Assert; import org.junit.Test; @@ -26,7 +27,7 @@ import static org.keycloak.testsuite.cli.KcAdmExec.execute; @AuthServerContainerExclude({AuthServer.REMOTE, AuthServer.QUARKUS}) public class KcAdmSessionTest extends AbstractAdmCliTest { - static Class> LIST_OF_JSON = new ArrayList() {}.getClass(); + static TypeReference> LIST_OF_JSON = new TypeReference>() {}; @Test public void test() throws IOException { diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/VerifyProfileTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/VerifyProfileTest.java index b6e9646cef..b91ab8f31d 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/VerifyProfileTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/VerifyProfileTest.java @@ -943,9 +943,10 @@ public class VerifyProfileTest extends AbstractTestRealmKeycloakTest { public static void setUserProfileConfiguration(RealmResource testRealm, String configuration) { - Response r = testRealm.users().userProfile().update(configuration); - if (r.getStatus() != 200) { - Assert.fail("UserProfile Configuration not set due to error: " + r.readEntity(String.class)); + try (Response r = testRealm.users().userProfile().update(configuration)) { + if (r.getStatus() != 200) { + Assert.fail("UserProfile Configuration not set due to error: " + r.readEntity(String.class)); + } } }