keycloak/quarkus/CONTRIBUTING.md

13 KiB

Contributing guide

Keycloak Quarkus Extension

Keycloak on Quarkus is basically a Quarkus Extension. For more details about extensions, please take a look at Writing Your Own Extension guide.

As an extension, the server can be embedded in any Quarkus application by adding the following dependency:

<dependencies>
    <dependency>
        <groupId>org.keycloak</groupId>
        <artifactId>keycloak-quarkus-server</artifactId>
    </dependency>
</dependencies>

Just like any other extension, the server extension has two main modules:

  • deployment
  • runtime

Within the deployment module you'll find the implementation for the build steps that run when (re)augmenting the application. This module is responsible for all metadata processing, such as:

  • Lookup SPIs and their providers and enable/disable them accordingly
  • Create a closed-world assumption about the providers that should be registered to the KeycloakSessionFactory
  • Customizations to how Hibernate, Resteasy, Liquibase, Infinispan, and other dependencies are configured and bootstrapped
  • Creating Jandex indexes for some server dependencies

The output of this module is bytecode generated by Quarkus that improves the server startup time and memory usage so that any processing done during (re)augmentation does not happen again when actually starting the server. Note that the code from this module does not run at all at runtime (when running the server) but only when building the project (the server module in particular, more on that later) or when triggering a re-augmentation when running the build command.

Within the runtime module you'll find the code that is run at runtime when starting and running the server. The main link between the deployment and runtime modules is the org.keycloak.quarkus.runtime.KeycloakRecorder class. The KeycloakRecorder holds a set of methods that are invoked from build steps that will end up in recorded bytecode. The recorded bytecode executes those methods at runtime just like they were called when a build step is run, with the exact same arguments and values, if any.

As an example, let us look at how the SPIs and their providers are discovered and how the KeycloakSessionFactory is created. Quarkus will run the org.keycloak.quarkus.deployment.KeycloakProcessor.configureProviders build step whenever you are (re)augmenting the server. The main outcome from this method is to invoke the org.keycloak.quarkus.runtime.KeycloakRecorder.configSessionFactory so that the code from this method is executed at runtime with a closed-world assumption about the providers that should be registered to the KeycloakSessionFactory. At runtime the code from that method is executed and a KeycloakSessionFactory is created without any processing to discover SPIs and their providers.

There are a few reasons why we have Keycloak as a Quarkus extension:

  • More control over the build-time augmentation and runtime stages.
  • More flexibility when extending Quarkus itself and the extensions used by the server (e.g.: Hibernate, Resteasy, etc.)
  • Make it easier to embed the server into Quarkus applications
  • Make it possible to allow Keycloak developers to customize the server distribution

Keycloak Server

The server module holds the Keycloak Server itself. It is basically a regular Quarkus application using the keycloak-quarkus-server extension. If you look at the pom.xml from this module you'll notice that it is a very simple Quarkus application with the bare minimum configuration to actually build and run the server.

As Quarkus application, the server is a mutable application using the mutable-jar package type. As a mutable application, Keycloak is able to allow users to configure some aspects of the server without having to re-build this module, something impractical from a user perspective.

The mutability of the server is directly related to the build command. As mentioned before, the build steps from the deployment module are only run when (re)augmenting the server and when running the build command, the server will indicate to Quarkus that the build steps should re-run and the recorded bytecode should be updated to reflect any change to the server configuration.

From a Quarkus perspective, the server is also a Command Mode Application and provides a CLI based on Picocli. As such, there is a single entrypoint that executes the code to execute the CLI and bootstrap the server. This entry point is the org.keycloak.quarkus.runtime.KeycloakMain class from the runtime module.

Keycloak Distribution

The server distribution is created by build the dist module. This module basically consists of packaging the deployment, runtime, and server modules artifacts and their dependencies to a ZIP or tarball file.

Within this directory you'll find the directory structure of the distribution and what is included in it.

Running the server in development mode

As a regular Quarkus application, you are able to run the server module in dev mode just like any regular application:

cd server
mvn clean quarkus:dev -Dquarkus.args="start-dev"

You can set any command or configuration option to the server by setting the quarkus.args environment variable.

When running in dev mode, you can benefit from the dev mode capabilities from Quarkus but with some limitations. The main limitations you'll find at the moment are:

  • Changes are only automatically reflected at runtime if you are changing resources from the deployment, runtime, and server modules. Other modules, such as keycloak-services still rely on Hot Swap in Java debuggers to reload classes.
  • There is nothing in the Dev UI related to the server itself, although you can still change some configuration from there.
  • There are some limitations when passing some options when running in dev mode. You should expect more improvements in this area.

We are working to improve the dev experience, and you should expect improvements over time.

Debugging the server distribution

The kc.sh|bat script allows you to remotely debug the distribution. For that, you should run the server as follows:

kc.sh --debug start-dev

By default, the debug port is available at 8787.

