Add startup entrypoint, multi-stage build, and aligned CI/CD - #4
Merged
Conversation
…ostnames Keycloak's realm import silently drops the config for the 'username' protocol mapper in the 'profile' client scope, reverting it to defaults. The realm JSON also has redirect URIs and web origins hardcoded to a specific hostname, making the image non-portable across environments. This adds an entrypoint script that wraps kc.sh, waits for the realm import to complete, then idempotently patches the mapper config and (when SERVER_HOST is set) rewrites client URIs to match the deployment hostname. See: keycloak/keycloak#36065 See: keycloak/keycloak#16289 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace the tag-triggered release workflow with the standard auto-bump-on-main pattern used by other webprotege services. Merge to main now auto-increments the patch version, builds and tests the Docker image, pushes to Docker Hub, creates a GitHub release, and notifies the deploy repo to update docker-compose.yml. Rename test.yaml to ci.yaml so it runs on feature branches and PRs only, matching the convention in other repos. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The release workflow now follows the standard Maven convention: pom contains the next version with -SNAPSHOT suffix, the workflow strips it for the release, then bumps patch and adds -SNAPSHOT back for the next development cycle. Major/minor version changes are controlled by setting the pom version on the branch (e.g. 3.0.0-SNAPSHOT for a major bump). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
5 tasks
Newly registered Keycloak users do not have the webprotege_username user attribute, which the username protocol mapper maps to the preferred_username JWT claim. Without it, the claim is absent from tokens and the API gateway treats the user as null, causing 400 errors. This adds a Keycloak EventListenerProvider SPI that fires on REGISTER events and sets webprotege_username to the Keycloak username for any newly registered user who does not already have the attribute. Migrated users are unaffected since their attribute is pre-populated. The listener is registered in the realm's eventsListeners array so Keycloak invokes it when users register. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The webprotege client previously defined six client roles (user, admin, SystemAdmin, ICAT_APPLICATION, ProjectCreator, ProjectManager). Only SystemAdmin was actually used — the authorization service's JwtRolesExtractor reads the resource_access.webprotege.roles claim and maps values to BuiltInRole entries. The other five roles were either not in BuiltInRole at all (user, admin, ICAT_APPLICATION) or were project-level roles that should not be granted globally via Keycloak (ProjectManager) or had an alternative path through Application Settings (ProjectCreator). Also removed unused realm-level roles (admin, SYSTEM_ADMIN) and updated the default-roles-webprotege composite to drop references to them. The realm-level SYSTEM_ADMIN was particularly misleading — the code only reads client-level roles, so this realm role was a silent no-op despite being granted to every user via the default composite. Removed the ICAT_APPLICATION assignment from the affected service-account user. The separate Icatx_application client (which also defines its own ICAT_APPLICATION role) is untouched. After cleanup, SystemAdmin is the only application-level role exposed via Keycloak. All other authorization decisions flow through WebProtege's own RoleAssignment mechanism or Application Settings. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The Icatx_application client and its service-account user were part of the ICAT-X customization of WebProtege, which lives on a separate branch parallel to main. Keeping it in the main realm bakes ICAT-X-specific authorization objects into every vanilla WebProtege deployment. Removed: - The Icatx_application client definition - The roles.client.Icatx_application entry (uma_protection, ICAT_APPLICATION) - The service-account-icatx_application user The ICAT-X branch can re-add these via its own fork of webprotege.json or via post-import kcadm commands in its own entrypoint customization. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Explain what the role grants, when to use it (first admin bootstrap), and the underlying mechanism (JWT resource_access.webprotege.roles mapped to BuiltInRole capabilities). The description is visible in the Keycloak admin console on the role detail page. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The previous description (1008 chars) exceeded Keycloak's 255-char VARCHAR limit on the role description column, causing a database error when imported or saved via the admin API. Condensed to 248 chars while keeping the essentials: what it grants, when to use it, and how it maps to the code. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The machine-client Keycloak client is not referenced by any vanilla WebProtege service. If it is used by a downstream customization (e.g. ICAT-X), that fork can re-add it in its own realm JSON or via post-import kcadm commands. Removed: - The machine-client client definition and its protocol mappers - The roles.client.machine-client entry - The service-account-machine-client user Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add a description to each client explaining its purpose: what backs it, who talks to it, and (where relevant) how it integrates with WebProtege services. Covers both the WebProtege-specific clients (webprotege, user-management) and the Keycloak built-in clients (account, account-console, admin-cli, broker, realm-management, security-admin-console). Someone reading the realm for the first time now has context on every entry without having to infer from URL paths or client settings. All descriptions are under 255 chars to fit the Keycloak DB column limit on the CLIENT description column. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add a 'Roles' section explaining that SystemAdmin is the only application-level role exposed via Keycloak and linking to the deploy repo README for the step-by-step bootstrap procedure. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
This PR eliminates the manual Keycloak setup steps that operators currently have to perform after deploying WebProtege. It also modernises the build and release process to match the patterns used by other webprotege services.
Before this PR
After starting Keycloak, operators had to manually:
kcadm.sh create realms(~1 step)usernameprotocol mapper across 4 sub-steps because Keycloak's realm import silently drops the mapper config (keycloak#36065, keycloak#16289)Self-hosted deployments also had to manually edit the realm JSON or run kcadm commands to update redirect URIs, web origins, and the frontend URL to match their hostname.
Building the Docker image required Java and Maven installed locally to compile the SPI plugin before running
docker build.After this PR
Keycloak starts, imports the realm, fixes the mapper, and configures all URIs automatically. The operator just sets
SERVER_HOSTand runsdocker compose up.Building the image requires only Docker —
docker build .handles everything.What changed
Entrypoint script (
entrypoint.sh)A bash wrapper around
kc.shthat runs after Keycloak starts:webprotegerealm via kcadm if it doesn't already exist. Keycloak does not auto-import from/opt/keycloak/import/; the entrypoint handles this explicitly.usernamemapper in theprofileclient scope correctly mapswebprotege_usernametopreferred_username. If not, deletes and recreates it. This works around a Keycloak bug where the import drops the mapper's config dict.SERVER_HOSTis set, updates thewebprotegeclient'sbaseUrl,redirectUris,webOrigins, and the realm'sfrontendUrlto match. This makes the image portable across local dev, staging, and production.All operations are idempotent — on subsequent restarts, the script detects the correct state and skips modifications. SIGTERM/SIGINT are forwarded to the Keycloak process for graceful shutdown.
Multi-stage Dockerfile
maven:3.9-eclipse-temurin-17. No local Java or Maven needed.jqbinary from GitHub releases (architecture-aware viaTARGETARCH). Used by the entrypoint to parse kcadm JSON output.keycloak/keycloak:26.1as before, with the entrypoint set as the image'sENTRYPOINT.Integration test (
test-entrypoint.sh)A self-contained test script that verifies the entrypoint end-to-end:
SERVER_HOST15 assertions total, runs in ~60 seconds. No host port needed (all checks run inside the container via
docker exec).CI/CD workflows
Aligned with the pattern used by other webprotege services:
ci.yaml— runs on feature branches and PRs. Builds the image and runs the integration test.release.yaml— triggers on merge to main. Reads the version frompom.xml, strips-SNAPSHOT, releases that version, then bumps patch and adds-SNAPSHOTback for the next development cycle. Includes the integration test as a gate before pushing to Docker Hub. Notifies the deploy repo after a successful release.notify-deploy-project.yaml— reusable workflow that triggersupdate-compose.ymlinwebprotege-deployto update the image version indocker-compose.yml.Versioning
Follows the standard Maven
-SNAPSHOTconvention:pom.xmlcontains the next intended version with-SNAPSHOT(e.g.2.0.0-SNAPSHOT)-SNAPSHOTand releases that version (2.0.0)2.0.1-SNAPSHOTX.Y.0-SNAPSHOTon your branch before mergingBranch protection
Added a "Protect Main" ruleset matching
webprotege-backend-service: no deletion, no force push, PRs required, bot bypass for release commits.Test plan
docker build .)