[KEYCLOAK-17866] - Upgrade to Quarkus v2

This commit is contained in:
Pedro Igor 2021-08-04 12:04:26 -03:00
parent 47484c1aed
commit aa018295c4
32 changed files with 339 additions and 132 deletions

View file

@ -2,6 +2,9 @@ name: Keycloak CI
on: [push, pull_request] on: [push, pull_request]
env:
DEFAULT_JDK_VERSION: 11
jobs: jobs:
build: build:
name: Build name: Build
@ -10,7 +13,7 @@ jobs:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- uses: actions/setup-java@v1 - uses: actions/setup-java@v1
with: with:
java-version: 1.8 java-version: ${{ env.DEFAULT_JDK_VERSION }}
- name: Update maven settings - name: Update maven settings
run: mkdir -p ~/.m2 ; cp .github/settings.xml ~/.m2/ run: mkdir -p ~/.m2 ; cp .github/settings.xml ~/.m2/
- name: Cache Maven packages - name: Cache Maven packages
@ -24,7 +27,7 @@ jobs:
- name: Build Keycloak - name: Build Keycloak
run: | 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-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-wildfly
mvn clean install -nsu -B -e -f testsuite/integration-arquillian/servers/auth-server -Pauth-server-undertow 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/checkout@v2
- uses: actions/setup-java@v1 - uses: actions/setup-java@v1
with: with:
java-version: 1.8 java-version: ${{ env.DEFAULT_JDK_VERSION }}
- name: Update maven settings - name: Update maven settings
run: mkdir -p ~/.m2 ; cp .github/settings.xml ~/.m2/ run: mkdir -p ~/.m2 ; cp .github/settings.xml ~/.m2/
- name: Cache Maven packages - name: Cache Maven packages
@ -94,7 +97,7 @@ jobs:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- uses: actions/setup-java@v1 - uses: actions/setup-java@v1
with: with:
java-version: 1.8 java-version: ${{ env.DEFAULT_JDK_VERSION }}
- name: Update maven settings - name: Update maven settings
run: mkdir -p ~/.m2 ; cp .github/settings.xml ~/.m2/ run: mkdir -p ~/.m2 ; cp .github/settings.xml ~/.m2/
- name: Cache Maven packages - name: Cache Maven packages
@ -160,7 +163,7 @@ jobs:
- uses: actions/setup-java@v1 - uses: actions/setup-java@v1
with: with:
java-version: 1.8 java-version: ${{ env.DEFAULT_JDK_VERSION }}
- name: Update maven settings - name: Update maven settings
run: mkdir -p ~/.m2 ; cp .github/settings.xml ~/.m2/ run: mkdir -p ~/.m2 ; cp .github/settings.xml ~/.m2/
@ -224,7 +227,7 @@ jobs:
- uses: actions/setup-java@v1 - uses: actions/setup-java@v1
if: ${{ github.event_name != 'pull_request' || env.GIT_DIFF != 0 }} if: ${{ github.event_name != 'pull_request' || env.GIT_DIFF != 0 }}
with: with:
java-version: 1.8 java-version: ${{ env.DEFAULT_JDK_VERSION }}
- name: Update maven settings - name: Update maven settings
if: ${{ github.event_name != 'pull_request' || env.GIT_DIFF != 0 }} if: ${{ github.event_name != 'pull_request' || env.GIT_DIFF != 0 }}
run: mkdir -p ~/.m2 ; cp .github/settings.xml ~/.m2/ run: mkdir -p ~/.m2 ; cp .github/settings.xml ~/.m2/
@ -263,7 +266,7 @@ jobs:
- uses: actions/setup-java@v1 - uses: actions/setup-java@v1
if: ${{ github.event_name != 'pull_request' || env.GIT_DIFF != 0 }} if: ${{ github.event_name != 'pull_request' || env.GIT_DIFF != 0 }}
with: with:
java-version: 1.8 java-version: ${{ env.DEFAULT_JDK_VERSION }}
- name: Update maven settings - name: Update maven settings
if: ${{ github.event_name != 'pull_request' || env.GIT_DIFF != 0 }} if: ${{ github.event_name != 'pull_request' || env.GIT_DIFF != 0 }}
run: mkdir -p ~/.m2 ; cp .github/settings.xml ~/.m2/ run: mkdir -p ~/.m2 ; cp .github/settings.xml ~/.m2/
@ -336,7 +339,7 @@ jobs:
name: keycloak-artifacts.zip name: keycloak-artifacts.zip
- uses: actions/setup-java@v1 - uses: actions/setup-java@v1
with: with:
java-version: 1.8 java-version: ${{ env.DEFAULT_JDK_VERSION }}
- name: Update maven settings - name: Update maven settings
run: mkdir -p ~/.m2 ; cp .github/settings.xml ~/.m2/ run: mkdir -p ~/.m2 ; cp .github/settings.xml ~/.m2/
- name: Run Quarkus cluster tests - name: Run Quarkus cluster tests
@ -374,7 +377,7 @@ jobs:
- uses: actions/setup-java@v1 - uses: actions/setup-java@v1
with: with:
java-version: 1.8 java-version: 8
- name: Update maven settings - name: Update maven settings
run: mkdir -p ~/.m2 ; cp .github/settings.xml ~/.m2/ run: mkdir -p ~/.m2 ; cp .github/settings.xml ~/.m2/
- name: Cache Maven packages - name: Cache Maven packages

