Maintenance¶
Two things keep the repository from rotting: GitHub Actions workflows that lint everything, and Renovate, which chases dependencies.
CI workflows¶
All in .github/workflows/.
| Workflow | Trigger | What it checks |
|---|---|---|
lint-ansible.yaml |
ansible/** |
ansible-lint |
lint-python.yaml |
boot_server/*.py, scripts/*.py, tests/*.py, pyproject.toml, uv.lock |
ruff check, ruff format --check and pytest on the boot server and the scripts |
lint-yaml.yaml |
**/*.yaml |
yamllint |
lint-markdown.yaml |
**/*.md |
markdownlint-cli2 |
docs.yaml |
push to main under docs/**, overrides/**, zensical.toml, pyproject.toml, uv.lock |
Builds the site with --strict and publishes it — see Contributing. The build runs with a read-only token; only the deploy job, which runs no dependency code, can push |
preview.yaml |
pull requests on the same paths | Publishes a preview under pr-preview/ from the build job's artifact, same split as docs.yaml; fork PRs skipped |
argo-diff-preview.yaml |
pull requests under payload/** |
Comments the rendered ArgoCD manifest diff against main; fork PRs skipped |
image-scan.yaml |
pull requests under payload/** |
scripts/image-scan-diff.py: renders every Application on main and on the PR, scans with Trivy only the images that changed, and fails on a fixable CRITICAL the replaced image did not carry. A finding already waiting on a release does not block the bump that gets closer to it |
renovate-validate.yaml |
renovate.json |
renovate-config-validator |
secret-scan.yaml |
every pull request | gitleaks over the PR's commits, the same version as the pre-commit hook; a manual run scans the whole history |
ci-ok.yaml |
every pull request | Waits for every other check on the head commit and fails if one failed. The only check the ruleset on main requires, so the others keep their path filters |
fonts-check.yaml |
scripts/update-fonts.sh, docs/assets/fonts/** |
make fonts-check: the committed woff2 files match the pinned releases — see Fonts |
Renovate¶
Configured in renovate.json, on top of the shared preset
JanWelker/renovate-config,
which holds the policy for every repository. Every component gets its own pull
request so a failing update never holds up an unrelated one; the only groups
are the sets below that must move together.
Automerge policy¶
- The preset's rules: three days between a release and its PR, patch and
minor automerge once
ci-okis green, majors open at 19:00 and wait for a human, and security fixes skip both the wait and the schedule. The preset's README says why for each. - One exception: a Kubernetes minor waits for a human. A Flatcar minor
lands the same way a Grafana chart patch does, but
kubernetes_versionis walked forward by hand in version-skew order, and a node provisioned from an automerged minor would run a kubelet ahead of the API server and fail itskubeadm join— see Upgrades. - Pairs that must move together are grouped into one PR:
prometheus-operator-crdswithkube-prometheus-stack, because the CRDs must not lag the operator;rook-cephwithrook-ceph-cluster, because Rook requires both charts on the same version; andmarkdownlint-cli2in the workflow with its pre-commit hook, because the two must lint alike;gitleakslikewise; theexternal-snapshottertag with thesnapshot-controllerimage it pins, because upstream's manifest at a tag names the previous release's image (the image is aregistry.k8s.ioone, so the pair waits for a human like every third-party image there); the two ARC charts, because the controller deletes a scale set whose chart version differs from its own; and every Python pin (requires-python,.python-version, thepythonimages), becauseuv syncfails when they name different minors. - Python ranges bump.
pyproject.tomldeclares>=ranges, which a new release already satisfies;rangeStrategy: bumpis what makes Renovate open a PR for the linters and the docs generator at all. - Flatcar and Kubernetes bumps change what a newly provisioned node installs, not what a running node runs. kubeadm cannot skip a minor, so a cluster that sits while two Kubernetes minors merge has to be walked forward one at a time — see Upgrades.
- A font bump cannot automerge without its second commit:
fonts-check.yamlfails on it andci-okwaits for it — see Fonts. config:best-practicespins GitHub Actions to commit SHAs and container images to digests, and collects every pin into one sharedrenovate/pin-dependenciesbranch.
Scope¶
Python (pyproject.toml, uv.lock), Docker images, GitHub Actions, Kubernetes
and ArgoCD manifests, Helm values files (payload/**/values.yaml) and
pre-commit hooks, plus custom regex managers for the versions in
ansible/inventory.yaml, the font pins in scripts/update-fonts.sh, any
# renovate: annotation and every CloudNativePG imageName under payload/,
and the tool versions the workflows under .github/workflows/ install by
name rather than by action (the diff-preview image, markdownlint-cli2,
renovate and Trivy). The Makefile is deliberately not
tracked: the bootstrap targets read targetRevision out of the owning
application.yaml — see Version pins.
Manager rules¶
A misconfigured manager fails silently: the run succeeds and nothing is checked. Prove a manager works by reading what it extracts:
It needs no credentials for a public repository and prints every dependency it
found and why it skipped any. The rules, each of which is written into
renovate.json:
pre-commitmust be enabled explicitly ("pre-commit": {"enabled": true}); Renovate ships that manager disabled, and apackageRulesentry for it does nothing on its own.- The
helm-valuesmanager reads only real values files. An image tag inline in an Application'shelm.valuesObject:is invisible to it; put a# renovate: datasource=... depName=...comment on the line above so the annotation manager picks it up, or move the values into avalues.yaml. - A custom manager that pins a full image reference needs
autoReplaceStringTemplate. Without it the pin cannot be written, and one unwritable pin fails the whole shared pin branch (Error updating branch: update failureon the Dependency Dashboard, the only symptom, since version bumps keep working). CapturingcurrentDigestonly lets Renovate update a digest that is already there. - A bare Helm
tag:value goes in thepinDigests: falserule. A tag like3.4.4-alpinehas no position for a digest; leave one out and the same banner comes back. - A two-part version needs a
versioningTemplate. The defaultsemver-coercedrejects6.03, so the dependency is extracted and then dropped before lookup.syslinuxand the Inter font pin carry aregex:versioning for this reason; Inter's also keepsv4.0-beta9h-style prereleases out. - Match the registry's spelling, not the GitHub org's. The diff-preview
image is
dagandersen/argocd-diff-previewon Docker Hub; a regex ondag-andersenmatches nothing and reports nothing. - Hold a major with a regex, not a range.
"allowedVersions": "/^17\\./"is matched before version parsing; a range like<18is graded by npm semver, where a three-part tag such as17.11.1-trixieparses as a prerelease and is dropped from every range that does not name one. Python is held at 3.14 this way until ansible-core supports 3.15. - Check that the currently pinned version passes its own rule. A filter that excludes what is deployed excludes everything, silently.
Language statistics¶
GitHub's language bar comes from
Linguist, which counts bytes of
files it classifies as a language. YAML is classified as data and Markdown as
prose, so both are excluded by default and the bar reported this repository as
Shell and Jinja. .gitattributes marks YAML detectable, and marks the three
vendored Grafana dashboards as vendored, because their upstream JSON blobs are
more than half the repository's YAML bytes and would otherwise be most of the
bar. A vendored dashboard added later needs its own line there; GitHub
recalculates on the next push to main.