From 8ed4e30201783ab5f572feb05a09ae8ae32b988d Mon Sep 17 00:00:00 2001 From: Keith Jackson Date: Fri, 30 Oct 2026 03:06:56 -0500 Subject: [PATCH] docs(decisions): handoff for feat/renovate-track-keycloak-operator Co-Authored-By: Claude Opus 5.5 --- ...-10-30-renovate-track-keycloak-operator.md | 113 ++++++++++++++++++ 1 file changed, 113 insertions(+) create mode 100644 decisions/2026-10-30-renovate-track-keycloak-operator.md diff --git a/decisions/2026-10-30-renovate-track-keycloak-operator.md b/decisions/2026-10-30-renovate-track-keycloak-operator.md new file mode 100644 index 0000000..822ea18 --- /dev/null +++ b/decisions/2026-10-30-renovate-track-keycloak-operator.md @@ -0,0 +1,113 @@ +--- +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.