View file

@ -78,8 +78,9 @@
</modules> </modules>
</profile> </profile>
<profile> <profile>
<id>community</id> <id>quarkus</id>
<activation> <activation>
<jdk>[11,)</jdk>
<property> <property>
<name>!product</name> <name>!product</name>
</property> </property>

View file

@ -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 ## Starting Keycloak
To start Keycloak during development first build as specified above, then run: To start Keycloak during development first build as specified above, then run:

10
pom.xml
View file

@ -1897,6 +1897,16 @@
<ee.maven.version>${wildfly.version}</ee.maven.version> <ee.maven.version>${wildfly.version}</ee.maven.version>
<product.filename.version>${project.version}</product.filename.version> <product.filename.version>${project.version}</product.filename.version>
</properties> </properties>
</profile>
<profile>
<id>quarkus</id>
<activation>
<jdk>[11,)</jdk>
<property>
<name>!product</name>
</property>
</activation>
<modules> <modules>
<module>quarkus</module> <module>quarkus</module>
</modules> </modules>

View file

@ -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 ├── 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 ## Building
Ensure you have JDK 11 (or newer) installed.
To build the module and produce the artifacts to run a server: To build the module and produce the artifacts to run a server:
mvn -f ../pom.xml clean install -DskipTestsuite -DskipExamples -DskipTests 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 ### Building the Distribution
To build the module as well as the distribution packages: 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). 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 ## Running
By default, the HTTP port is disabled and you need to provide the key material to configure HTTPS. If you want to enable 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: 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 ## Contributing
@ -40,17 +54,19 @@ the HTTP port, run the server in development mode as follows:
To run the server in development mode: To run the server in development mode:
cd server mvn -f server/pom.xml compile quarkus:dev
mvn compile quarkus:dev
You should be able to attach your debugger to port `5005`. 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 For debugging the build steps, you can suspend the JVM by running:
be recognized automatically when running in development mode.
However, considering that there is no real code in the `server` module (but from `runtime` and its dependencies), changes you make to mvn -f server/pom.xml -Dsuspend=true compile quarkus:dev
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.
NOTE: We need to improve DevX and figure out why changes to dependencies are not being recognized when running tests or running 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.
Quarkus Dev Mode.
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.

View file

@ -58,10 +58,6 @@
<groupId>io.quarkus</groupId> <groupId>io.quarkus</groupId>
<artifactId>quarkus-jdbc-mysql-deployment</artifactId> <artifactId>quarkus-jdbc-mysql-deployment</artifactId>
</dependency> </dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-vertx-web-deployment</artifactId>
</dependency>
<dependency> <dependency>
<groupId>io.quarkus</groupId> <groupId>io.quarkus</groupId>
<artifactId>quarkus-bootstrap-core</artifactId> <artifactId>quarkus-bootstrap-core</artifactId>

View file

