Renovate: the PR policy after the alignment with cluster-config
The config now matches cluster-config's: global automerge with platformAutomerge, no PR caps, separateMinorPatch, config:best-practices. Two statements on these pages had become false and are rewritten rather than left standing: that Renovate merges its own PRs because main has no required status checks, and that majors are merged by hand. Both are active on main now - a review requirement and validate-charts as the single status context the matrix reports under - which is precisely why platformAutomerge could be turned on and why automerging majors is not unattended: the review is the gate. Touches Renovate (PR policy), Forge-and-Tooling (auto-merge, required check), Versioning (the order of a stepwise major jump is kept at the review now), CI-CD (required check, why the install-test blocks), and the two pages quoting the searxng downgrade as something that happened "unattended".
6 files changed
+17
-11
No files matched your search
+2
-2
@@ -70,7 +70,7 @@ Trivy is installed from its release tarball, not via `aquasecurity/setup-trivy`
|
|||||||
|
|
||||||
`ci/run-install-test.sh <chart>` against a throwaway [k3d](https://k3d.io) cluster: apply the stub `OnePasswordItem` CRD plus the chart's fixtures, `helm install` with the CI values, and wait for every Deployment and StatefulSet to roll out. Since probes and hardening are active, "Ready" means the app actually answers. On failure the job dumps pod descriptions, events and container logs.
|
`ci/run-install-test.sh <chart>` against a throwaway [k3d](https://k3d.io) cluster: apply the stub `OnePasswordItem` CRD plus the chart's fixtures, `helm install` with the CI values, and wait for every Deployment and StatefulSet to roll out. Since probes and hardening are active, "Ready" means the app actually answers. On failure the job dumps pod descriptions, events and container logs.
|
||||||
|
|
||||||
**The job blocks the PR.** That stopped being negotiable when Renovate began automerging patch and minor updates: a chart that no longer starts must not reach `main` and publish unattended.
|
**The job blocks the PR.** That stopped being negotiable when Renovate began automerging patch and minor updates: a chart that no longer starts must not reach `main` and publish unattended. It counts double now that automerge covers **every** update type — `validate-charts` is a required check on `main`, so a red install-test keeps the armed PR where it is.
|
||||||
|
|
||||||
## Install-test fixtures
|
## Install-test fixtures
|
||||||
|
|
||||||
@@ -123,7 +123,7 @@ All measured on this instance, not taken from documentation, and the reason for
|
|||||||
|
|
||||||
**Runner labels mean different machines.** `ubuntu-latest` has **no Docker socket** here; `ubuntu-docker` has one. Exactly one job cares — the install-test, because k3d is a Docker client — so its `runs-on` is an expression rather than a fixed label, and the same file still works on a GitHub runner where `ubuntu-docker` does not exist.
|
**Runner labels mean different machines.** `ubuntu-latest` has **no Docker socket** here; `ubuntu-docker` has one. Exactly one job cares — the install-test, because k3d is a Docker client — so its `runs-on` is an expression rather than a fixed label, and the same file still works on a GitHub runner where `ubuntu-docker` does not exist.
|
||||||
|
|
||||||
**A matrix runs concurrently inside ONE job, under ONE status context.** Operationally: all legs share one Docker daemon, which makes a per-leg resource name mandatory. For branch protection: a required check per chart is not possible, and the context name carries the *unexpanded* matrix expression, so required-check names must be read off a real run.
|
**A matrix runs concurrently inside ONE job, under ONE status context.** Operationally: all legs share one Docker daemon, which makes a per-leg resource name mandatory. For branch protection: a required check per chart is not possible, and the context name carries the *unexpanded* matrix expression, so required-check names must be read off a real run. What `main` does require is **`validate-charts`**, as that single context, alongside a review — the two conditions Renovate's auto-merge waits for (see [Renovate → PR policy](Renovate#pr-policy)).
|
||||||
|
|
||||||
**Expected runtimes**, so a later deviation is visible as one:
|
**Expected runtimes**, so a later deviation is visible as one:
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -49,13 +49,13 @@ If signing fails, stop and report. Pushing unsigned work only moves the failure
|
|||||||
|
|
||||||
## No merge queue — auto-merge takes its place
|
## No merge queue — auto-merge takes its place
|
||||||
|
|
||||||
Gitea has no merge queue. Its place is taken by **auto-merge on green checks**: a PR is armed once and merges itself when the required checks pass.
|
Gitea has no merge queue. Its place is taken by **auto-merge on green checks**: a PR is armed once and merges itself when the required check passes **and the required review exists**. Both are active on `main` — an armed PR that nobody has reviewed sits there instead of merging, which is what makes Renovate's global `platformAutomerge: true` something other than unattended merging (see [Renovate → PR policy](Renovate#pr-policy)).
|
||||||
|
|
||||||
This does not change who merges. **The user merges.** An agent never merges its own PR and never arms auto-merge on it.
|
This does not change who merges. **The user merges.** An agent never merges its own PR and never arms auto-merge on it.
|
||||||
|
|
||||||
## A required check per chart is not possible
|
## A required check per chart is not possible
|
||||||
|
|
||||||
Gitea runs a whole matrix **as one job under one status context**. The install-test therefore reports once for all charts, and the context name carries the *unexpanded* matrix expression:
|
`main` requires **`validate-charts`**, as the one status context it reports under. Per chart is the part that cannot be had: Gitea runs a whole matrix **as one job under one status context**. The install-test therefore reports once for all charts, and the context name carries the *unexpanded* matrix expression:
|
||||||
|
|
||||||
```
|
```
|
||||||
Validate Helm Charts / Install-test ${{ fromJSON(needs.detect-changed-charts.outputs.install-charts) }} (pull_request)
|
Validate Helm Charts / Install-test ${{ fromJSON(needs.detect-changed-charts.outputs.install-charts) }} (pull_request)
|
||||||
|
|||||||
+1
-1
@@ -90,7 +90,7 @@ A Renovate rule that is syntactically fine and matches nothing produces no PRs
|
|||||||
|
|
||||||
## The comparison that read a hash as a number
|
## The comparison that read a hash as a number
|
||||||
|
|
||||||
Renovate's regex versioning parses `major`, `minor`, `patch`, `build` and `revision` with `Number.parseInt`. A commit hash captured as `build` therefore becomes whatever digits lead it: `9fea41204` is `9`, `777ba8fa4` is `777`, a hash starting with a letter is `NaN`. Renovate offered an image pushed *earlier the same day* as an update to a newer one — unattended, under the patch/minor automerge rule.
|
Renovate's regex versioning parses `major`, `minor`, `patch`, `build` and `revision` with `Number.parseInt`. A commit hash captured as `build` therefore becomes whatever digits lead it: `9fea41204` is `9`, `777ba8fa4` is `777`, a hash starting with a letter is `NaN`. Renovate offered an image pushed *earlier the same day* as an update to a newer one — a downgrade that looks exactly like the routine date bump a reviewer waves through, and automerge then carries it to `main`.
|
||||||
|
|
||||||
**Now:** **a capture group is a claim about how the value is compared.** Parts of a tag that carry no ordering stay outside every group. `prerelease` is not the escape hatch — it sorts correctly but marks every tag unstable, handing the chart to `ignoreUnstable`.
|
**Now:** **a capture group is a claim about how the value is compared.** Parts of a tag that carry no ordering stay outside every group. `prerelease` is not the escape hatch — it sorts correctly but marks every tag unstable, handing the chart to `ignoreUnstable`.
|
||||||
|
|
||||||
|
|||||||
+9
-5
@@ -33,7 +33,7 @@ It is deliberately *not* a `# renovate:` comment inside each `Chart.yaml`: that
|
|||||||
|
|
||||||
The rule every `versioningTemplate` override shares, learned from `searxng` and worth reading before writing the next one.
|
The rule every `versioningTemplate` override shares, learned from `searxng` and worth reading before writing the next one.
|
||||||
|
|
||||||
Renovate's regex versioning parses `major`, `minor`, `patch`, `build` and `revision` with `Number.parseInt`, so a **non-numeric commit hash captured as `build` becomes whatever digits lead it**: `9fea41204` is `9`, `777ba8fa4` is `777`, and a hash starting with a letter is `NaN`. Measured on renovate 41.173.1 and 44.48.0, that makes Renovate offer an image pushed *earlier the same day* as an update to a newer one — unattended, under the patch/minor automerge rule.
|
Renovate's regex versioning parses `major`, `minor`, `patch`, `build` and `revision` with `Number.parseInt`, so a **non-numeric commit hash captured as `build` becomes whatever digits lead it**: `9fea41204` is `9`, `777ba8fa4` is `777`, and a hash starting with a letter is `NaN`. Measured on renovate 41.173.1 and 44.48.0, that makes Renovate offer an image pushed *earlier the same day* as an update to a newer one — a downgrade dressed as a routine date bump, which is what a review waves through and automerge then carries to `main`.
|
||||||
|
|
||||||
**Parts of a tag that carry no ordering belong outside every capture group.** The anchored pattern still requires them to be there; they simply take no part in the comparison.
|
**Parts of a tag that carry no ordering belong outside every capture group.** The anchored pattern still requires them to be there; they simply take no part in the comparison.
|
||||||
|
|
||||||
@@ -75,10 +75,14 @@ A consequence of `freesailarr` pinning every tag in `values.yaml`: the script se
|
|||||||
|
|
||||||
## PR policy
|
## PR policy
|
||||||
|
|
||||||
- **Automerge** for `patch`, `minor` and `digest`, after a `minimumReleaseAge` of 3 days and only once the PR pipeline is green — install-test included, which is why that job no longer runs with `continue-on-error`. Minor is in scope because the sidecar images barely move in patch steps (`alpine 3.22` → `3.24`, `nginx 1.30.4` → `1.31.4` are both minor), so a patch-only rule would practically never fire.
|
The config is **aligned with the one in `cluster-config`**, because the same `jlab-renovate-bot` instance serves both and two repositories behaving differently under one bot is a source of surprises rather than of safety.
|
||||||
- **Renovate merges itself** (`platformAutomerge: false`) rather than handing the PR to the forge's own auto-merge, which on **both** GitHub and Gitea would merge immediately while `main` has no required status checks. That is a statement about the branch protection, not about the platform: once required checks exist, `platformAutomerge: true` becomes the better setting on either side.
|
|
||||||
- **Majors are merged by hand**, and arrive one line at a time (`separateMultipleMajor`) — see [Versioning](Versioning#app-major-jumps-are-taken-one-line-at-a-time).
|
- **Automerge is global** — `automerge: true`, `automergeType: "pr"`, `platformAutomerge: true`, for every update type, **majors included**. What keeps that from being unattended merging is the branch protection, not the absence of automerge: `main` requires a **review** and requires **`validate-charts`** to pass. `platformAutomerge` arms the PR on the forge, and Gitea merges it once *both* are satisfied — an armed PR nobody reviews just sits there. This is exactly the condition the older `platformAutomerge: false` comment named as the point at which `true` becomes the better value; it arrived, so the value changed. `cluster-config` records the same reasoning, having retired its dashboard-approval step for it.
|
||||||
- `prConcurrentLimit: 10` and `prHourlyLimit: 4` keep the k3d install-tests from all starting at once.
|
- **`minimumReleaseAge: "3 days"`** for `patch`, `minor` and `digest` updates — Renovate does not open the PR before then. A merge here is never just a merge: a chart publish follows it straight away, so a release that is yanked hours after it appeared must not reach the registry. `cluster-config` spends the same three days on the dependencies where a merge is expensive (authentik, rancher); here that is all of them. **Majors carry no age gate** — they are held at the review, not by a delay.
|
||||||
|
- **Majors** arrive one line at a time (`separateMultipleMajor`): a v1 → v3 jump produces a PR for the latest v2 first, then one for v3 — see [Versioning](Versioning#app-major-jumps-are-taken-one-line-at-a-time). `separateMinorPatch: true` splits minor and patch into separate PRs as well, so one red install-test does not hold up unrelated updates.
|
||||||
|
- **No PR caps** (`prConcurrentLimit: 100`, `prHourlyLimit: 0`). The cost is known and accepted rather than discovered: the first run after a quiet week can open many PRs at once, and every chart PR starts a k3d install-test, which on a Gitea runner **queues inside one job** instead of fanning out (see [CI/CD](CI-CD)). The alternative was updates trickling in four per hour, and nothing merges without a review anyway.
|
||||||
|
- **`config:best-practices`** is in `extends`, as in `cluster-config`. Two of its members matter here: `helpers:pinGitHubActionDigests` moves the workflow files from `actions/checkout@v7` to SHA pins, and `docker:pinDigests` is switched off again for `custom.regex` and `helm-values`. An `appVersion:` is a **tag** by construction — the templates render `<repo>:{{ .Chart.AppVersion }}` and the chart version's `+up` suffix is built from the same string — so a digest has nowhere to go. Sidecar digests in `values.yaml` could be pinned; that is its own decision, since each digest change would be a chart patch plus a publish.
|
||||||
|
- **Labels:** every Renovate PR carries `renovate`; app-version PRs additionally `app-version`, subchart PRs `subchart`.
|
||||||
|
|
||||||
## `GITHUB_COM_TOKEN`
|
## `GITHUB_COM_TOKEN`
|
||||||
|
|
||||||
|
|||||||
@@ -42,6 +42,8 @@ So that the validate and install-test pipelines actually run. The `change-app-ve
|
|||||||
|
|
||||||
A v1 → v3 jump is **two** PRs: first to the last release of the old major line, merged and published, then to the newest major. Renovate produces exactly that shape itself via `separateMultipleMajor`.
|
A v1 → v3 jump is **two** PRs: first to the last release of the old major line, merged and published, then to the newest major. Renovate produces exactly that shape itself via `separateMultipleMajor`.
|
||||||
|
|
||||||
|
**Majors automerge like everything else** — the review is what holds them, not a separate manual-merge rule (see [Renovate → PR policy](Renovate#pr-policy)). The order is therefore kept at the review: approve the old-line PR first, let it merge and publish, and the new-major PR rebases onto it. Approving both at once hands the sequence to whichever pipeline turns green first, which is the one thing this procedure exists to prevent.
|
||||||
|
|
||||||
The reason is migrations: most apps support an upgrade from the previous major, not from any major. Skipping the intermediate release means the app's own migration path never runs, and the failure arrives inside a database that the new version has already started rewriting.
|
The reason is migrations: most apps support an upgrade from the previous major, not from any major. Skipping the intermediate release means the app's own migration path never runs, and the failure arrives inside a database that the new version has already started rewriting.
|
||||||
|
|
||||||
## Who writes the version
|
## Who writes the version
|
||||||
|
|||||||
+1
-1
@@ -206,7 +206,7 @@ and carries on. It cannot fix the ownership as a non-root process — which is t
|
|||||||
|
|
||||||
SearXNG publishes no semver line: every tag is `<date>-<commit>` and `latest` moves daily. `appVersion` is the exact tag, and the `+up` suffix normalizes the dash to a dot as [Versioning](Versioning) requires.
|
SearXNG publishes no semver line: every tag is `<date>-<commit>` and `latest` moves daily. `appVersion` is the exact tag, and the `+up` suffix normalizes the dash to a dot as [Versioning](Versioning) requires.
|
||||||
|
|
||||||
The chart's `customManagers` block overrides `versioning` with an anchored regex in which **the commit hash sits outside every capture group**. That is the lesson this chart paid for: Renovate parses `major`/`minor`/`patch`/`build`/`revision` with `Number.parseInt`, so a hash captured as `build` becomes whatever digits lead it — `9fea41204` is `9`, `777ba8fa4` is `777`, a hash starting with a letter is `NaN`. Measured on renovate 41.173.1 and 44.48.0, that makes Renovate offer an image pushed *earlier the same day* as an update to a newer one, unattended under the patch/minor automerge rule. A capture group is a claim about how the value is compared; parts of a tag that carry no ordering belong outside every group. `prerelease` is not the way out — it sorts correctly but marks every tag unstable, handing the chart to `ignoreUnstable`. See [Renovate](Renovate) and [Lessons learned](Lessons-Learned).
|
The chart's `customManagers` block overrides `versioning` with an anchored regex in which **the commit hash sits outside every capture group**. That is the lesson this chart paid for: Renovate parses `major`/`minor`/`patch`/`build`/`revision` with `Number.parseInt`, so a hash captured as `build` becomes whatever digits lead it — `9fea41204` is `9`, `777ba8fa4` is `777`, a hash starting with a letter is `NaN`. Measured on renovate 41.173.1 and 44.48.0, that makes Renovate offer an image pushed *earlier the same day* as an update to a newer one — a downgrade dressed as a routine date bump, which a review waves through and automerge then carries to `main`. A capture group is a claim about how the value is compared; parts of a tag that carry no ordering belong outside every group. `prerelease` is not the way out — it sorts correctly but marks every tag unstable, handing the chart to `ignoreUnstable`. See [Renovate](Renovate) and [Lessons learned](Lessons-Learned).
|
||||||
|
|
||||||
## CI
|
## CI
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user