The operator ran the Renovate workflow manually and it failed. The cause is not
the workflow: renovate.json was never a valid Renovate config. It carried five
pseudo-comment keys (_comment, _kubernetes_comment, _unmanageable_comment,
_ignorePaths_comment, _docker_comment) because JSON has no comments, and
Renovate REJECTS unknown top-level options. Proven with the official validator:
ERROR: Found errors in configuration
Invalid configuration option: _comment
Invalid configuration option: _docker_comment
Invalid configuration option: _ignorePaths_comment
Invalid configuration option: _kubernetes_comment
Invalid configuration option: _unmanageable_comment
This is the SECOND independent reason Renovate never worked, on top of PR #77's
cron firing outside renovate.json's own schedule window. Either alone was enough
to produce "nothing ever happens"; #73 shipped both.
Converted renovate.json -> renovate.json5. JSON5 permits real // comments and
Renovate discovers renovate.json5 natively; nothing in .gitea/ or ci/ pins the
old filename and no RENOVATE_CONFIG_FILE is set. Every word of the original
rationale is preserved as positioned comments (74 comment lines) rather than
flattened into a description string.
Verified the conversion dropped nothing: 18 non-comment keys before and after,
no missing/extra keys, no changed values, docker: {enabled: true} still present.
The validator now returns "Config validated successfully".
DISPROVED while here: `enabledManagers` containing bare `regex` was suspected of
needing to be `custom.regex` (which would silently disable every inline
`# renovate:` marker). The validator accepts `regex`; unfounded, unchanged.
Renovate has still never completed a run -- this fixes one of two blockers and
nothing in the gate catches an invalid config. See the handoff.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
188 lines
11 KiB
JSON
188 lines
11 KiB
JSON
// =================================================================================================
|
|
// Renovate configuration — JSON5, so the rationale can live NEXT TO what it explains.
|
|
// =================================================================================================
|
|
// THIS FILE WAS `renovate.json` AND IT WAS INVALID. It carried five `_comment` / `_*_comment`
|
|
// pseudo-comment keys, because JSON has no comments. Renovate REJECTS unknown top-level options, so
|
|
// the whole config failed validation and the first manual run of the workflow ERRORED OUT. Proven
|
|
// with the official validator (`ghcr.io/renovatebot/renovate renovate-config-validator`):
|
|
//
|
|
// ERROR: Found errors in configuration
|
|
// Invalid configuration option: _comment
|
|
// Invalid configuration option: _docker_comment
|
|
// Invalid configuration option: _ignorePaths_comment
|
|
// Invalid configuration option: _kubernetes_comment
|
|
// Invalid configuration option: _unmanageable_comment
|
|
//
|
|
// Renovate discovers `renovate.json5` natively (no RENOVATE_CONFIG_FILE needed, and nothing in
|
|
// .gitea/ or ci/ pins the old filename — only prose in comments), and JSON5 permits real `//`
|
|
// comments. So the fix keeps every word of the original rationale, positioned, instead of
|
|
// flattening it into a `description` string.
|
|
//
|
|
// VALIDATE BEFORE COMMITTING — it is one command and it is the only thing that catches this class:
|
|
// podman run --rm -v "$PWD/renovate.json5:/tmp/renovate.json5:ro,Z" \
|
|
// ghcr.io/renovatebot/renovate renovate-config-validator /tmp/renovate.json5
|
|
//
|
|
// NOTE on what was NOT wrong: `enabledManagers` containing bare `regex` was suspected of needing to
|
|
// be `custom.regex`. The validator accepts `regex`, so that suspicion was unfounded and nothing
|
|
// here changes it.
|
|
{ "$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
|
// Renovate config — Phase 4 automated updates (ADR-0009, R-UPD-01/02). AS OF 2026-10-17
|
|
// RENOVATE OWNS BOTH HALVES: Helm chart/dependency bumps AND per-service container image tags.
|
|
// It opens PRs for everything; allowed low-risk classes auto-merge on green CI. The ADR-0009
|
|
// split that gave image tags to Flux image-automation is formally closed — see
|
|
// .gitea/workflows/renovate.yaml for the three measured reasons (it pushes commits rather than
|
|
// opening PRs, so it bypasses the CLAUDE.md §1 gate; `main` is protected with
|
|
// user_can_push:false and the flux-system GitRepository has no secretRef, so it cannot write
|
|
// anyway; and enabling it would need an in-cluster git WRITE credential for the repo that
|
|
// defines the cluster). image-automation has been commented out at
|
|
// platform/kustomization.yaml:52 since it was authored and the cluster holds zero
|
|
// ImagePolicy/ImageRepository/ImageUpdateAutomation objects, so nothing is being torn down.
|
|
// CONSEQUENCE: the `docker` and `kubernetes` managers, previously disabled ONLY to avoid
|
|
// fighting that controller, are now ENABLED — without them every plain `image: repo/name:tag`
|
|
// line in this repo was maintained by nothing at all, which is how the oauth2-proxy auth
|
|
// sidecars reached 7 minors behind the oauth2-proxy running in the same cluster. RUNNER:
|
|
// .gitea/workflows/renovate.yaml (scheduled daily); platform/endpoint are set there, not here.
|
|
"extends": ["config:recommended", ":dependencyDashboard", ":semanticCommits", "helpers:pinGitHubActionDigests"],
|
|
"timezone": "America/New_York",
|
|
"schedule": ["after 3am and before 6am"],
|
|
"prConcurrentLimit": 5,
|
|
"prHourlyLimit": 2,
|
|
"commitMessagePrefix": "chore(deps):",
|
|
"labels": ["dependencies", "automated-update"],
|
|
"ignoreUnstable": true,
|
|
"respectLatest": true,
|
|
"enabledManagers": ["flux", "kustomize", "helmv3", "kubernetes", "regex", "github-actions"],
|
|
"flux": {
|
|
"managerFilePatterns": [
|
|
"/(^|/)platform/.+\\.ya?ml$/",
|
|
"/(^|/)apps/.+\\.ya?ml$/"
|
|
]
|
|
},
|
|
"kustomize": {
|
|
"managerFilePatterns": [
|
|
"/(^|/)kustomization\\.ya?ml$/"
|
|
]
|
|
},
|
|
// ADDED 2026-10-17. The `kubernetes` manager reads plain `image:` fields out of Kubernetes
|
|
// manifests, and it is NOT auto-detected — it only looks at files matched here, which is why
|
|
// this block is as broad as it is. Scope covers every place this repo writes a container image:
|
|
// platform components, the rendered app waves, the ServiceDefinition catalog that generates
|
|
// them, the runbooks web server and the pm-agent job template. `catalog/` matters as much as
|
|
// `apps/`: apps/wave-*/*.yaml are GENERATED by catalog/render.py, so a bump applied only to the
|
|
// rendered output is reverted by the next render and would also fail ci/render-golden.sh.
|
|
// Renovate will raise BOTH, and both must land together.
|
|
"kubernetes": {
|
|
"managerFilePatterns": [
|
|
"/(^|/)platform/.+\\.ya?ml$/",
|
|
"/(^|/)platform-config/.+\\.ya?ml$/",
|
|
"/(^|/)apps/.+\\.ya?ml$/",
|
|
"/(^|/)catalog/.+\\.ya?ml$/",
|
|
"/(^|/)runbooks/serving/.+\\.ya?ml$/",
|
|
"/(^|/)infra/agent/.+\\.ya?ml$/"
|
|
]
|
|
},
|
|
"customManagers": [
|
|
{
|
|
"customType": "regex",
|
|
"description": "Inline `# renovate: datasource=... depName=...` markers on pinned URLs / version literals (e.g. platform/kubevirt/kustomization.yaml release URLs, ci/versions.env, infra/devcontainer/versions.env).",
|
|
"managerFilePatterns": [
|
|
"/(^|/)kustomization\\.ya?ml$/",
|
|
"/(^|/)versions\\.env$/"
|
|
],
|
|
"matchStrings": [
|
|
"#\\s*renovate:\\s*datasource=(?<datasource>\\S+)\\s+depName=(?<depName>\\S+)(?:\\s+versioning=(?<versioning>\\S+))?\\s*\\n.*?(?<currentValue>v?[0-9]+\\.[0-9]+\\.[0-9]+)"
|
|
],
|
|
"versioningTemplate": "{{#if versioning}}{{{versioning}}}{{else}}semver{{/if}}"
|
|
}
|
|
],
|
|
"packageRules": [
|
|
{
|
|
"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": [
|
|
"minor",
|
|
"patch",
|
|
"digest"
|
|
],
|
|
"automerge": true,
|
|
"automergeType": "pr",
|
|
"platformAutomerge": true
|
|
},
|
|
{
|
|
"description": "MAJOR / potentially-breaking bumps require human review (docs/11 safety rail). Never auto-merged.",
|
|
"matchUpdateTypes": [
|
|
"major"
|
|
],
|
|
"automerge": false,
|
|
"addLabels": [
|
|
"needs-review",
|
|
"breaking"
|
|
]
|
|
},
|
|
{
|
|
"description": "Keep the KubeVirt + CDI operator pair on a compatible track \u2014 grouped so they bump together (platform/kubevirt/kustomization.yaml).",
|
|
"matchDepNames": [
|
|
"kubevirt/kubevirt",
|
|
"kubevirt/containerized-data-importer"
|
|
],
|
|
"groupName": "kubevirt-cdi"
|
|
},
|
|
{
|
|
"description": "Pinned tool versions (ci/versions.env, infra/devcontainer/versions.env) are a deliberate change surface \u2014 label but do not auto-merge; a version bump there changes CI reproducibility.",
|
|
"matchFileNames": [
|
|
"ci/versions.env",
|
|
"infra/devcontainer/versions.env"
|
|
],
|
|
"automerge": false,
|
|
"addLabels": [
|
|
"ci-tooling"
|
|
]
|
|
},
|
|
{
|
|
"description": "ADDED 2026-10-17. NEVER AUTO-MERGE THE BLAST-RADIUS COMPONENTS, not even a patch. The default rule above auto-merges minor/patch/digest on a green gate, which is right for an app image: a bad Sonarr bump inconveniences one pod. It is wrong for these five, because the gate is a STATIC check \u2014 it renders manifests and lints them, it does not boot a node, drain a volume or form an etcd quorum (vars.CLUSTER_AVAILABLE is unset, so L2/L3 conformance does not run in CI at all). So for anything whose failure mode is the CLUSTER rather than a service, a green gate is not evidence and a human must read the changelog. kured is here because it reboots control-plane nodes; longhorn because a storage-engine bump migrates live volume data; cilium because it is the CNI and a bad bump is a total partition (and its version is DUAL-PINNED in infra/ansible/roles/cilium/defaults/main.yml, which Renovate does not see, so the two would silently desync); cloudnative-pg because it drives Postgres switchovers; kube-prometheus-stack because ci/versions.env asserts an exact promtool/Prometheus lockstep and a chart bump that moves Prometheus fails the gate until that file moves with it.",
|
|
"matchPackageNames": [
|
|
"kured",
|
|
"longhorn",
|
|
"cilium",
|
|
"cloudnative-pg",
|
|
"kube-prometheus-stack"
|
|
],
|
|
"automerge": false,
|
|
"addLabels": [
|
|
"needs-review",
|
|
"blast-radius"
|
|
]
|
|
}
|
|
],
|
|
// WHAT RENOVATE STILL CANNOT FIX, so nobody mistakes a working bot for full coverage. A
|
|
// FLOATING tag has no version to compare, so Renovate skips it silently — it does not pin it
|
|
// and does not warn. As of 2026-10-17 that is: plexinc/pms-docker:latest,
|
|
// lscr.io/linuxserver/jackett:latest, pihole/pihole (NO TAG AT ALL), quay.io/keycloak/keycloak
|
|
// (no tag), nextcloud:31-apache and docker:27-dind. Those must be pinned BY HAND to a concrete
|
|
// version before any automation can maintain them, and until then they drift on every pull with
|
|
// nothing recording what is running. Separately, the HelmRelease minor-locking ranges (9 of 12,
|
|
// e.g. `>=88.0.0 <89.0.0` on kube-prometheus-stack) cap Renovate at patches inside a line it
|
|
// has been told not to leave; raising those ceilings is deliberate per-component work and is
|
|
// NOT part of enabling the bot. Both lists are in decisions/2026-10-17-automated-upgrades.md.
|
|
// platform/kyverno/tests/** ADDED 2026-10-17: those Kyverno policy fixtures pin `alpine:latest`
|
|
// and a `conformance-runner:latest`, which are test inputs whose whole point is to be a fixed
|
|
// shape, not software to keep current. `conformance/**` was already ignored for the same
|
|
// reason; this closes the matching hole that enabling the `kubernetes` manager would otherwise
|
|
// have opened.
|
|
"ignorePaths": [
|
|
"**/*.example",
|
|
"**/*.sops.yaml",
|
|
"conformance/**",
|
|
"platform/image-automation/per-service/**",
|
|
"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
|
|
}
|
|
}
|