feat(keycloak): render the client-URL desired state from the realm import

The realm import is a one-shot: once Done=True it never re-runs, so a redirect URI
changed in git never reaches the realm. Healing that needs a DESIRED STATE the
reconciler can read -- and the reconciler pod has no Kubernetes API access at all
(automountServiceAccountToken: false), so it cannot read the KeycloakRealmImport CR.
Its desired state must be mounted, hence a ConfigMap.

A hand-authored ConfigMap would be a second source of truth for every public
hostname in the realm, free to disagree with the import -- the same shape as the bug
being closed. So it is GENERATED from the import and the render is gated, the same
render==golden idiom catalog/render.py already uses for apps/.

URL strings are copied VERBATIM: a ${BASE_DOMAIN} token in the source appears
unchanged here, and both files are rendered by the SAME Flux Kustomization
(./platform-config), so both are substituted in one pass with one value. The
renderer never parses a hostname and never reads cluster-vars, so there is no
second place the domain can be decided.

Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
jakman85andClaude Opus 5 committed 2026-09-12 21:24:52 -05:00
1 parent fa4deac877
commit 51d8676f90
3 files changed
+382

No files matched your search

+11
View File
@@ -93,6 +93,17 @@ resources:
# so it applies after the keycloak-realm-clients Secret (rendered by ./platform) and
# alongside the import whose failure mode it covers.
- ../platform/keycloak/client-secret-reconciler.yaml
# The DESIRED STATE for every environment-derived client URL (redirectUris, webOrigins,
# rootUrl, baseUrl, adminUrl), GENERATED from keycloak-realm-import.yaml by
# ci/render-keycloak-client-urls.py and gated by the L0 check. The reconciler pod has no
# Kubernetes API access (automountServiceAccountToken: false), so it cannot read the
# KeycloakRealmImport CR — its desired state must be MOUNTED, hence this ConfigMap.
#
# IT MUST BE RENDERED BY THIS SAME KUSTOMIZATION as the realm import above: both are then
# substituted in one pass by this Kustomization's postBuild.substituteFrom (cluster-vars),
# so a ${BASE_DOMAIN} token in either resolves to the same value and the two declarations
# cannot disagree about the domain.
- ../platform/keycloak/client-urls.yaml
- ../platform/keycloak/k8s-oidc-rbac.yaml # maps OIDC groups -> cluster roles (kubectl SSO RBAC)
- ../platform/keycloak/httproute.yaml
- ../platform/keycloak/servicemonitor.yaml