Compare commits
10
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
35ba875380 | ||
|
|
1a33814522 | ||
|
|
4a3f6dee59 | ||
|
|
759a0e51c8 | ||
|
|
ea30ba876c | ||
|
|
e8d26669a4 | ||
|
|
048254bfaf | ||
|
|
dd94101647 | ||
|
|
8a9baa92bc | ||
|
|
0605ecf593 |
No files matched your search
@@ -78,7 +78,7 @@ spec:
|
|||||||
spec:
|
spec:
|
||||||
containers:
|
containers:
|
||||||
- name: gitea
|
- name: gitea
|
||||||
image: gitea/gitea:1.27.3
|
image: gitea/gitea:28.3.0
|
||||||
ports:
|
ports:
|
||||||
- containerPort: 3000
|
- containerPort: 3000
|
||||||
env:
|
env:
|
||||||
@@ -89,14 +89,18 @@ spec:
|
|||||||
secretKeyRef:
|
secretKeyRef:
|
||||||
name: gitea-bootstrap
|
name: gitea-bootstrap
|
||||||
key: secret-key
|
key: secret-key
|
||||||
- name: GITEA__server__DOMAIN
|
|
||||||
value: gitea.${BASE_DOMAIN}
|
|
||||||
- name: GITEA__server__ROOT_URL
|
- name: GITEA__server__ROOT_URL
|
||||||
value: https://gitea.${BASE_DOMAIN}/
|
value: https://gitea.${BASE_DOMAIN}/
|
||||||
- name: GITEA__actions__ENABLED
|
- name: GITEA__actions__ENABLED
|
||||||
value: 'true'
|
value: 'true'
|
||||||
- name: GITEA__actions__DEFAULT_ACTIONS_URL
|
- name: GITEA__actions__DEFAULT_ACTIONS_URL
|
||||||
value: github
|
value: github
|
||||||
|
- name: GITEA__actions__RUN_RETENTION_DAYS
|
||||||
|
value: '400'
|
||||||
|
- name: GITEA__security__EGRESS_MODE
|
||||||
|
value: lax
|
||||||
|
- name: GITEA__service__DISABLE_REGISTRATION
|
||||||
|
value: 'true'
|
||||||
- name: GITEA_ADMIN_USERNAME
|
- name: GITEA_ADMIN_USERNAME
|
||||||
valueFrom:
|
valueFrom:
|
||||||
secretKeyRef:
|
secretKeyRef:
|
||||||
|
|||||||
+39
-5
@@ -10,7 +10,7 @@
|
|||||||
name: gitea
|
name: gitea
|
||||||
kind: container
|
kind: container
|
||||||
source:
|
source:
|
||||||
image: gitea/gitea:1.27.3 # pinned recent stable tag (R-UPD-01); update.range governs advancement
|
image: gitea/gitea:28.3.0 # pinned recent stable tag (R-UPD-01); update.range governs advancement
|
||||||
# Headless-install bootstrap (formerly apps/gitea-bootstrap-patch.yaml; now first-class via the
|
# Headless-install bootstrap (formerly apps/gitea-bootstrap-patch.yaml; now first-class via the
|
||||||
# ServiceDefinition env/envFrom/lifecycle fields, schema >=1.2.0). Gitea must come up INSTALLED
|
# ServiceDefinition env/envFrom/lifecycle fields, schema >=1.2.0). Gitea must come up INSTALLED
|
||||||
# without the interactive web wizard and with a FIXED, persistent SECRET_KEY, so all of this is
|
# without the interactive web wizard and with a FIXED, persistent SECRET_KEY, so all of this is
|
||||||
@@ -23,14 +23,43 @@ env:
|
|||||||
secretKeyRef:
|
secretKeyRef:
|
||||||
name: gitea-bootstrap
|
name: gitea-bootstrap
|
||||||
key: secret-key
|
key: secret-key
|
||||||
- name: GITEA__server__DOMAIN # correct public clone/redirect URLs for the TLS-terminated host
|
# ROOT_URL is the ONLY source of the instance domain since Gitea 28 (go-gitea/gitea#39400): [server]
|
||||||
value: gitea.${BASE_DOMAIN}
|
# DOMAIN is no longer read, and the SSH domain defaults from ROOT_URL too. GITEA__server__DOMAIN was
|
||||||
|
# removed in the 28 upgrade; the stale DOMAIN line environment-to-ini already wrote into app.ini is
|
||||||
|
# inert. Correct public clone/redirect URLs for the TLS-terminated host come from this one value.
|
||||||
- name: GITEA__server__ROOT_URL
|
- name: GITEA__server__ROOT_URL
|
||||||
value: https://gitea.${BASE_DOMAIN}/
|
value: https://gitea.${BASE_DOMAIN}/
|
||||||
- name: GITEA__actions__ENABLED # Phase 6 CI/CD: declared (not implicit upstream default) so the
|
- name: GITEA__actions__ENABLED # Phase 6 CI/CD: declared (not implicit upstream default) so the
|
||||||
value: "true" # in-cluster act_runner (ns gitea-actions) registers against it
|
value: "true" # in-cluster act_runner (ns gitea-actions) registers against it
|
||||||
- name: GITEA__actions__DEFAULT_ACTIONS_URL # resolve `uses: <org>/<action>@ref` from github.com (cluster
|
- name: GITEA__actions__DEFAULT_ACTIONS_URL # resolve `uses: <org>/<action>@ref` from github.com (cluster
|
||||||
value: github # has internet egress; not air-gapped)
|
value: github # has internet egress; not air-gapped)
|
||||||
|
# Gitea 28 added run retention (go-gitea/gitea#38855): completed runs, with their jobs, logs and
|
||||||
|
# artifacts, are deleted by the cleanup_action_runs cron after RUN_RETENTION_DAYS. Upstream's default
|
||||||
|
# (400) is kept but DECLARED, so the deletion horizon is visible here rather than implicit. The spec
|
||||||
|
# repo states no CI-history retention requirement; 0 would mean keep forever.
|
||||||
|
- name: GITEA__actions__RUN_RETENTION_DAYS
|
||||||
|
value: "400"
|
||||||
|
# Gitea 28 (go-gitea/gitea#39426) puts webhooks, the OAuth2 avatar fetch and OpenID 2.0 behind an
|
||||||
|
# egress policy: in the default lax mode, PUBLIC targets are allowed and private/loopback/CGNAT
|
||||||
|
# targets need an IP or CIDR entry in [security] ALLOWED_HOST_LIST (a hostname entry is NOT enough
|
||||||
|
# for a private address). DECLARED lax with an EMPTY allow list, deliberately:
|
||||||
|
# * the Keycloak OIDC login source is NOT gated by it. In 28.3.0 the openidConnect provider is
|
||||||
|
# goth's, built with its default HTTP client (services/auth/source/oauth2/providers_openid.go);
|
||||||
|
# egress.NewSecurityPolicy is used only by routers/web/auth/oauth.go (avatar fetch, and
|
||||||
|
# [oauth2_client] UPDATE_AVATAR is off here) and modules/auth/openid (OpenID 2.0, unused).
|
||||||
|
# So keycloak.<domain> resolving to the private Gateway address in-cluster does not matter.
|
||||||
|
# * this instance has no webhooks and no mirrors (measured 2026-11-07), and 1.27 already refused
|
||||||
|
# private webhook targets by default, so nothing that worked before is newly denied.
|
||||||
|
# Adding an entry here would only WIDEN the webhook SSRF surface. Add the narrowest CIDR (e.g. the
|
||||||
|
# Gateway address /32) only if a real private webhook target appears.
|
||||||
|
- name: GITEA__security__EGRESS_MODE
|
||||||
|
value: lax
|
||||||
|
# Gitea 28 (go-gitea/gitea#39400) disables LOCAL self-registration unless DISABLE_REGISTRATION is
|
||||||
|
# explicitly false; 1.27 defaulted it open. Declared true: accounts arrive via Keycloak only.
|
||||||
|
# OAuth2 auto-registration below is unaffected — routers/web/auth/oauth.go gates it on
|
||||||
|
# ALLOW_ONLY_INTERNAL_REGISTRATION and ENABLE_AUTO_REGISTRATION, not on DISABLE_REGISTRATION.
|
||||||
|
- name: GITEA__service__DISABLE_REGISTRATION
|
||||||
|
value: "true"
|
||||||
- name: GITEA_ADMIN_USERNAME # break-glass local admin creds (consumed by the postStart hook)
|
- name: GITEA_ADMIN_USERNAME # break-glass local admin creds (consumed by the postStart hook)
|
||||||
valueFrom:
|
valueFrom:
|
||||||
secretKeyRef:
|
secretKeyRef:
|
||||||
@@ -233,9 +262,14 @@ auth:
|
|||||||
# recovery tools (docs/15 "Availability caveat"). No secret is embedded here.
|
# recovery tools (docs/15 "Availability caveat"). No secret is embedded here.
|
||||||
update:
|
update:
|
||||||
channel: stable # R-UPD-01
|
channel: stable # R-UPD-01
|
||||||
range: ">=1.22.0 <2.0.0" # stay on the 1.x stable line
|
range: ">=28.0.0 <29.0.0" # Gitea dropped the "1." prefix at 28.0.0 (successor of 1.27); stay on 28.x
|
||||||
cadence: "0 4 * * *"
|
cadence: "0 4 * * *"
|
||||||
autoRollbackOnFailedHealth: true # R-UPD-03
|
# R-UPD-03. INERT FOR THIS SERVICE, and it must stay that way: this flag renders only an annotation
|
||||||
|
# on a Flux ImagePolicy, and apps/ is rendered --no-image-automation (ci/render-golden.sh), so no
|
||||||
|
# ImagePolicy exists and the Deployment (strategy Recreate) is never auto-reverted. That matters
|
||||||
|
# because a Gitea upgrade (1.27 -> 28 included) MIGRATES THE SQLITE DB ONE-WAY: an image revert onto
|
||||||
|
# a migrated DB is not a rollback. Roll Gitea back by RESTORING THE DATA PVC from its pre-upgrade backup.
|
||||||
|
autoRollbackOnFailedHealth: true
|
||||||
observability:
|
observability:
|
||||||
metrics: true
|
metrics: true
|
||||||
slis:
|
slis:
|
||||||
|
|||||||
@@ -0,0 +1,62 @@
|
|||||||
|
---
|
||||||
|
date: 2026-11-07
|
||||||
|
branch: feat/gitea-28
|
||||||
|
title: "Gitea 1.27.3 -> 28.3.0 (upstream dropped the 1. prefix)"
|
||||||
|
---
|
||||||
|
|
||||||
|
## What changed
|
||||||
|
|
||||||
|
- `catalog/gitea.yaml`: `source.image` `gitea/gitea:1.27.3` -> `gitea/gitea:28.3.0` (still the
|
||||||
|
newest stable tag on Docker Hub and GitHub on 2026-11-07: 28.0.0, 28.1.0, 28.2.0, 28.3.0).
|
||||||
|
`update.range` `">=1.22.0 <2.0.0"` -> `">=28.0.0 <29.0.0"`.
|
||||||
|
- 28.x breaking changes handled in the env block:
|
||||||
|
- `GITEA__server__DOMAIN` **removed**: 28 no longer reads `[server] DOMAIN` (go-gitea/gitea#39400);
|
||||||
|
`ROOT_URL` was already set and is now the only source of the domain. Nothing else in the repo
|
||||||
|
reads the variable (the bootstrap script sets ROOT_URL/SSH_PORT only).
|
||||||
|
- `GITEA__actions__RUN_RETENTION_DAYS=400` **declared** (go-gitea/gitea#38855 added the 400-day
|
||||||
|
default; the spec repo states no CI-history retention requirement).
|
||||||
|
- `GITEA__security__EGRESS_MODE=lax` **declared, with no allow list.** Read from the 28.3.0
|
||||||
|
source: the Keycloak OIDC login source does NOT go through the new egress policy (goth's
|
||||||
|
openidConnect provider, default HTTP client). The policy covers webhooks, the OAuth2 avatar
|
||||||
|
fetch (`UPDATE_AVATAR` is off) and OpenID 2.0. A hostname entry would not even help for a
|
||||||
|
private address — `WithLocalNeedsIPAllow` requires an IP/CIDR entry. So the brief's
|
||||||
|
"Keycloak SSO breaks" premise does not hold for 28.3.0, and an allow-list entry would only
|
||||||
|
widen webhook SSRF reach.
|
||||||
|
- `GITEA__service__DISABLE_REGISTRATION=true` **declared** (28's new default; 1.27 defaulted it
|
||||||
|
open). OAuth2 auto-registration is gated separately and keeps working.
|
||||||
|
- `update.autoRollbackOnFailedHealth`: kept `true`, commented as **inert**. It renders only an
|
||||||
|
annotation on a Flux ImagePolicy, and `apps/` is rendered `--no-image-automation`, so no
|
||||||
|
ImagePolicy exists. The Deployment is `Recreate` with no probes, so nothing reverts the image
|
||||||
|
automatically. Rollback = restore the PVC, never an image revert onto the migrated DB.
|
||||||
|
- `apps/wave-0/gitea.yaml` re-rendered with the canonical command
|
||||||
|
(`render.py --all catalog -o apps --wave-layout --no-image-automation`, in the toolbox). Only
|
||||||
|
gitea changed.
|
||||||
|
- `renovate.json5`: gitea `allowedVersions` `<2.0.0` -> `<29.0.0`; the description no longer calls
|
||||||
|
28.x a stale tag.
|
||||||
|
|
||||||
|
## What I verified
|
||||||
|
|
||||||
|
- Docker Hub tag list and GitHub tags: 28.3.0 is the newest 28.x stable tag.
|
||||||
|
- Read the v28.3.0 source: `modules/egress/policy/policy.go`, `modules/egress/policies.go`, every
|
||||||
|
`egress.New*` caller, `services/auth/source/oauth2/providers_openid.go`,
|
||||||
|
`modules/setting/{security,actions,service}.go`, `routers/web/auth/{oauth,linkaccount}.go`,
|
||||||
|
`cmd/admin_user_list.go`, `cmd/admin_auth*.go`, the Dockerfile, and `docker/root` (the s6 setup
|
||||||
|
still runs `environment-to-ini`). The postStart hook's CLI is unchanged in 28.3.0: `admin user
|
||||||
|
list --admin` still has Username in column 2, `admin auth list` still has ID/Name in columns 1/2,
|
||||||
|
`add-oauth`/`update-oauth` keep the same flags, and curl and su-exec are still in the image.
|
||||||
|
- Live Gitea (API, 2026-11-07): version 1.27.3. Zero repo, org and system webhooks, no mirrors.
|
||||||
|
- `renovate-config-validator` at 44.167.0 (the tag renovate.yaml pins): "Config validated
|
||||||
|
successfully".
|
||||||
|
- Ran the toolbox gate on the committed tree (numbers are in the PR).
|
||||||
|
|
||||||
|
## What I did NOT verify
|
||||||
|
|
||||||
|
- **The upgrade itself.** Nothing booted 28.3.0 against a copy of the production DB. Migration
|
||||||
|
time, migration success and the post-upgrade SSO login are all UNVERIFIED, and the PR's
|
||||||
|
post-merge checklist covers them.
|
||||||
|
- **The age of the latest gitea-data Longhorn/off-cluster backup.** The cluster read failed from
|
||||||
|
the worktree (no k3s-prod context with the kubeconfig available), so the operator must confirm
|
||||||
|
it before merging.
|
||||||
|
- How the Envoy Gateway handles the new `/-/ws` WebSocket. If upgrades are not forwarded, live
|
||||||
|
notifications fall back to polling (not breaking). UNVERIFIED.
|
||||||
|
- Whether the L2/L3 conformance suite passes. It does not run in CI.
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
---
|
||||||
|
date: 2026-11-08
|
||||||
|
branch: fix/keycloak-operator-crds
|
||||||
|
title: "Keycloak 26.8.2 operator CrashLoop: install its two new CRDs, raise its memory"
|
||||||
|
---
|
||||||
|
|
||||||
|
## What changed
|
||||||
|
|
||||||
|
**Incident.** PR #121 (Renovate) bumped `platform/keycloak/kustomization.yaml`'s three
|
||||||
|
`keycloak-k8s-resources` URLs 26.0.6 -> 26.8.2. Upstream `kubernetes/` at 26.8.2 holds **four**
|
||||||
|
CRDs, not two (GitHub contents API, `?ref=26.8.2`):
|
||||||
|
|
||||||
|
| file | at 26.0.6 | at 26.8.2 |
|
||||||
|
|---|---|---|
|
||||||
|
| `keycloaks.k8s.keycloak.org-v1.yml` | yes | yes |
|
||||||
|
| `keycloakrealmimports.k8s.keycloak.org-v1.yml` | yes | yes |
|
||||||
|
| `keycloakoidcclients.k8s.keycloak.org-v1.yml` | — | **new** (v2alpha1) |
|
||||||
|
| `keycloaksamlclients.k8s.keycloak.org-v1.yml` | — | **new** (v2alpha1) |
|
||||||
|
| `kubernetes.yml` | yes | yes (now carries RBAC for both new kinds) |
|
||||||
|
|
||||||
|
(`cluster-wide/` is a separate variant; upstream's own `kubernetes/kustomization.yml` lists exactly
|
||||||
|
the five files above.) The 26.8.2 operator starts an informer for every controller it has and dies
|
||||||
|
on the first missing kind: `Couldn't start informer for keycloaksamlclients.k8s.keycloak.org/v2alpha1
|
||||||
|
... GET .../namespaces/keycloak/keycloaksamlclients ... Not Found`, exit 1, CrashLoopBackOff. Flux
|
||||||
|
`platform` (`wait: true`, 5m) sat in health checks and every dependent Kustomization reported
|
||||||
|
`dependency ... is not ready`. The old 26.0.6 ReplicaSet kept serving (RollingUpdate 25%/25%), so SSO
|
||||||
|
stayed up.
|
||||||
|
|
||||||
|
**Fix.**
|
||||||
|
- `platform/keycloak/kustomization.yaml`: added the two CRD URLs, each with the same
|
||||||
|
`# renovate: datasource=github-tags depName=keycloak/keycloak-k8s-resources` marker (the repo's
|
||||||
|
custom.regex manager matches all five — checked by running its regex over the file). Header
|
||||||
|
comment updated to v26.8.2, plus a warning that Renovate cannot add a CRD a release introduces.
|
||||||
|
- Same file: a `patches:` JSON6902 patch on Deployment `keycloak-operator`: memory limit
|
||||||
|
450Mi -> **768Mi**, request 450Mi -> **512Mi**. 450Mi is upstream's default (unchanged at 26.8.2,
|
||||||
|
so not something the bump changed). Evidence it is too small here: the 26.0.6 pod had **154
|
||||||
|
restarts**, `lastState.terminated.reason: OOMKilled` (exit 137, 2026-11-07T23:09:54Z), and
|
||||||
|
`kubectl top` showed **421Mi / 450Mi**. 26.8.2 runs four controllers instead of two on Quarkus
|
||||||
|
3.40.1.1 (was 3.15.1). No variable in the operator to disable watching unused kinds was used —
|
||||||
|
installing the CRDs is upstream's supported shape.
|
||||||
|
- `platform/keycloak/README.md` "Upgrading Keycloak": the diff-the-CRD-list step; "three URLs" ->
|
||||||
|
"the release URLs". `renovate.json5`: same wording fix in a comment only.
|
||||||
|
|
||||||
|
## What I verified
|
||||||
|
|
||||||
|
- Upstream file lists at 26.0.6 and 26.8.2 (GitHub API) and `diff` of `kubernetes.yml` between the
|
||||||
|
two: the new kinds' ClusterRoles + RoleBindings ship in it, and the Deployment's resources are
|
||||||
|
still 450Mi/450Mi.
|
||||||
|
- Live, read-only (`kubectl --context k3s-prod`, 2026-11-08 ~05:57Z): only `keycloaks` and
|
||||||
|
`keycloakrealmimports` CRDs installed; Deployment strategy RollingUpdate maxSurge/maxUnavailable
|
||||||
|
25%; RS `keycloak-operator-58dff6c4` (26.8.2) 0/1 CrashLoopBackOff, RS `78dbb75f64` (26.0.6) 1/1;
|
||||||
|
operator log is the informer error above; Flux `platform` `Healthy=Unknown` "Running health
|
||||||
|
checks for revision main@sha1:e8d2666"; no NetworkPolicy and no CiliumNetworkPolicy in ns
|
||||||
|
`keycloak`.
|
||||||
|
- No sensitive path touched (`infra/gitea/approval-policy.json`), so this should merge on green.
|
||||||
|
- Gate: see the PR body for the exact rc and stages of the run against the final commit.
|
||||||
|
|
||||||
|
## What I did NOT verify
|
||||||
|
|
||||||
|
- **That the 26.8.2 operator becomes Ready after this merges.** Only the two missing kinds were
|
||||||
|
observed failing; another start-up failure behind them is possible.
|
||||||
|
- **That 768Mi is enough** — no 26.8.2 memory measurement exists yet; re-check `kubectl top` after
|
||||||
|
a week.
|
||||||
|
- **The Keycloak server upgrade 26.0.6 -> 26.8.2 itself.** Once the operator runs it will roll
|
||||||
|
keycloak-0/1 and Keycloak migrates the DB schema. The CR's `spec.update.strategy` defaults to
|
||||||
|
`RecreateOnImageChange` — expect a **full SSO outage** while both pods recreate, not a rolling
|
||||||
|
one. Back up / confirm the latest CNPG backup first if that matters.
|
||||||
|
- **The 26.8.2 Keycloak CRD's `spec.networkPolicy.enabled` defaults to `true`.** The operator will
|
||||||
|
create a NetworkPolicy selecting the Keycloak pods (first policy in that namespace). With no
|
||||||
|
`from` lists set it allows all sources on the HTTP/management ports, so Gateway, ServiceMonitor
|
||||||
|
and the reconciler Jobs should still reach it — but this was read from the CRD text, not observed.
|
||||||
|
- **The passkey reconciler's 26.0.6 Admin API assumptions against 26.8.2** (README "Upgrading
|
||||||
|
Keycloak" list) — not re-checked here; PR #121 merged without it.
|
||||||
|
- `catalog/keycloak.yaml`'s documentation-only image tag was not updated.
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
---
|
||||||
|
date: 2026-11-08
|
||||||
|
branch: fix/keycloak-servicemonitor-owner
|
||||||
|
title: One owner for the keycloak ServiceMonitor — disable the 26.8 operator's (409 loop stalled platform-config)
|
||||||
|
---
|
||||||
|
|
||||||
|
## What changed
|
||||||
|
|
||||||
|
- `platform/keycloak/keycloak-instance.yaml`: added `spec.serviceMonitor.enabled: false`.
|
||||||
|
The 26.x Keycloak operator creates its own ServiceMonitor, named after the CR (`keycloak/keycloak`),
|
||||||
|
whenever `metrics-enabled=true` and the ServiceMonitor CRD exists. `serviceMonitor.enabled` defaults
|
||||||
|
to `true` in the 26.8.2 CRD, both in v2alpha1 and in v2beta1 (v2beta1 is now the storage version).
|
||||||
|
That is the same name and namespace as the repo's Flux-owned `platform/keycloak/servicemonitor.yaml`.
|
||||||
|
After the 26.0.6 -> 26.8.2 bump (#121/#144) the operator's PATCH against it returned
|
||||||
|
`409 Conflict` and it ran out of retries. The CR then sat at `Ready=False`/`HasErrors=True`, and
|
||||||
|
`platform-config` (`wait: true`, which health-checks every object it applies, this CR included)
|
||||||
|
blocked everything that `dependsOn` it: velero, apps-vms, apps-wave-0 (and so waves 1/2 and
|
||||||
|
pm-agent), homepage, oauth2-proxy, headlamp, conformance, vpn-ingress, crafty, runbooks.
|
||||||
|
- `platform/keycloak/servicemonitor.yaml`: a comment saying it is the sole owner.
|
||||||
|
- **Why ours is kept:** these are written against it: the L0 `servicemonitor-ports-resolve`
|
||||||
|
allow-list entry `platform/keycloak/servicemonitor.yaml:keycloak`, the L2 `keycloak-monitored`
|
||||||
|
chainsaw test, the README's ownership table, and the dashboard's `up{namespace="keycloak"}`.
|
||||||
|
The operator's version (upstream `KeycloakServiceMonitorDependentResource` @26.8.2) scrapes the same
|
||||||
|
target: port name `management` (9000 on `keycloak-service`), path `/metrics`, interval 30s,
|
||||||
|
selector = the operator's instance labels. It offers nothing ours lacks, so having two owners
|
||||||
|
would add a fight and gain nothing.
|
||||||
|
|
||||||
|
## What I verified
|
||||||
|
|
||||||
|
- Live, read-only, `k3s-prod` ~06:25Z: the CRD schema field `spec.serviceMonitor.enabled`
|
||||||
|
(`default: true`) exists in both served versions. The live `keycloak` ServiceMonitor has exactly
|
||||||
|
**one** managedFields manager, `kustomize-controller` (Apply, 2026-09-05), **no ownerReferences**,
|
||||||
|
and labels `kustomize.toolkit.fluxcd.io/name=platform-config`, `release=kube-prometheus-stack`. So
|
||||||
|
the operator's apply never landed, and nothing on the object ties it to the Keycloak CR.
|
||||||
|
- The ServiceMonitor will not be deleted when the operator is switched off: the operator's dependent
|
||||||
|
informer is filtered on `Constants.DEFAULT_LABELS_AS_STRING`
|
||||||
|
(`app.kubernetes.io/managed-by=keycloak-operator`). Our object does not carry that label and has
|
||||||
|
no ownerReference, so the operator cannot see it as its secondary and cannot delete or GC it.
|
||||||
|
This conclusion comes from reading the code. It was not observed.
|
||||||
|
- The other operator dependents in ns `keycloak` do not collide with anything: `keycloak-service`,
|
||||||
|
`keycloak-discovery`, the StatefulSet `keycloak`, `keycloak-initial-admin`, and
|
||||||
|
`keycloak-network-policy` (owner Keycloak, no Flux label). The repo declares none of these names.
|
||||||
|
It only *references* `keycloak-service` by name (HTTPRoute, reconcilers). There are no PDBs in the
|
||||||
|
namespace, and the Ingress is already `enabled: false`. The only NetworkPolicy in the namespace is
|
||||||
|
the operator's. The repo has no Keycloak NetworkPolicy, and no Kyverno-generated default-deny is
|
||||||
|
present there. NetworkPolicies add up, so the operator's allow-policy cannot fight another owner.
|
||||||
|
Left at its default.
|
||||||
|
- Local gate: see the PR body. It records the rc and stages.
|
||||||
|
|
||||||
|
## What I did NOT verify
|
||||||
|
|
||||||
|
- That the CR reaches `Ready=True`/`HasErrors=False` after merge. The status also says
|
||||||
|
"Performing Keycloak upgrade, scaling down the deployment". I am inferring that this half clears
|
||||||
|
once the workflow stops erroring. It is not proven.
|
||||||
|
- That the operator stops issuing the PATCH once `enabled: false` is applied. Watch the operator
|
||||||
|
logs for 409.
|
||||||
|
- That `platform-config` and its downstream Kustomizations go Ready on the new revision, and that
|
||||||
|
Prometheus keeps exactly one `serviceMonitor/keycloak/keycloak/0` pool with targets up.
|
||||||
|
- The operator's actual label set for `DEFAULT_LABELS_AS_STRING`. I took it from the observed labels
|
||||||
|
on operator-created objects, not from `Constants.java`.
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
---
|
||||||
|
date: 2026-11-08
|
||||||
|
branch: chore/renovate-config-cleanup
|
||||||
|
title: Renovate config — hand-migrate the docker key, track gitea/runner, keep HelmRelease range lower bounds
|
||||||
|
---
|
||||||
|
|
||||||
|
## What changed
|
||||||
|
|
||||||
|
- **`renovate.json5`: the deprecated top-level `"docker": {"enabled": true}` is hand-migrated** to
|
||||||
|
`packageRules[0] = {"matchCategories": ["docker"], "enabled": true}`, the exact shape Renovate's
|
||||||
|
own migration produced on `origin/renovate/migrate-config` (PR #139). Its comment moved with it.
|
||||||
|
This replaces #139, which was closed because its rewrite deleted every comment in the file. No
|
||||||
|
other comment was removed or reflowed (diff is additions plus the moved block).
|
||||||
|
- **`gitea/runner` added to the blast-radius rule** beside `gitea/act_runner`, with a comment:
|
||||||
|
act_runner was renamed upstream to gitea/runner after 0.6.1; Renovate does not follow renames,
|
||||||
|
so the actual image migration is a separate manual PR.
|
||||||
|
- **Two new packageRules for flux/helm `>=A <B` ranges:** `rangeStrategy: "bump"`, plus
|
||||||
|
`matchUpdateTypes: ["bump"] -> enabled: false`, so in-range releases (which Flux adopts on its
|
||||||
|
own) do not become PRs. Cause of the bug, from the 44.167.0 source: rangeStrategy is unset
|
||||||
|
(`auto`); the flux manager has no `getRangeStrategy`/`updateLockedDependency`, so
|
||||||
|
`lib/modules/manager/index.ts getRangeStrategy` resolves `auto` to `replace`; helm versioning is
|
||||||
|
the npm api verbatim, and npm `getNewValue` under `replace` turns `>=1.12.0 <1.13.0` into
|
||||||
|
`<1.14.0` (what PRs #131, #130 and #115 did).
|
||||||
|
- **`platform/headlamp/helmrelease.yaml`: `version: "<0.47.0"` -> `">=0.46.0 <0.47.0"`**, comment
|
||||||
|
refreshed. The live chart is 0.46.0, so the set of installable charts is unchanged.
|
||||||
|
|
||||||
|
## What I verified
|
||||||
|
|
||||||
|
- **Semantic diff of #139:** I parsed `renovate.json5` on `origin/renovate/migrate-config` and on its
|
||||||
|
parent `5b484f6` with json5 (the copy inside the renovate image). With `docker` removed from the
|
||||||
|
parent and `packageRules[0]` removed from the migrated file, the two are identical. So the only
|
||||||
|
change was `{"matchCategories":["docker"],"enabled":true}`, inserted at packageRules[0].
|
||||||
|
- **Validator, `ghcr.io/renovatebot/renovate:44.167.0`** (the tag that `.gitea/workflows/renovate.yaml`
|
||||||
|
pins as `container.image`): `renovate-config-validator` gives `Config validated successfully`
|
||||||
|
(rc 0), and so does `--strict` (rc 0), both as a file path and in repo mode. **`--strict` on
|
||||||
|
origin/main's file gives rc 1** and prints the `docker` migration diff. Nothing is left to migrate.
|
||||||
|
- **`getNewValue` called in the 44.167.0 image** (helm versioning), currentValue
|
||||||
|
`>=1.12.0 <1.13.0`, newVersion 1.13.0: replace `<1.14.0`, widen `>=1.12.0 <1.14.0`,
|
||||||
|
bump `>=1.13.0 <1.14.0`, pin/update-lockfile `<1.14.0`. On `<0.47.0`, bump returns the value
|
||||||
|
unchanged, so it would never propose anything for that range. That is why the rule is scoped to
|
||||||
|
the `>=A <B` shape.
|
||||||
|
- **Local dry run** (`--platform=local`, `RENOVATE_DRY_RUN=lookup`, 44.167.0, live chart repos)
|
||||||
|
against a fixture repo of four HelmReleases under `platform/`:
|
||||||
|
- With this branch's config: `No config migration necessary`. longhorn `>=1.9.0 <1.10.0` ->
|
||||||
|
`>=1.13.0 <1.14.0` (minor). kube-prometheus-stack `>=88.0.0 <89.0.0` -> `>=93.1.0 <94.0.0`
|
||||||
|
(major). The in-range bump `>=88.6.5 <89.0.0` was dropped (`Filtered out 1 disabled update(s)`).
|
||||||
|
headlamp `>=0.46.0 <0.47.0` and `<0.47.0`: no update.
|
||||||
|
- Control run with origin/main's config: `Config migration necessary`, longhorn -> `<1.14.0`,
|
||||||
|
kube-prometheus-stack -> `<94.0.0`. This reproduces the reported bug.
|
||||||
|
- Live headlamp HelmRelease (read-only kubectl, k3s-prod): spec `<0.47.0`, history chartVersion
|
||||||
|
`0.46.0`, Ready `True`.
|
||||||
|
|
||||||
|
## What I did NOT verify
|
||||||
|
|
||||||
|
- **I have not seen a real scheduled Renovate run against this repo with this config.** The claim that
|
||||||
|
Renovate stops reopening the migrate-config PR rests on the validator and on `No config migration
|
||||||
|
necessary` in the local dry run, not on the Gitea bot. After merge, watch the next run, and if
|
||||||
|
`renovate/migrate-config` is still on the remote, delete it.
|
||||||
|
- **Under bump, the new lower bound is the release Renovate proposes**, e.g. `>=1.13.2 <1.14.0`, not
|
||||||
|
`>=1.13.0`. Flux still installs the newest release in the window. Ranges with other shapes keep the
|
||||||
|
default `replace`: `3.9.x`, `~`, ceiling-only, and exact pins.
|
||||||
|
- **The open PRs #131, #115 and any others still carry unbounded `<X` values.** They were generated
|
||||||
|
under the old strategy, and Renovate should rebase or regenerate them under bump on its next run.
|
||||||
|
I have not checked that it does.
|
||||||
|
- The gitea/runner rename itself (a new image reference and any config changes) is not done here.
|
||||||
@@ -40,7 +40,11 @@ spec:
|
|||||||
# values (-session-ttl + the -oidc-* args from the externalSecret; useCookie stays default-false
|
# values (-session-ttl + the -oidc-* args from the externalSecret; useCookie stays default-false
|
||||||
# so `-oidc-use-cookie` is NOT emitted) is supported by the v0.41.0 backend. Renovate maintains
|
# so `-oidc-use-cookie` is NOT emitted) is supported by the v0.41.0 backend. Renovate maintains
|
||||||
# within 0.41.x; the appVersion continues to pin the Headlamp image.
|
# within 0.41.x; the appVersion continues to pin the Headlamp image.
|
||||||
version: "<0.47.0"
|
# 2026-11-08: NOW 0.46.x. PR #130 (Renovate, default rangeStrategy `replace`) rewrote
|
||||||
|
# `>=0.41.0 <0.42.0` to the unbounded-below `<0.47.0`; the lower bound is restored here at the
|
||||||
|
# chart that was live when it merged (0.46.0). renovate.json5 now bumps `>=A <B` ranges as a window,
|
||||||
|
# so the next widen PR proposes `>=0.47.x <0.48.0` rather than `<0.48.0`.
|
||||||
|
version: ">=0.46.0 <0.47.0"
|
||||||
sourceRef:
|
sourceRef:
|
||||||
kind: HelmRepository
|
kind: HelmRepository
|
||||||
name: headlamp
|
name: headlamp
|
||||||
|
|||||||
@@ -367,9 +367,17 @@ is involved — so step 2 failing with no log entry is the expected signature of
|
|||||||
The `Keycloak` CR has no `spec.image`, so the server image is whatever the operator's
|
The `Keycloak` CR has no `spec.image`, so the server image is whatever the operator's
|
||||||
`RELATED_IMAGE_KEYCLOAK` names — the same tag as the operator (prod, 2026-10-30: operator
|
`RELATED_IMAGE_KEYCLOAK` names — the same tag as the operator (prod, 2026-10-30: operator
|
||||||
`26.0.6`, `keycloak-0/1` on `quay.io/keycloak/keycloak:26.0.6`). Operator, CRDs and server are
|
`26.0.6`, `keycloak-0/1` on `quay.io/keycloak/keycloak:26.0.6`). Operator, CRDs and server are
|
||||||
released in lockstep from `keycloak/keycloak-k8s-resources`, and the three URLs in
|
released in lockstep from `keycloak/keycloak-k8s-resources`, and the release URLs in
|
||||||
`kustomization.yaml` are the one knob. Renovate raises them as ONE PR (`keycloak-operator` group,
|
`kustomization.yaml` are the one knob. Renovate raises them as ONE PR (`keycloak-operator` group,
|
||||||
labels `needs-review` + `blast-radius`), **never auto-merged**; majors wait for a dashboard tick.
|
labels `needs-review` + `blast-radius`), **never auto-merged**; majors wait for a dashboard tick.
|
||||||
|
|
||||||
|
**Renovate bumps the URLs that exist; it cannot add a CRD a release introduced.** Before merging a
|
||||||
|
bump, diff `kubernetes/` (and its `kustomization.yml`) at the new tag against the URL list. 26.0.6
|
||||||
|
shipped 2 CRDs, 26.8.2 ships 4 (`keycloakoidcclients`, `keycloaksamlclients` added); PR #121 bumped
|
||||||
|
only the existing URLs and the 26.8.2 operator CrashLooped on the missing `keycloaksamlclients`
|
||||||
|
informer, stalling Flux `platform` and every tier below it (2026-11-08,
|
||||||
|
`decisions/2026-11-08-keycloak-operator-crds.md`). The operator's memory is also patched in
|
||||||
|
`kustomization.yaml` (upstream's 450Mi OOMKilled 26.0.6 here); keep the patch across bumps.
|
||||||
`catalog/keycloak.yaml`'s image tag is documentation only (its render deploys nothing) and
|
`catalog/keycloak.yaml`'s image tag is documentation only (its render deploys nothing) and
|
||||||
Renovate is disabled for it — update it by hand to match after an operator bump merges.
|
Renovate is disabled for it — update it by hand to match after an operator bump merges.
|
||||||
|
|
||||||
|
|||||||
@@ -38,6 +38,20 @@ spec:
|
|||||||
ingress:
|
ingress:
|
||||||
enabled: false # exposed via Gateway API HTTPRoute (httproute.yaml), not the
|
enabled: false # exposed via Gateway API HTTPRoute (httproute.yaml), not the
|
||||||
# operator's built-in Ingress
|
# operator's built-in Ingress
|
||||||
|
# --- ONE OWNER for the `keycloak` ServiceMonitor: this repo (servicemonitor.yaml), not the operator.
|
||||||
|
# Since 26.x the operator creates its OWN ServiceMonitor, named after this CR (`keycloak`, i.e.
|
||||||
|
# the SAME name/namespace as ours), whenever metrics-enabled=true and the CRD exists —
|
||||||
|
# spec.serviceMonitor.enabled defaults TRUE. On 2026-11-08, right after the 26.0.6 -> 26.8.2
|
||||||
|
# bump, its server-side apply onto our Flux-owned object failed `409 Conflict`, the operator
|
||||||
|
# exhausted retries, the CR sat Ready=False/HasErrors=True, and `platform-config` (wait: true,
|
||||||
|
# so it health-checks this CR) stalled every Kustomization downstream of it. Ours is kept
|
||||||
|
# because the L0 servicemonitor-ports-resolve and L2 keycloak-monitored conformance tests and
|
||||||
|
# the dashboard's up{namespace="keycloak"} are written against it; the operator's would scrape
|
||||||
|
# the same thing (management port 9000, /metrics, 30s). Do NOT flip this to true without
|
||||||
|
# deleting servicemonitor.yaml in the same change — two owners of one object is a fight
|
||||||
|
# (cf. the default-deny NetworkPolicy drift loop).
|
||||||
|
serviceMonitor:
|
||||||
|
enabled: false
|
||||||
# --- Hardening + observability options (docs/15). Brute-force detection is a REALM setting
|
# --- Hardening + observability options (docs/15). Brute-force detection is a REALM setting
|
||||||
# (bruteForceProtected in the realm import); these enable health + metrics endpoints. ---
|
# (bruteForceProtected in the realm import); these enable health + metrics endpoints. ---
|
||||||
additionalOptions:
|
additionalOptions:
|
||||||
|
|||||||
@@ -20,19 +20,33 @@ kind: Kustomization
|
|||||||
namespace: keycloak
|
namespace: keycloak
|
||||||
resources:
|
resources:
|
||||||
- namespace.yaml
|
- namespace.yaml
|
||||||
# --- Keycloak Operator v26.0.6 (pinned upstream release manifests) ---
|
# --- Keycloak Operator v26.8.2 (pinned upstream release manifests) ---
|
||||||
# The operator, its CRDs and the SERVER IMAGE move in lockstep: the Keycloak CR has no
|
# The operator, its CRDs and the SERVER IMAGE move in lockstep: the Keycloak CR has no
|
||||||
# spec.image, so the operator's RELATED_IMAGE_KEYCLOAK (quay.io/keycloak/keycloak:<same tag>)
|
# spec.image, so the operator's RELATED_IMAGE_KEYCLOAK (quay.io/keycloak/keycloak:<same tag>)
|
||||||
# is what runs. These three URLs are therefore THE Keycloak version. Each carries a Renovate
|
# is what runs. These URLs are therefore THE Keycloak version. Each carries a Renovate
|
||||||
# marker (custom.regex manager, renovate.json5) under ONE depName, so all three bump in ONE
|
# marker (custom.regex manager, renovate.json5) under ONE depName, so all of them bump in ONE
|
||||||
# branch, never auto-merged (packageRule "keycloak-operator"). Before merging a bump, re-check
|
# branch, never auto-merged (packageRule "keycloak-operator"). Before merging a bump, re-check
|
||||||
# passkey-reconciler.yaml's 26.0.6 Admin API assumptions — see README.md "Upgrading Keycloak".
|
# passkey-reconciler.yaml's 26.0.6 Admin API assumptions — see README.md "Upgrading Keycloak".
|
||||||
|
#
|
||||||
|
# THE URL LIST MUST MATCH UPSTREAM'S kubernetes/kustomization.yml AT THE NEW TAG — Renovate only
|
||||||
|
# rewrites the version in the URLs that are here; it cannot add a CRD the new release introduced.
|
||||||
|
# 26.0.6 shipped 2 CRDs; 26.8.2 ships 4 (+ keycloakoidcclients, keycloaksamlclients, both
|
||||||
|
# v2alpha1). PR #121 bumped only the existing three URLs, and the 26.8.2 operator — which starts an
|
||||||
|
# informer for EVERY kind it controls, used or not — CrashLooped on
|
||||||
|
# `Couldn't start informer for keycloaksamlclients.k8s.keycloak.org/v2alpha1 ... Not Found`,
|
||||||
|
# stalling Flux `platform` and everything downstream (2026-11-08). On every bump, diff
|
||||||
|
# https://github.com/keycloak/keycloak-k8s-resources/tree/<tag>/kubernetes
|
||||||
|
# against this list before merging.
|
||||||
# renovate: datasource=github-tags depName=keycloak/keycloak-k8s-resources
|
# renovate: datasource=github-tags depName=keycloak/keycloak-k8s-resources
|
||||||
- https://raw.githubusercontent.com/keycloak/keycloak-k8s-resources/26.0.6/kubernetes/keycloaks.k8s.keycloak.org-v1.yml
|
- https://raw.githubusercontent.com/keycloak/keycloak-k8s-resources/26.8.2/kubernetes/keycloaks.k8s.keycloak.org-v1.yml
|
||||||
# renovate: datasource=github-tags depName=keycloak/keycloak-k8s-resources
|
# renovate: datasource=github-tags depName=keycloak/keycloak-k8s-resources
|
||||||
- https://raw.githubusercontent.com/keycloak/keycloak-k8s-resources/26.0.6/kubernetes/keycloakrealmimports.k8s.keycloak.org-v1.yml
|
- https://raw.githubusercontent.com/keycloak/keycloak-k8s-resources/26.8.2/kubernetes/keycloakrealmimports.k8s.keycloak.org-v1.yml
|
||||||
# renovate: datasource=github-tags depName=keycloak/keycloak-k8s-resources
|
# renovate: datasource=github-tags depName=keycloak/keycloak-k8s-resources
|
||||||
- https://raw.githubusercontent.com/keycloak/keycloak-k8s-resources/26.0.6/kubernetes/kubernetes.yml
|
- https://raw.githubusercontent.com/keycloak/keycloak-k8s-resources/26.8.2/kubernetes/keycloakoidcclients.k8s.keycloak.org-v1.yml
|
||||||
|
# renovate: datasource=github-tags depName=keycloak/keycloak-k8s-resources
|
||||||
|
- https://raw.githubusercontent.com/keycloak/keycloak-k8s-resources/26.8.2/kubernetes/keycloaksamlclients.k8s.keycloak.org-v1.yml
|
||||||
|
# renovate: datasource=github-tags depName=keycloak/keycloak-k8s-resources
|
||||||
|
- https://raw.githubusercontent.com/keycloak/keycloak-k8s-resources/26.8.2/kubernetes/kubernetes.yml
|
||||||
# --- Admin (break-glass) credential — SOPS-encrypted, referenced by name (ADR-0007, R-IDP-04) ---
|
# --- Admin (break-glass) credential — SOPS-encrypted, referenced by name (ADR-0007, R-IDP-04) ---
|
||||||
- keycloak-admin-credentials.sops.yaml
|
- keycloak-admin-credentials.sops.yaml
|
||||||
# --- OIDC client secrets (grafana + oauth2-proxy) the realm import injects via placeholders ---
|
# --- OIDC client secrets (grafana + oauth2-proxy) the realm import injects via placeholders ---
|
||||||
@@ -51,3 +65,23 @@ resources:
|
|||||||
- keycloak-db-backup-credentials-prod.sops.yaml
|
- keycloak-db-backup-credentials-prod.sops.yaml
|
||||||
# --- Grafana dashboard for Keycloak health (plain ConfigMap; R-OBS-03) ---
|
# --- Grafana dashboard for Keycloak health (plain ConfigMap; R-OBS-03) ---
|
||||||
- grafana-dashboard.yaml
|
- grafana-dashboard.yaml
|
||||||
|
# --- Operator memory: upstream ships requests=limits=450Mi, which this cluster outgrew ON 26.0.6 ---
|
||||||
|
# Measured on prod 2026-11-08: the 26.0.6 operator pod had 154 restarts in 64d, lastState
|
||||||
|
# `OOMKilled` (exit 137, 2026-11-07T23:09:54Z), and sat at 421Mi of its 450Mi (94%) in `kubectl top`.
|
||||||
|
# 26.8.2 keeps the same 450Mi default but runs FOUR controllers/informers instead of two (adds
|
||||||
|
# keycloakoidcclient + keycloaksamlclient) on a newer Quarkus (3.15.1 -> 3.40.1.1), so it would
|
||||||
|
# plausibly start above a ceiling 26.0.6 already crossed. Limit 768Mi = ~1.8x the observed 421Mi
|
||||||
|
# working set; request 512Mi = the observed working set plus headroom, so the scheduler reserves what
|
||||||
|
# it actually uses. Revisit with `kubectl top` once 26.8.2 has run for a week. Patched here (not in
|
||||||
|
# the remote kubernetes.yml) so a Renovate bump keeps it.
|
||||||
|
patches:
|
||||||
|
- target:
|
||||||
|
kind: Deployment
|
||||||
|
name: keycloak-operator
|
||||||
|
patch: |-
|
||||||
|
- op: replace
|
||||||
|
path: /spec/template/spec/containers/0/resources/limits/memory
|
||||||
|
value: 768Mi
|
||||||
|
- op: replace
|
||||||
|
path: /spec/template/spec/containers/0/resources/requests/memory
|
||||||
|
value: 512Mi
|
||||||
@@ -8,6 +8,9 @@
|
|||||||
# /metrics) once metrics-enabled/health-enabled are set (see keycloak-instance.yaml additionalOptions).
|
# /metrics) once metrics-enabled/health-enabled are set (see keycloak-instance.yaml additionalOptions).
|
||||||
# We select the operator-created keycloak-service (which selects the Keycloak pods) and scrape the pods'
|
# We select the operator-created keycloak-service (which selects the Keycloak pods) and scrape the pods'
|
||||||
# management port by number via targetPort.
|
# management port by number via targetPort.
|
||||||
|
# SOLE OWNER: the operator's own ServiceMonitor (same name `keycloak`) is switched OFF in
|
||||||
|
# keycloak-instance.yaml (spec.serviceMonitor.enabled: false) — 26.x defaults it ON and its apply
|
||||||
|
# 409-looped against this object on 2026-11-08. Keep exactly one of the two.
|
||||||
# TODO(WS-SEC, bring-up): confirm the management port number/name the operator exposes (9000 default)
|
# TODO(WS-SEC, bring-up): confirm the management port number/name the operator exposes (9000 default)
|
||||||
# and adjust targetPort if the pinned operator changes it. The KeycloakDown / KeycloakDatabaseDown
|
# and adjust targetPort if the pinned operator changes it. The KeycloakDown / KeycloakDatabaseDown
|
||||||
# alerts (platform/monitoring/alerts.yaml) key on kube-state-metrics and fire regardless of this SM.
|
# alerts (platform/monitoring/alerts.yaml) key on kube-state-metrics and fire regardless of this SM.
|
||||||
|
|||||||
+66
-13
@@ -37,6 +37,8 @@
|
|||||||
// renovate-config-validator /tmp/renovate.json5
|
// renovate-config-validator /tmp/renovate.json5
|
||||||
// (Not `--strict`: that also fails on a pending migration of the pre-existing top-level
|
// (Not `--strict`: that also fails on a pending migration of the pre-existing top-level
|
||||||
// `"docker": {"enabled": true}` block at the bottom, which is a warning, not an error.)
|
// `"docker": {"enabled": true}` block at the bottom, which is a warning, not an error.)
|
||||||
|
// RESOLVED 2026-11-08: that block is now hand-migrated to the first packageRule (`matchCategories:
|
||||||
|
// ["docker"]`), the exact shape Renovate's own migration produced, so no migration is pending.
|
||||||
//
|
//
|
||||||
// `regex` vs `custom.regex` in enabledManagers, RESOLVED FROM SOURCE (Renovate 39.264.0 and
|
// `regex` vs `custom.regex` in enabledManagers, RESOLVED FROM SOURCE (Renovate 39.264.0 and
|
||||||
// later): both work. lib/config/migrations/custom/enabled-managers-migration.ts rewrites
|
// later): both work. lib/config/migrations/custom/enabled-managers-migration.ts rewrites
|
||||||
@@ -196,6 +198,22 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"packageRules": [
|
"packageRules": [
|
||||||
|
// The `docker` manager (Dockerfiles, docker-compose) and the `kubernetes` manager above were
|
||||||
|
// BOTH previously `enabled: false`, solely so Renovate would not fight Flux image-automation
|
||||||
|
// over a running tag. That controller was never enabled and never will be here (see _comment),
|
||||||
|
// so the only thing those two flags were achieving was leaving every plain `image:` tag in the
|
||||||
|
// repo unmanaged. `docker` is left out of enabledManagers because this repo contains no
|
||||||
|
// Dockerfile that ships to the cluster; `kubernetes` is now ON and is the one that matters.
|
||||||
|
// MOVED HERE 2026-11-08 from a top-level `"docker": {"enabled": true}` key, which Renovate
|
||||||
|
// deprecated (the top-level language objects became `matchCategories` rules). Hand-migrated to
|
||||||
|
// the exact output of Renovate's own config migration (branch renovate/migrate-config, PR #139:
|
||||||
|
// a json5 semantic diff of that file against its parent shows THIS rule, inserted at
|
||||||
|
// packageRules[0], as the ONLY change). #139 itself was closed unmerged because its rewrite
|
||||||
|
// dropped every comment in this file. With nothing left to migrate, Renovate stops reopening it.
|
||||||
|
{
|
||||||
|
"matchCategories": ["docker"],
|
||||||
|
"enabled": true
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"description": "Auto-merge the allowed low-risk classes on green CI \u2014 no human authoring (R-UPD-02). The Gitea merge gate (make -C ci gate) is the required check.",
|
"description": "Auto-merge the allowed low-risk classes on green CI \u2014 no human authoring (R-UPD-02). The Gitea merge gate (make -C ci gate) is the required check.",
|
||||||
"matchUpdateTypes": [
|
"matchUpdateTypes": [
|
||||||
@@ -252,6 +270,11 @@
|
|||||||
// Keycloak's (the IdP) included, now that imageName is tracked.
|
// Keycloak's (the IdP) included, now that imageName is tracked.
|
||||||
"gitea/gitea",
|
"gitea/gitea",
|
||||||
"gitea/act_runner",
|
"gitea/act_runner",
|
||||||
|
// ADDED 2026-11-08. act_runner was RENAMED upstream to gitea/runner after 0.6.1
|
||||||
|
// (https://gitea.com/gitea/runner/releases). Renovate does not follow a rename — it will
|
||||||
|
// never propose gitea/act_runner -> gitea/runner — so that migration is a manual PR. Listed
|
||||||
|
// now so the new name is blast-radius from its first reference, not after an auto-merge.
|
||||||
|
"gitea/runner",
|
||||||
"ghcr.io/cloudnative-pg/postgresql"
|
"ghcr.io/cloudnative-pg/postgresql"
|
||||||
],
|
],
|
||||||
"automerge": false,
|
"automerge": false,
|
||||||
@@ -264,7 +287,7 @@
|
|||||||
// and the server image are released in lockstep from keycloak/keycloak-k8s-resources, and the
|
// and the server image are released in lockstep from keycloak/keycloak-k8s-resources, and the
|
||||||
// Keycloak CR carries no spec.image, so the operator's RELATED_IMAGE_KEYCLOAK picks the server
|
// Keycloak CR carries no spec.image, so the operator's RELATED_IMAGE_KEYCLOAK picks the server
|
||||||
// (measured on prod 2026-10-30: operator 26.0.6 -> keycloak-0/1 run quay.io/keycloak/keycloak:
|
// (measured on prod 2026-10-30: operator 26.0.6 -> keycloak-0/1 run quay.io/keycloak/keycloak:
|
||||||
// 26.0.6). The three release URLs in platform/keycloak/kustomization.yaml carry `# renovate:`
|
// 26.0.6). The release URLs in platform/keycloak/kustomization.yaml carry `# renovate:`
|
||||||
// markers under this ONE depName, so they move together in one branch (groupName makes that
|
// markers under this ONE depName, so they move together in one branch (groupName makes that
|
||||||
// explicit). NEVER AUTO-MERGED, not even a patch: Keycloak is the IdP that gates SSO for every
|
// explicit). NEVER AUTO-MERGED, not even a patch: Keycloak is the IdP that gates SSO for every
|
||||||
// app AND kubectl OIDC, and platform/keycloak/passkey-reconciler.yaml encodes Keycloak 26.0.6
|
// app AND kubectl OIDC, and platform/keycloak/passkey-reconciler.yaml encodes Keycloak 26.0.6
|
||||||
@@ -316,9 +339,9 @@
|
|||||||
"enabled": false
|
"enabled": false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"description": "ADDED 2026-10-30. MIRRORS catalog/<svc>.yaml `update.range` — the operator's declared per-service policy (schemas/servicedefinition.schema.json), which Renovate does not read. Without it the dry run proposed gitea 28.2.0, qbittorrent 20.04.1 (both stale/odd tags, not real successors), radarr 6.x and nextcloud 35 (four majors past 31; Nextcloud cannot skip majors). KEEP IN STEP with the catalog ranges.",
|
"description": "ADDED 2026-10-30. MIRRORS catalog/<svc>.yaml `update.range` — the operator's declared per-service policy (schemas/servicedefinition.schema.json), which Renovate does not read. Without it the dry run proposed gitea 28.2.0, qbittorrent 20.04.1 (the qbittorrent tag is a stale/odd one, not a real successor; gitea 28.x IS the real successor of 1.27 — upstream dropped the \"1.\" prefix at 28.0.0 — and the catalog moved to it on 2026-11-07, so the gitea ceiling is now <29.0.0: 28.x patches/minors are proposed, never auto-merged per the blast-radius rule, and 29 waits for a human), radarr 6.x and nextcloud 35 (four majors past 31; Nextcloud cannot skip majors). KEEP IN STEP with the catalog ranges.",
|
||||||
"matchPackageNames": ["gitea/gitea"],
|
"matchPackageNames": ["gitea/gitea"],
|
||||||
"allowedVersions": "<2.0.0"
|
"allowedVersions": "<29.0.0"
|
||||||
},
|
},
|
||||||
{ "matchPackageNames": ["nextcloud"], "allowedVersions": "<32.0.0" },
|
{ "matchPackageNames": ["nextcloud"], "allowedVersions": "<32.0.0" },
|
||||||
{ "matchPackageNames": ["lscr.io/linuxserver/qbittorrent"], "allowedVersions": "<6.0.0" },
|
{ "matchPackageNames": ["lscr.io/linuxserver/qbittorrent"], "allowedVersions": "<6.0.0" },
|
||||||
@@ -368,6 +391,45 @@
|
|||||||
"automerge": false,
|
"automerge": false,
|
||||||
"addLabels": ["needs-review", "range-widen"]
|
"addLabels": ["needs-review", "range-widen"]
|
||||||
},
|
},
|
||||||
|
// ADDED 2026-11-08 — KEEP THE LOWER BOUND WHEN A MINOR-LOCKED RANGE MOVES. MEASURED PROBLEM:
|
||||||
|
// PRs #131 (longhorn), #130 (headlamp) and #115 (kube-prometheus-stack) turned
|
||||||
|
// `">=1.12.0 <1.13.0"` into `"<1.14.0"` — an UNBOUNDED-below range that also admits every
|
||||||
|
// older chart (and #130 merged: platform/headlamp/helmrelease.yaml carried `<0.47.0`). WHY,
|
||||||
|
// from the 44.167.0 source this file is validated against: no rangeStrategy is set, so it is
|
||||||
|
// `auto`; the flux manager has no getRangeStrategy and no updateLockedDependency, so
|
||||||
|
// lib/modules/manager/index.ts getRangeStrategy resolves `auto` -> `replace`. The helm
|
||||||
|
// datasource's defaultVersioning is `helm`, which is the npm versioning api verbatim
|
||||||
|
// (lib/modules/versioning/helm/index.ts: `api = { ...npm }`), and npm getNewValue under
|
||||||
|
// `replace` rewrites `>=A <B` to just `<C`. Calling that getNewValue in the 44.167.0 image for
|
||||||
|
// currentValue `>=1.12.0 <1.13.0`, newVersion 1.13.0:
|
||||||
|
// replace -> `<1.14.0` widen -> `>=1.12.0 <1.14.0` bump -> `>=1.13.0 <1.14.0`
|
||||||
|
// Only `bump` keeps a one-minor window. (widen keeps the bound but grows the window by a minor
|
||||||
|
// on every PR — the opposite of minor-locking.) Under bump the new lower bound is the release
|
||||||
|
// proposed (e.g. 1.13.2 -> `>=1.13.2 <1.14.0`); Flux still picks the newest that satisfies it.
|
||||||
|
// Scoped to the bounded `>=A <B` shape only: on a ceiling-only range like `<0.47.0` bump
|
||||||
|
// returns the value unchanged, so Renovate would silently propose NOTHING for it, ever.
|
||||||
|
{
|
||||||
|
"description": "ADDED 2026-11-08. HelmRelease `version: \">=A <B\"` ranges move as a whole window (rangeStrategy bump), so a range-widen PR yields `>=1.13.0 <1.14.0`, not `<1.14.0`. See the comment above for the measured cause.",
|
||||||
|
"matchManagers": ["flux"],
|
||||||
|
"matchDatasources": ["helm"],
|
||||||
|
"matchCurrentValue": "/^>=\\S+ <\\S+$/",
|
||||||
|
"rangeStrategy": "bump"
|
||||||
|
},
|
||||||
|
// ...AND DO NOT LET bump ADD NOISE. Under bump Renovate also raises the lower bound for a
|
||||||
|
// release ALREADY INSIDE the range (lib/workers/repository/process/lookup/generate.ts sets
|
||||||
|
// isBump; e.g. 1.12.5 against `>=1.12.0 <1.13.0` -> `>=1.12.5 <1.13.0`), which `replace` never
|
||||||
|
// proposed. Flux already installs that release with no PR, and the range-widen rule above
|
||||||
|
// would hold every one for review. util/package-rules/update-types.ts matches isBump as
|
||||||
|
// updateType `bump`, so exactly those updates are switched off here; out-of-range moves are
|
||||||
|
// ordinary minor/major updates and are unaffected.
|
||||||
|
{
|
||||||
|
"description": "ADDED 2026-11-08. No PR for a chart release the HelmRelease range already admits — Flux adopts it on its own.",
|
||||||
|
"matchManagers": ["flux"],
|
||||||
|
"matchDatasources": ["helm"],
|
||||||
|
"matchCurrentValue": "/^>=\\S+ <\\S+$/",
|
||||||
|
"matchUpdateTypes": ["bump"],
|
||||||
|
"enabled": false
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"description": "ADDED 2026-10-30. velero-plugin-for-aws is pinned to a RELEASE CANDIDATE (v1.14.3-rc.2) and Renovate proposed NOTHING for it, although v1.14.3 and v1.14.4 are stable on Docker Hub. Docker versioning treats everything after the first `-` as a compatibility SUFFIX, so only other `*-rc.2` tags qualify and the RC is a fossil forever. Semver versioning reads it as a prerelease, and a prerelease upgrades to the stable release.",
|
"description": "ADDED 2026-10-30. velero-plugin-for-aws is pinned to a RELEASE CANDIDATE (v1.14.3-rc.2) and Renovate proposed NOTHING for it, although v1.14.3 and v1.14.4 are stable on Docker Hub. Docker versioning treats everything after the first `-` as a compatibility SUFFIX, so only other `*-rc.2` tags qualify and the RC is a fossil forever. Semver versioning reads it as a prerelease, and a prerelease upgrades to the stable release.",
|
||||||
"matchPackageNames": ["velero/velero-plugin-for-aws"],
|
"matchPackageNames": ["velero/velero-plugin-for-aws"],
|
||||||
@@ -439,14 +501,5 @@
|
|||||||
"conformance/tests/**",
|
"conformance/tests/**",
|
||||||
"platform/image-automation/per-service/**",
|
"platform/image-automation/per-service/**",
|
||||||
"platform/kyverno/tests/**"
|
"platform/kyverno/tests/**"
|
||||||
],
|
]
|
||||||
// The `docker` manager (Dockerfiles, docker-compose) and the `kubernetes` manager above were
|
|
||||||
// BOTH previously `enabled: false`, solely so Renovate would not fight Flux image-automation
|
|
||||||
// over a running tag. That controller was never enabled and never will be here (see _comment),
|
|
||||||
// so the only thing those two flags were achieving was leaving every plain `image:` tag in the
|
|
||||||
// repo unmanaged. `docker` is left out of enabledManagers because this repo contains no
|
|
||||||
// Dockerfile that ships to the cluster; `kubernetes` is now ON and is the one that matters.
|
|
||||||
"docker": {
|
|
||||||
"enabled": true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user