An additional environment variable DEBUG_SUSPEND can be set to suspend the JVM, when launched in debug mode. The DEBUG_SUSPEND variable supports the following values:

  • y - The debug mode JVM launch is suspended
  • n - The debug mode JVM is started without suspending

Suspending the JVM when in debug mode is useful if you want to debug the early stages of the bootstrap code.

When making changes to the deployment, runtime, or server modules, you can update the distribution with the new artifacts by executing the following command:

mvn -DskipTests clean install

After the quarkus module and sub-modules are built, you can update the distribution as follows:

cp -r server/target/lib ${KC_HOME_DIR}

In the example above, the ${KC_HOME_DIR} variable points to the root directory of the distribution.

You should also be able to update a server dependency directly. For that, copy the jar to the following location:

cp services/target/keycloak-services-${KC_VERSION}.jar ${KC_HOME_DIR}/lib/lib/main/org.keycloak.keycloak-services-${KC_VERSION}.jar

Running tests

The distribution has its own distribution and the main tests are within the tests/integration module.

The test suite has two main types of tests:

  • jvm
  • distribution

The jvm tests execute both the test class and server within the same JVM. While the distribution tests execute the server by running the distribution in a separte JVM.

The distribution tests are marked as such using the DistributionTest annotation. If not marked with this annotation, the test is a JVM test.

To run the tests, execute the following command within the quarkus module:

mvn clean install

By default, the tests will run using a raw distribution. If you want to run tests from the tests/integration module directly, please make sure the distribution was built with the latest changes you did to it and its dependencies.

Running tests using a container

You can also run tests using a container instead of the raw distribution by setting the kc.quarkus.tests.dist property as follows:

mvn clean install -Dkc.quarkus.tests.dist=docker

When setting the kc.quarkus.tests.dist to docker tests will run using a container instead of the raw distribution.

Running storage tests

The storage tests are disabled by default but can be activated using the test-database profile:

mvn clean install -Ptest-database -Dtest=PostgreSQLDistTest

Running tests from the IDE

You are also able to run tests from your IDE. For that, choose a test from the tests/integration module and execute it accordingly.

Running tests from the base testsuite

Sometimes you might want to run the tests from the base test suite using the distribution. For that, make sure you have built the quarkus module and then execute the following command from the project root directory:

mvn -f testsuite/integration-arquillian/pom.xml clean install -Pauth-server-quarkus -Dtest=OIDCProtocolMappersTest

Resolving DNS names when running tests

In order to avoid using external services for DNS resolution, the tests are executed using a local host file by setting the -Djdk.net.hosts.file=${project.build.testOutputDirectory}/hosts_file system property.

Documentation

The documentation is a set of guides available from the docs module. Please, look at the this guide about how to update and build the distribution guides.

Before contributing the changes

Before contributing changes, make to read the main Keycloak Contributing Guide.

Please, make sure:

  • Documentation is updated if you are introducing any new behavior or changing an existing one that needs to be communicated
  • Make sure you have a test within the tests/integration module to cover the changes
  • You probably want to run a full build of the quarkus module, including running tests, to make sure you won't be surprised by failures in CI.

Upgrading Quarkus Version

Upgrading Quarkus requires a few steps:

  • Change the Quarkus version
  • Change the dependencies we are using from Quarkus
  • Run a build to make sure the server extension is not broken

The steps still require a lot of manual work, and we should be improving this.

Changing the Quarkus version

To change the Quarkus version, you can run the following script:

./set-quarkus-version.sh <version>

The set-quarkus-version.sh script is enough to change the version for all dependencies we are using from Quarkus.

The <version> should point to a branch or a tag from Quarkus repository.

It is also possible to change to a snapshot version by running:

./set-quarkus-version.sh

Run a local build

After changing the dependency versions, you can run a local build to make sure the server extension is not broken by API changes and if all tests are passing:

mvn clean install

Changing versions of JDBC Extensions

It might happen that when upgrading a version for any of the JDBC extensions (e.g.: quarkus-jdbc-postgresql) you also need to make sure the server extension is using the same JDBC Drivers.

For that, you should look at the deployment module of the corresponding JDBC extension from Quarkus (e.g.: https://github.com/quarkusio/quarkus/blob/main/extensions/jdbc/jdbc-postgresql/deployment/src/main/java/io/quarkus/jdbc/postgresql/deployment/JDBCPostgreSQLProcessor.java) to check if they match with the drivers used by the server extension by looking at the org.keycloak.config.database.Database class.

Changing versions of Quarkiverse dependencies

Make sure the Quarkiverse dependencies are also updated and in sync with the Quarkus version you are upgrading.

For now, we only have this Quarkiverse dependency:

What can go wrong when upgrading?

The perfect scenario is that after performing all the steps above the server extension will compile, the distribution can be built, and all tests will pass.

However, it is expected breaking changes between Quarkus upgrades that break the integration code we have in both deployment and runtime modules. When this happens, you should understand what is breaking and upgrade the integration code accordingly.

References