@ -50,8 +50,11 @@ import java.util.jar.JarFile;
import io.quarkus.deployment.IsDevelopment; import io.quarkus.deployment.IsDevelopment;
import io.quarkus.deployment.builditem.HotDeploymentWatchedFileBuildItem; import io.quarkus.deployment.builditem.HotDeploymentWatchedFileBuildItem;
import io.quarkus.deployment.builditem.IndexDependencyBuildItem; 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.hibernate.orm.deployment.HibernateOrmConfig;
import io.quarkus.resteasy.server.common.deployment.ResteasyDeploymentCustomizerBuildItem; import io.quarkus.resteasy.server.common.deployment.ResteasyDeploymentCustomizerBuildItem;
import io.quarkus.runtime.LaunchMode;
import io.quarkus.smallrye.health.runtime.SmallRyeHealthHandler; import io.quarkus.smallrye.health.runtime.SmallRyeHealthHandler;
import io.quarkus.vertx.http.deployment.RouteBuildItem; import io.quarkus.vertx.http.deployment.RouteBuildItem;
import io.vertx.core.Handler; import io.vertx.core.Handler;
@ -85,6 +88,7 @@ import org.keycloak.provider.ProviderFactory;
import org.keycloak.provider.ProviderManager; import org.keycloak.provider.ProviderManager;
import org.keycloak.provider.Spi; import org.keycloak.provider.Spi;
import org.keycloak.provider.quarkus.QuarkusRequestFilter; import org.keycloak.provider.quarkus.QuarkusRequestFilter;
import org.keycloak.provider.quarkus.dev.QuarkusDevRequestFilter;
import org.keycloak.quarkus.KeycloakRecorder; import org.keycloak.quarkus.KeycloakRecorder;
import io.quarkus.deployment.annotations.BuildProducer; import io.quarkus.deployment.annotations.BuildProducer;
@ -191,6 +195,16 @@ class KeycloakProcessor {
recorder.configSessionFactory(factories, defaultProviders, preConfiguredProviders, Environment.isRebuild()); recorder.configSessionFactory(factories, defaultProviders, preConfiguredProviders, Environment.isRebuild());
} }
/**
* Register the custom {@link org.eclipse.microprofile.config.spi.ConfigSource} implementations.
*
* @param configSources
*/
@BuildStep
void configureConfigSources(BuildProducer<StaticInitConfigSourceProviderBuildItem> configSources) {
configSources.produce(new StaticInitConfigSourceProviderBuildItem(KeycloakConfigSourceProvider.class.getName()));
}
/** /**
* <p>Make the build time configuration available at runtime so that the server can run without having to specify some of * <p>Make the build time configuration available at runtime so that the server can run without having to specify some of
* the properties again. * the properties again.
@ -253,8 +267,15 @@ class KeycloakProcessor {
} }
@BuildStep @BuildStep
void initializeFilter(BuildProducer<FilterBuildItem> filters) { void initializeFilter(BuildProducer<FilterBuildItem> filters, LaunchModeBuildItem launchModeBuildItem) {
filters.produce(new FilterBuildItem(new QuarkusRequestFilter(),FilterBuildItem.AUTHORIZATION - 10)); 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(); metricsHandler = new NotFoundHandler();
} }
routes.produce(new RouteBuildItem(DEFAULT_HEALTH_ENDPOINT, healthHandler)); routes.produce(RouteBuildItem.builder().route(DEFAULT_HEALTH_ENDPOINT).handler(healthHandler).build());
routes.produce(new RouteBuildItem(DEFAULT_HEALTH_ENDPOINT.concat("/live"), healthHandler)); routes.produce(RouteBuildItem.builder().route(DEFAULT_HEALTH_ENDPOINT.concat("/live")).handler(healthHandler).build());
routes.produce(new RouteBuildItem(DEFAULT_HEALTH_ENDPOINT.concat("/ready"), healthHandler)); routes.produce(RouteBuildItem.builder().route(DEFAULT_HEALTH_ENDPOINT.concat("/ready")).handler(healthHandler).build());
routes.produce(new RouteBuildItem(KeycloakMetricsHandler.DEFAULT_METRICS_ENDPOINT, metricsHandler)); routes.produce(RouteBuildItem.builder().route(KeycloakMetricsHandler.DEFAULT_METRICS_ENDPOINT).handler(metricsHandler).build());
} }
@BuildStep @BuildStep
@ -310,7 +331,7 @@ class KeycloakProcessor {
private Map<Spi, Map<Class<? extends Provider>, Map<String, ProviderFactory>>> loadFactories( private Map<Spi, Map<Class<? extends Provider>, Map<String, ProviderFactory>>> loadFactories(
Map<String, ProviderFactory> preConfiguredProviders) { Map<String, ProviderFactory> preConfiguredProviders) {
loadConfig(); Config.init(new MicroProfileConfigProvider());
BuildClassLoader providerClassLoader = new BuildClassLoader(); BuildClassLoader providerClassLoader = new BuildClassLoader();
ProviderManager pm = new ProviderManager(KeycloakDeploymentInfo.create().services(), providerClassLoader); ProviderManager pm = new ProviderManager(KeycloakDeploymentInfo.create().services(), providerClassLoader);
Map<Spi, Map<Class<? extends Provider>, Map<String, ProviderFactory>>> factories = new HashMap<>(); Map<Spi, Map<Class<? extends Provider>, Map<String, ProviderFactory>>> factories = new HashMap<>();
@ -485,18 +506,6 @@ class KeycloakProcessor {
} }
} }
protected void loadConfig() {
ServiceLoader<ConfigProviderFactory> 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() { private boolean isMetricsEnabled() {
return Configuration.getOptionalBooleanValue(MicroProfileConfigProvider.NS_KEYCLOAK_PREFIX.concat("metrics.enabled")).orElse(false); return Configuration.getOptionalBooleanValue(MicroProfileConfigProvider.NS_KEYCLOAK_PREFIX.concat("metrics.enabled")).orElse(false);
} }

View file

@ -3,3 +3,4 @@ quarkus.application.name=Keycloak
quarkus.banner.enabled=false quarkus.banner.enabled=false
quarkus.resteasy.ignore-application-classes=true quarkus.resteasy.ignore-application-classes=true
quarkus.arc.ignored-split-packages=org.keycloak.*

View file

@ -31,21 +31,37 @@
<packaging>pom</packaging> <packaging>pom</packaging>
<properties> <properties>
<quarkus.version>1.13.3.Final</quarkus.version> <!-- Quarkus version -->
<resteasy.version>4.5.9.Final</resteasy.version> <quarkus.version>2.2.2.Final</quarkus.version>
<jackson.version>2.12.1</jackson.version>
<!--
Override versions based on Quarkus dependencies.
Make sure to update these dependencies when Quarkus version changes.
-->
<resteasy.version>4.7.0.Final</resteasy.version>
<jackson.version>2.12.5</jackson.version>
<jackson.databind.version>${jackson.version}</jackson.databind.version> <jackson.databind.version>${jackson.version}</jackson.databind.version>
<hibernate.version>5.4.29.Final</hibernate.version> <hibernate.core.version>5.6.0.Beta1</hibernate.core.version>
<mysql.driver.version>8.0.24</mysql.driver.version> <mysql.driver.version>8.0.26</mysql.driver.version>
<postgresql.driver.version>42.2.20</postgresql.driver.version> <postgresql.version>42.2.23</postgresql.version>
<picocli.version>4.6.1</picocli.version> <microprofile-metrics-api.version>3.0</microprofile-metrics-api.version>
<snakeyaml.version>1.28</snakeyaml.version> <wildfly.common.version>1.5.4.Final-format-001</wildfly.common.version>
<surefire-plugin.version>3.0.0-M5</surefire-plugin.version>
<wildfly.common.format.version>1.5.4.Final-format-001</wildfly.common.format.version> <!--
<maven.compiler.source>1.8</maven.compiler.source> Java EE dependencies. Not available from JDK 11+.
The dependencies and their versions are the same used by Wildfly distribution.
-->
<org.jboss.spec.javax.xml.bind.jboss-jaxb-api_2.3_spec.version>2.0.1.Final</org.jboss.spec.javax.xml.bind.jboss-jaxb-api_2.3_spec.version>
<sun.saaj-impl.version>1.4.1.SP1</sun.saaj-impl.version>
<org.jvnet.staxex.version>1.8.3</org.jvnet.staxex.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.target>1.8</maven.compiler.target> <maven.compiler.plugin.version>3.8.1</maven.compiler.plugin.version>
<noDeps>true</noDeps> <maven.compiler.release>11</maven.compiler.release>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<surefire-plugin.version>3.0.0-M5</surefire-plugin.version>
</properties> </properties>
<dependencyManagement> <dependencyManagement>
@ -58,12 +74,7 @@
<scope>import</scope> <scope>import</scope>
</dependency> </dependency>
<!-- Override the dependencies below to use the versions used by Quarkus --> <!-- Override the dependencies below to use the versions used by Keycloak -->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
<version>${hibernate.version}</version>
</dependency>
<dependency> <dependency>
<groupId>org.infinispan</groupId> <groupId>org.infinispan</groupId>
<artifactId>infinispan-core</artifactId> <artifactId>infinispan-core</artifactId>
@ -79,45 +90,74 @@
<artifactId>infinispan-client-hotrod</artifactId> <artifactId>infinispan-client-hotrod</artifactId>
<version>${infinispan.version}</version> <version>${infinispan.version}</version>
</dependency> </dependency>
<!-- Dependencies removed from JDK 11 and in compliance with those used by Wildfly. -->
<dependency> <dependency>
<groupId>org.wildfly.common</groupId> <groupId>org.jboss.spec.javax.xml.bind</groupId>
<artifactId>wildfly-common</artifactId> <artifactId>jboss-jaxb-api_2.3_spec</artifactId>
<version>${wildfly.common.format.version}</version> <version>${org.jboss.spec.javax.xml.bind.jboss-jaxb-api_2.3_spec.version}</version>
</dependency>
<dependency>
<groupId>com.sun.xml.messaging.saaj</groupId>
<artifactId>saaj-impl</artifactId>
<version>${sun.saaj-impl.version}</version>
<exclusions> <exclusions>
<exclusion> <exclusion>
<groupId>*</groupId> <groupId>javax.xml.soap</groupId>
<artifactId>*</artifactId> <artifactId>saaj-api</artifactId>
</exclusion>
<exclusion>
<groupId>org.jvnet.mimepull</groupId>
<artifactId>mimepull</artifactId>
</exclusion>
<exclusion>
<groupId>org.jvnet.staxex</groupId>
<artifactId>stax-ex</artifactId>
</exclusion> </exclusion>
</exclusions> </exclusions>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.yaml</groupId> <groupId>org.jvnet.staxex</groupId>
<artifactId>snakeyaml</artifactId> <artifactId>stax-ex</artifactId>
<version>${snakeyaml.version}</version> <version>${org.jvnet.staxex.version}</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.driver.version}</version>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>${postgresql.driver.version}</version>
<exclusions> <exclusions>
<exclusion> <exclusion>
<groupId>org.checkerframework</groupId> <groupId>javax.xml.stream</groupId>
<artifactId>checker-qual</artifactId> <artifactId>stax-api</artifactId>
</exclusion>
<exclusion>
<groupId>javax.activation</groupId>
<artifactId>activation</artifactId>
</exclusion> </exclusion>
</exclusions> </exclusions>
</dependency> </dependency>
</dependencies> </dependencies>
</dependencyManagement> </dependencyManagement>
<build>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>${maven.compiler.plugin.version}</version>
</plugin>
</plugins>
</pluginManagement>
</build>
<modules> <modules>
<module>deployment</module>
<module>runtime</module> <module>runtime</module>
<module>deployment</module>
<module>server</module> <module>server</module>
</modules> </modules>
<repositories>
<repository>
<id>jboss-public-repository</id>
<name>Jboss Public</name>
<url>https://repository.jboss.org/nexus/content/groups/public/</url>
</repository>
</repositories>
</project> </project>

View file

@ -12,6 +12,7 @@
<name>Keycloak Quarkus Server Extension</name> <name>Keycloak Quarkus Server Extension</name>
<artifactId>keycloak-quarkus-server</artifactId> <artifactId>keycloak-quarkus-server</artifactId>
<description>Keycloak Server</description>
<dependencies> <dependencies>
<!-- Quarkus --> <!-- Quarkus -->
@ -76,7 +77,6 @@
<dependency> <dependency>
<groupId>info.picocli</groupId> <groupId>info.picocli</groupId>
<artifactId>picocli</artifactId> <artifactId>picocli</artifactId>
<version>${picocli.version}</version>
</dependency> </dependency>
<!-- Keycloak --> <!-- Keycloak -->
@ -461,6 +461,19 @@
<groupId>org.infinispan</groupId> <groupId>org.infinispan</groupId>
<artifactId>infinispan-jboss-marshalling</artifactId> <artifactId>infinispan-jboss-marshalling</artifactId>
</dependency> </dependency>
<dependency>
<groupId>org.jboss.spec.javax.xml.bind</groupId>
<artifactId>jboss-jaxb-api_2.3_spec</artifactId>
</dependency>
<dependency>
<groupId>com.sun.xml.messaging.saaj</groupId>
<artifactId>saaj-impl</artifactId>
</dependency>
<dependency>
<groupId>org.jvnet.staxex</groupId>
<artifactId>stax-ex</artifactId>
</dependency>
<dependency> <dependency>
<groupId>junit</groupId> <groupId>junit</groupId>
<artifactId>junit</artifactId> <artifactId>junit</artifactId>

View file

@ -22,6 +22,7 @@ import static org.keycloak.cli.Picocli.error;
import static org.keycloak.cli.Picocli.getCliArgs; import static org.keycloak.cli.Picocli.getCliArgs;
import static org.keycloak.cli.Picocli.parseConfigArgs; import static org.keycloak.cli.Picocli.parseConfigArgs;
import static org.keycloak.util.Environment.getProfileOrDefault; import static org.keycloak.util.Environment.getProfileOrDefault;
import static org.keycloak.util.Environment.isDevMode;
import java.io.PrintWriter; import java.io.PrintWriter;
import java.util.Arrays; import java.util.Arrays;
@ -51,8 +52,11 @@ public class KeycloakMain {
if (cliArgs.length == 0) { if (cliArgs.length == 0) {
// no arguments, just start the server // no arguments, just start the server
start(Collections.emptyList(), new PrintWriter(System.err)); start(Collections.emptyList(), new PrintWriter(System.err));
if (!isDevMode()) {
System.exit(CommandLine.ExitCode.OK); System.exit(CommandLine.ExitCode.OK);
} }
return;
}
// parse arguments and execute any of the configured commands // parse arguments and execute any of the configured commands
parseAndRun(cliArgs); parseAndRun(cliArgs);
@ -63,9 +67,20 @@ public class KeycloakMain {
} }
private static void start(List<String> cliArgs, PrintWriter errorWriter) { private static void start(List<String> cliArgs, PrintWriter errorWriter) {
Quarkus.run(null, (integer, throwable) -> { try {
error(cliArgs, errorWriter, String.format("Failed to start server using profile (%s).", getProfileOrDefault("none")), throwable.getCause()); 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(); Quarkus.waitForExit();
} }
@ -96,6 +111,10 @@ public class KeycloakMain {
System.exit(cmd.getCommandSpec().exitCodeOnExecutionException()); 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);
}
} }
} }

View file

@ -114,7 +114,12 @@ public final class ShowConfigCommand {
@Override @Override
public boolean test(String s) { public boolean test(String s) {
ConfigValue configValue = getConfigValue(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)) .filter(property -> filterByGroup(property))

View file

@ -105,7 +105,7 @@ public class KeycloakConfigSourceProvider implements ConfigSourceProvider {
String homeDir = Environment.getHomeDir(); String homeDir = Environment.getHomeDir();
if (homeDir == null) { 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); return Paths.get(homeDir, "conf", PersistedConfigSource.KEYCLOAK_PROPERTIES);

View file

@ -18,6 +18,7 @@
package org.keycloak.configuration; package org.keycloak.configuration;
import java.util.Map; import java.util.Map;
import java.util.Set;
import java.util.TreeMap; import java.util.TreeMap;
import org.eclipse.microprofile.config.spi.ConfigSource; import org.eclipse.microprofile.config.spi.ConfigSource;
@ -44,6 +45,11 @@ public class SysPropConfigSource implements ConfigSource {
return properties; return properties;
} }
@Override
public Set<String> getPropertyNames() {
return properties.keySet();
}
public String getValue(final String propertyName) { public String getValue(final String propertyName) {
return System.getProperty(propertyName); return System.getProperty(propertyName);
} }

View file

@ -51,6 +51,7 @@ import org.keycloak.Config;
import org.keycloak.ServerStartupError; import org.keycloak.ServerStartupError;
import org.keycloak.common.Version; import org.keycloak.common.Version;
import org.keycloak.connections.jpa.updater.JpaUpdaterProvider; 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.updater.liquibase.conn.LiquibaseConnectionProvider;
import org.keycloak.connections.jpa.util.JpaUtils; import org.keycloak.connections.jpa.util.JpaUtils;
import org.keycloak.exportimport.ExportImportManager; import org.keycloak.exportimport.ExportImportManager;
@ -152,14 +153,14 @@ public final class QuarkusJpaConnectionProviderFactory implements JpaConnectionP
session.close(); session.close();
} }
if (initSchema) { if (initSchema || ExportImportConfig.ACTION_EXPORT.equals(ExportImportConfig.getAction())) {
runJobInTransaction(factory, this::initSchemaOrExport); runJobInTransaction(factory, this::initSchemaOrExport);
} }
} }
@Override @Override
public Connection getConnection() { public Connection getConnection() {
SessionFactoryImpl entityManagerFactory = SessionFactoryImpl.class.cast(emf); SessionFactoryImpl entityManagerFactory = emf.unwrap(SessionFactoryImpl.class);
try { try {
return entityManagerFactory.getJdbcServices().getBootstrapJdbcConnectionAccess().obtainConnection(); return entityManagerFactory.getJdbcServices().getBootstrapJdbcConnectionAccess().obtainConnection();

View file

@ -31,7 +31,13 @@ public class ResteasyVertxProvider implements ResteasyProvider {
R data = ResteasyContext.getContextData(type); R data = ResteasyContext.getContextData(type);
if (data == null) { 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; return data;

View file

@ -0,0 +1,20 @@
package org.keycloak.provider.quarkus.dev;
import io.vertx.ext.web.RoutingContext;
import org.keycloak.provider.quarkus.QuarkusRequestFilter;
/**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/
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);
}
}

View file

@ -19,6 +19,7 @@ package org.keycloak.quarkus;
import static org.keycloak.configuration.Configuration.getBuiltTimeProperty; import static org.keycloak.configuration.Configuration.getBuiltTimeProperty;
import static org.keycloak.configuration.Configuration.getConfig; import static org.keycloak.configuration.Configuration.getConfig;
import static org.keycloak.configuration.Configuration.getConfigValue;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -135,17 +136,21 @@ public class KeycloakRecorder {
if (!StreamSupport.stream(getConfig().getPropertyNames().spliterator(), false) if (!StreamSupport.stream(getConfig().getPropertyNames().spliterator(), false)
.filter(new Predicate<String>() { .filter(new Predicate<String>() {
@Override @Override
public boolean test(String s) { public boolean test(String propertyName) {
ConfigValue configValue = getConfig().getConfigValue(s); ConfigValue configValue = getConfigValue(propertyName);
return configValue.getConfigSourceName().equals(PersistedConfigSource.NAME); if (configValue == null) {
return false;
}
return PersistedConfigSource.NAME.equals(configValue.getSourceName());
} }
}) })
.anyMatch(new Predicate<String>() { .anyMatch(new Predicate<String>() {
@Override @Override
public boolean test(String s) { public boolean test(String propertyName) {
return PropertyMappers.canonicalFormat(finalPropertyName) return PropertyMappers.canonicalFormat(finalPropertyName)
.equalsIgnoreCase(PropertyMappers.canonicalFormat(s)); .equalsIgnoreCase(PropertyMappers.canonicalFormat(propertyName));
} }
})) { })) {
String prop = "--" + cliNameFormat.substring(3) + "=" + value.getValue(); String prop = "--" + cliNameFormat.substring(3) + "=" + value.getValue();

View file

@ -59,7 +59,7 @@ public class KeycloakReadyHealthCheck extends DataSourceHealthCheck {
long invalidCount = agroalDataSource.getMetrics().invalidCount(); long invalidCount = agroalDataSource.getMetrics().invalidCount();
if (activeCount < 1 || invalidCount > 0) { if (activeCount < 1 || invalidCount > 0) {
HealthCheckResponse activeCheckResult = super.call(); HealthCheckResponse activeCheckResult = super.call();
if (activeCheckResult.getState() == HealthCheckResponse.State.DOWN) { if (activeCheckResult.getStatus() == HealthCheckResponse.Status.DOWN) {
builder.down(); builder.down();
Instant failingTime = failingSince.updateAndGet(this::createInstanceIfNeeded); Instant failingTime = failingSince.updateAndGet(this::createInstanceIfNeeded);
builder.withData("Failing since", DATE_FORMATTER.format(failingTime)); builder.withData("Failing since", DATE_FORMATTER.format(failingTime));

View file

@ -19,6 +19,8 @@ package org.keycloak.util;
import java.util.Optional; import java.util.Optional;
import io.quarkus.runtime.LaunchMode;
import io.quarkus.runtime.configuration.ProfileManager;
import org.apache.commons.lang3.SystemUtils; import org.apache.commons.lang3.SystemUtils;
import org.keycloak.configuration.Configuration; import org.keycloak.configuration.Configuration;
@ -80,7 +82,12 @@ public final class Environment {
} }
public static boolean isDevMode() { 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() { public static boolean isWindows() {

View file

@ -1,4 +1,5 @@
--- ---
artifact: ${project.groupId}:${project.artifactId}:${project.version}
name: "Keycloak" name: "Keycloak"
metadata: metadata:
keywords: keywords:

View file

@ -26,6 +26,7 @@ import java.util.Map;
import java.util.Properties; import java.util.Properties;
import io.quarkus.hibernate.orm.runtime.dialect.QuarkusH2Dialect; import io.quarkus.hibernate.orm.runtime.dialect.QuarkusH2Dialect;
import io.quarkus.runtime.LaunchMode;
import io.smallrye.config.SmallRyeConfig; import io.smallrye.config.SmallRyeConfig;
import org.eclipse.microprofile.config.ConfigProvider; import org.eclipse.microprofile.config.ConfigProvider;
import org.eclipse.microprofile.config.spi.ConfigProviderResolver; import org.eclipse.microprofile.config.spi.ConfigProviderResolver;
@ -272,6 +273,6 @@ public class ConfigurationTest {
private SmallRyeConfig createConfig() { private SmallRyeConfig createConfig() {
KeycloakConfigSourceProvider.reload(); KeycloakConfigSourceProvider.reload();
return ConfigUtils.configBuilder(true, true).build(); return ConfigUtils.configBuilder(true, LaunchMode.NORMAL).build();
} }
} }

View file

@ -19,20 +19,6 @@
<groupId>org.keycloak</groupId> <groupId>org.keycloak</groupId>
<artifactId>keycloak-quarkus-server</artifactId> <artifactId>keycloak-quarkus-server</artifactId>
</dependency> </dependency>
<!-- This dependency should not be here but due to the structure of the modules we need to make sure it is built
before this module -->
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-quarkus-server-deployment</artifactId>
<scope>compile</scope>
<exclusions>
<exclusion>
<groupId>*</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies> </dependencies>
<build> <build>

View file

@ -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;
/**
* <p>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.
*
* <p>There are some limitations during development such as:
*
* <ul>
* <li>Transient dependencies from the keycloak server extension (runtime module) are not eligible for hot-reload</li>
* <li>Code changes such as changing the structure of classes (e.g.: new/change methods) should still require a JVM restart</li>
* </ul>
*
* <p>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 <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/
public class IDELauncher {
public static void main(String[] args) {
List<String> devArgs = new ArrayList<>();
devArgs.addAll(Arrays.asList(args));
if (devArgs.isEmpty()) {
devArgs.add("start-dev");
}
Quarkus.run(devArgs.toArray(new String[devArgs.size()]));
}
}

View file

@ -1,14 +1,10 @@
# Default and non-production grade database vendor # Default and non-production grade database vendor
db=h2-file db=h2-file
db.username = sa
db.password = keycloak
# Default, and insecure, and non-production grade configuration for the development profile # Insecure requests are disabled by default
%dev.http.enabled=true http.enabled=false
%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
# Metrics and healthcheck are disabled by default # Metrics and healthcheck are disabled by default
metrics.enabled=false metrics.enabled=false
@ -16,6 +12,13 @@ metrics.enabled=false
# Themes # Themes
spi.theme.folder.dir=${kc.home.dir:}/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 # Logging configuration. INFO is the default level for most of the categories
#quarkus.log.level = DEBUG #quarkus.log.level = DEBUG
quarkus.log.category."org.jboss.resteasy.resteasy_jaxrs.i18n".level=WARN quarkus.log.category."org.jboss.resteasy.resteasy_jaxrs.i18n".level=WARN

View file

@ -20,3 +20,6 @@ quarkus.transaction-manager.default-transaction-timeout=300
# application classes are no longer supported by resteasy extension # application classes are no longer supported by resteasy extension
quarkus.resteasy.ignore-application-classes=true quarkus.resteasy.ignore-application-classes=true
# Ignore split packages for Keycloak related packages
quarkus.arc.ignored-split-packages=org.keycloak.*

View file

@ -42,7 +42,7 @@
<profiles> <profiles>
<profile> <profile>
<id>quarkus</id> <id>auth-server-quarkus</id>
<modules> <modules>
<module>quarkus</module> <module>quarkus</module>
</modules> </modules>

View file

@ -6,6 +6,7 @@
<exec osfamily="unix" dir="${auth.server.home}/bin" executable="./kc.sh" failonerror="true"> <exec osfamily="unix" dir="${auth.server.home}/bin" executable="./kc.sh" failonerror="true">
<arg value="config"/> <arg value="config"/>
<arg value="-Dquarkus.http.root-path=/auth"/> <arg value="-Dquarkus.http.root-path=/auth"/>
<arg value="--http-enabled=true"/>
</exec> </exec>
</target> </target>

View file

@ -126,7 +126,7 @@ public class KeycloakQuarkusServerDeployableContainer implements DeployableConta
} }
if (isReaugmentBeforeStart()) { if (isReaugmentBeforeStart()) {
List<String> commands = new ArrayList<>(Arrays.asList("./kc.sh", "config", "-Dquarkus.http.root-path=/auth")); List<String> commands = new ArrayList<>(Arrays.asList("./kc.sh", "config", "-Dquarkus.http.root-path=/auth", "--http-enabled=true"));
addAdditionalCommands(commands); addAdditionalCommands(commands);

View file

@ -34,6 +34,7 @@ import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import com.fasterxml.jackson.core.type.TypeReference;
/** /**
* This class adapts the functionality from the old testsuite to make tests * 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> T loadJson(InputStream is, TypeReference<T> type) {
try {
return JsonSerialization.readValue(is, type);
} catch (IOException e) {
throw new RuntimeException("Failed to parse json", e);
}
}
} }

View file

@ -1,5 +1,6 @@
package org.keycloak.testsuite.cli.admin; package org.keycloak.testsuite.cli.admin;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.node.ObjectNode; import com.fasterxml.jackson.databind.node.ObjectNode;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Test; import org.junit.Test;
@ -26,7 +27,7 @@ import static org.keycloak.testsuite.cli.KcAdmExec.execute;
@AuthServerContainerExclude({AuthServer.REMOTE, AuthServer.QUARKUS}) @AuthServerContainerExclude({AuthServer.REMOTE, AuthServer.QUARKUS})
public class KcAdmSessionTest extends AbstractAdmCliTest { public class KcAdmSessionTest extends AbstractAdmCliTest {
static Class<? extends List<ObjectNode>> LIST_OF_JSON = new ArrayList<ObjectNode>() {}.getClass(); static TypeReference<List<ObjectNode>> LIST_OF_JSON = new TypeReference<List<ObjectNode>>() {};
@Test @Test
public void test() throws IOException { public void test() throws IOException {

View file

@ -943,11 +943,12 @@ public class VerifyProfileTest extends AbstractTestRealmKeycloakTest {
public static void setUserProfileConfiguration(RealmResource testRealm, String configuration) { public static void setUserProfileConfiguration(RealmResource testRealm, String configuration) {
Response r = testRealm.users().userProfile().update(configuration); try (Response r = testRealm.users().userProfile().update(configuration)) {
if (r.getStatus() != 200) { if (r.getStatus() != 200) {
Assert.fail("UserProfile Configuration not set due to error: " + r.readEntity(String.class)); Assert.fail("UserProfile Configuration not set due to error: " + r.readEntity(String.class));
} }
} }
}
public static UserRepresentation getUser(RealmResource testRealm, String userId) { public static UserRepresentation getUser(RealmResource testRealm, String userId) {
return testRealm.users().get(userId).toRepresentation(); return testRealm.users().get(userId).toRepresentation();