--- date: 2026-10-30 branch: feat/renovate-track-keycloak-operator title: Renovate tracks the Keycloak operator (the real Keycloak version), never auto-merged; the catalog's phantom image is pinned as documentation and disabled --- ## What changed - **`platform/keycloak/kustomization.yaml`**: each of the three `keycloak-k8s-resources/26.0.6` release URLs (two CRDs and `kubernetes.yml`) now has a `# renovate: datasource=github-tags depName=keycloak/keycloak-k8s-resources` marker on the line above it. That is the format the existing `custom.regex` marker manager parses (the kubevirt pattern). A comment explains that these URLs are the Keycloak version. The `Keycloak` CR has no `spec.image`, so the operator's `RELATED_IMAGE_KEYCLOAK` picks the server image. - **`renovate.json5`** has two new packageRules: - **`keycloak/keycloak-k8s-resources`**: `groupName: keycloak-operator`, `automerge: false`, `addLabels: [needs-review, blast-radius]`. A comment explains why. The operator, the CRDs and the server are released in lockstep. The IdP gates SSO for every app and for kubectl. And `passkey-reconciler.yaml` encodes Keycloak 26.0.6 Admin API shapes that the static gate never exercises. Majors still go through the existing dashboard-approval rule. - **`quay.io/keycloak/keycloak`**: `enabled: false`. Its only occurrences are in files that deploy nothing. The "STILL CANNOT FIX" comment also gains a one-line correction for keycloak. - **`catalog/keycloak.yaml`**: `source.image` changed from `quay.io/keycloak/keycloak` (no tag) to `quay.io/keycloak/keycloak:26.0.6`. A comment says the image is DOCUMENTATION-ONLY and that the operator chooses the real one. `apps/wave-1/keycloak.yaml` was re-rendered, which changes one line. - **`platform/keycloak/README.md`**: - Corrects the false claim "Renovate maintains the version". Until now Renovate could not see the version. - Adds a new section, **"Upgrading Keycloak: the operator IS the version"**. It lists the 26.0.6 assumptions in the passkey reconciler to diff against a new tag, and the post-merge checks. **The phantom: pin and disable, not delete.** `apps/wave-1/keycloak.yaml` is not listed in `apps/wave-1/kustomization.yaml`, and render-golden excludes it (`RENDER_GOLDEN_NOT_ONBOARDED`). Deleting it would still not be a no-op: - It is the fixture for the keycloak cases in `apps/wave-1/tests/app-slis.test.yaml` (the StatefulSet-based `keycloak-down` rule). Those cases are discovered by the every-PrometheusRule-in-apps/wave-1 convention. - Removing it means moving that rule and its tests to `platform/keycloak`. That is a separate observability change, not a Renovate one. Pinning plus `enabled: false` changes one line in a file nothing applies. It cannot affect the cluster, and render-golden stays green. ## What I verified - **Live state, prod, read-only kubectl, 2026-10-30.** - Keycloak CR `keycloak` has an empty `spec.image`. - The operator Deployment runs `quay.io/keycloak/keycloak-operator:26.0.6`, imageID `sha256:8e3a1a56346f…`, with `RELATED_IMAGE_KEYCLOAK=quay.io/keycloak/keycloak:26.0.6`. - `keycloak-0` and `keycloak-1` both run `quay.io/keycloak/keycloak:26.0.6`, imageID `sha256:a93d22e13b86…`. - There is no PrometheusRule containing "keycloak" in any namespace. This confirms the phantom deploys nothing, including its `keycloak-down` rule (see the next section). - **`renovate-config-validator`** on the pinned `ghcr.io/renovatebot/renovate:44.167.0`: `Config validated successfully`. The only migration note is the pre-existing `docker` block. - **Local dry run** (`RENOVATE_PLATFORM=local`, `RENOVATE_DRY_RUN=full`, debug, the same pinned image, this worktree, with a GitHub token passed by environment variable; the token string occurs 0 times in the log): - The regex manager went to 21 files / 24 deps. - `keycloak/keycloak-k8s-resources` was extracted three times from `platform/keycloak/kustomization.yaml`, each at `currentValue 26.0.6`, datasource `github-tags`, versioning `semver`. - Each extraction has one update, `26.8.1`, updateType `minor`, and all three share **one** `branchName: renovate/keycloak-operator`. - `quay.io/keycloak/keycloak` was extracted from `catalog/keycloak.yaml` (regex) and from `apps/wave-1/keycloak.yaml` (kubernetes) at 26.0.6, with `skipReason: disabled` and no updates. - **Rule effects.** `--platform=local` does not print each branch's merged flags. So I ran Renovate's own `applyPackageRules` (from `dist/util/package-rules` in the same 44.167.0 image) over this `renovate.json5` for each case: | Update | Result | |---|---| | operator minor 26.0.6 → 26.8.1 | `automerge:false`, `groupName:keycloak-operator`, labels `dependencies, automated-update, needs-review, blast-radius` | | operator patch | same as the minor | | operator major | `automerge:false`, `dependencyDashboardApproval:true`, labels add `breaking` | | `quay.io/keycloak/keycloak`, catalog and rendered | `enabled:false` | | control: sonarr patch | `automerge:true`, so the rule did not disable auto-merge globally | - **render-golden, host:** `render.py --selftest` passed, and `all 10 onboarded service golden(s) reproduce`. Before the pin, the committed phantom was byte-identical to the canonical render. I re-rendered it after the pin. - **Toolbox gate** (commit 24db269, worktree with the ENGINE shim mounting `.git` and the spec repo, `DEV_KUBECONFIG=/dev/null`): **rc 3**. - The only stage that did not run is **`gitea-settings`**. - spec-immutability, decisions-log, lint, l0, render-golden, l1 and approval-policy all passed. - L2/L3 `conformance` scaffold-skipped, as expected. - **Not green.** - **Sensitive paths:** none touched (checked against `infra/gitea/approval-policy.json`), so no `/approve-override` is needed. ## What I did NOT verify - **A real Renovate run.** No PR was opened by Renovate. The labels and `automerge:false` are proven by Renovate's own matcher, not by an opened PR. - **That 26.8.1 is safe to adopt.** I did not diff 26.0.6 against 26.8.1. That includes the reconciler's assumptions: `recurseExecutions` displayName, `updateExecutions`, the `RpEntityName` guard, `requiredActions` REPLACE, and config aliases. The README section lists them for whoever reviews the first bump PR. It also says to take or confirm a fresh CNPG backup first, because a server minor may migrate the schema. - **FINDING, not fixed: Keycloak has no deployed "IdP down" alert.** The `keycloak-down` SLI rule exists only inside the undeployed `apps/wave-1/keycloak.yaml`. promtool tests it, but it is not on the cluster: there is no keycloak PrometheusRule live. Separately, because that file exists, `render.py --emit-waves` would treat keycloak as onboarded. It would then add a Deployment named `keycloak` beside the operator's StatefulSet (the known `--emit-waves` hazard). The proper fix is a separate PR: move the rule and its tests into `platform/keycloak`, then delete the phantom. - **The catalog tag can drift.** Nothing ties `catalog/keycloak.yaml`'s documentation tag to the operator URLs. It must be updated by hand after an operator bump, and the README says so. - **Concurrent branches** `fix/renovate-hold-auth-and-self` and `feat/pin-pihole-jackett-plex` also edit `renovate.json5`. Neither had merged when I checked. A rebase may be needed.