ci: call the shared SonarQube block, and say what it does not do
Validate Helm Charts / Detect changed charts (pull_request) Successful in 4s
Validate Helm Charts / Build ${{ fromJSON(needs.detect-changed-charts.outputs.charts) }} (pull_request) Skipped
Validate Helm Charts / Trivy config ${{ fromJSON(needs.detect-changed-charts.outputs.scan-charts) }} (pull_request) Skipped
Validate Helm Charts / Install-test (pull_request) Skipped
Validate Helm Charts / Resource quantities (pull_request) Successful in 9s
SonarQube Analysis / SonarQube Analysis (push) Failing after 1m0s

Adds the caller side of the analysis: .github/workflows/sonar.yml as a hull of
the same kind as the publish ones, and sonar-project.properties as the
project-specific half. The workflow itself is the shared block in
workflow-util/pipelines, bound to @v4.

No paths filter, unlike the publish hulls: the analysis looks at the whole tree
- the five shell scripts under ci/, every workflow file, the YAML and Helm
templates, the values.schema.json files, and the secret detection over
everything. A filter would leave the previous result standing after a change it
did not cover while still presenting it as current.

workflow_dispatch is on the hull on purpose. Three things about this path are
unmeasured - node24 in the runner image, the ~59 MiB scanner download, and
whether a repository variable reaches a workflow called from another repository
- and any of them may need more than one attempt. Without a dispatch every
attempt would cost a dummy commit on main.

No exclusions, including the Helm templates, which are not valid YAML because
of {{ }}. What the Helm and YAML analysers make of them is only learnable by
letting them run over them once, and an exclusion written on a guess hides
whatever else matches it. If the first run produces noise worth suppressing,
the exclusion belongs in sonar-project.properties with the finding it answers
named next to it.

The project key carries the owner (jlab-cluster_helm-charts) because the
instance serves two, and a key cannot be changed afterwards without losing the
project's history.

The analysis is not a gate. The Community Build analyses the main branch only,
so the trigger is a push to main and a finding appears after the merge - at
which point there is nothing left to block. README and the CI-CD wiki page both
say so, with the two conditions under which a gate would become right, so that
its absence is not read as an oversight.
This commit was merged in pull request #73.
This commit is contained in:
jklein committed 2026-10-01 23:49:48 +00:00
1 parent 8878329f31
commit 67d8c94fb3
3 files changed
+94

No files matched your search

+37
View File
@@ -0,0 +1,37 @@
name: SonarQube Analysis
# The analysis itself lives in workflow-util/pipelines, bound to a tag like
# every other call to that repository. This hull only decides WHEN it runs.
#
# No "paths" filter, unlike the publish-<chart>.yml hulls: the analysis looks at
# the whole tree - the five shell scripts under ci/, every workflow file, the
# YAML and Helm templates, the values.schema.json files, and the secret
# detection that covers everything. A filter would mean a change outside the
# filter leaves the last result standing while claiming to be current.
on:
# push on main, and only main, because the SonarQube Community Build analyses
# the main branch only - there is no pull-request analysis to hook into. The
# consequence belongs said out loud rather than discovered: a finding appears
# AFTER the merge. This is an observation of the state of main, not a gate,
# and the shared block deliberately carries no quality-gate step.
push:
branches:
- main
# workflow_dispatch, and it is not decoration. Three things about this path
# are unmeasured - whether node24 is in the runner image, whether the ~59 MiB
# scanner download gets through (a 17 MB one once did not, MTU 1500 against a
# pod network of 1450), and whether a repository variable reaches a workflow
# called from another repository. Any of them may need more than one attempt,
# and without a dispatch every attempt would cost a dummy commit on main.
# Only the maintainer can trigger it; the bot account cannot dispatch.
workflow_dispatch:
jobs:
sonar:
name: SonarQube Analysis
uses: workflow-util/pipelines/.gitea/workflows/sonar-analysis.yml@v4
# secrets: inherit passes SONAR_TOKEN through. The block declares it as
# required, and a missing required secret fails job CREATION on this
# instance, not the job - the run would stand there with no steps at all.
# SONAR_HOST_URL is a repository variable and needs nothing here.
secrets: inherit
+25
View File
@@ -68,6 +68,7 @@ Every chart has a wiki page of its own — `https://git.jk-dev.de/jlab-cluster/h
**It stays in this repository, and that is a decision, not an oversight** (Gitea #66, decision 4). It is reusable in form but not in substance: it is welded to the `ci/` fixtures, to the `ci/excluded-charts.txt` parser and to this repository's chart matrix. A building block that only one repository can use is not shared, it is merely moved somewhere else — and moving it would buy nothing while putting a tag between us and a file we change often. `publish-helm-chart.yml` was the opposite case: it takes a chart name and a registry owner and knows nothing about this repository, which is exactly why it could go. This note exists so the question is not asked again in half a year.
- `install-test-all-charts.yml` — manually dispatched **all-charts install-test**: the same job, with a matrix over every installable chart instead of the changed ones. Optional input for a subset of charts (empty = all). See below for when to run it.
- `sonar.yml` — **SonarQube analysis**, a hull like the publish ones: it triggers on pushes to `main` (no `paths` filter — the analysis looks at the whole tree) or manually, and calls `workflow-util/pipelines/.gitea/workflows/sonar-analysis.yml@v4` with `secrets: inherit`. The project-specific half is `sonar-project.properties` in the repo root. **It is not a gate** and it does not block anything — see below.
### Where this repo lives, and what a Gitea runner does differently
@@ -170,6 +171,30 @@ Why it exists at all: the answer arrived once by accident. Gitea PR #48 correcte
- **The result is written down.** Every matrix leg records its own outcome next to the four facts that decide what the outcome means — date, runner label, `K3S_IMAGE`, `kubectl` version — and a final job records the verdict over the whole matrix. A green run nobody notes is unknown again next time. Per leg and not as one table, because a matrix leg only ever sees its own job summary.
- **Expected runtime: 20 charts in 12m41s** (Gitea PR #48, one job, all legs concurrently on one Docker daemon). The figure is there so a later deviation is visible as one; compare it against the chart count the run prints in its own summary — 24 at the time of writing, and one more with every new chart. The job timeout is 30 minutes per leg and lives in `install-test.yml`, i.e. it is the same for both callers on purpose.
### SonarQube analysis (after the merge, and not a gate)
`sonar.yml` sends the whole tree through a SonarQube analysis. The workflow itself is the shared block [`sonar-analysis.yml`](https://git.jk-dev.de/workflow-util/pipelines/wiki/sonar-analysis) in `workflow-util/pipelines`, bound to `@v4`; the only thing on this side is `sonar-project.properties` in the repo root, holding the project key `jlab-cluster_helm-charts`. The key carries the owner because the instance serves two (`jlab-cluster` and `suer`), and a project key cannot be changed afterwards without losing the project's history.
**What is analysed** — the languages were checked against SonarQube's list rather than assumed, and the fit is better than expected:
| What is in this repository | Analyser |
|---|---|
| the 5 shell scripts under `ci/` — the whole CI logic | Shell |
| ~30 workflow files | GitHub Actions |
| 238 YAML files and 26 Helm template directories | Kubernetes/Helm, YAML |
| 26 `values.schema.json` | JSON |
| the whole tree | Secrets |
The last row is the valuable one. **The premise of this repository is that no credential ever lands in it** — every secret is a 1Password `OnePasswordItem` reference — and until now nothing checked that premise.
**It is not a gate, and that is a decision.** The SonarQube Community Build analyses the **main branch only**; there is no pull-request analysis to hook into, so the trigger is a push to `main` and **a finding appears after the merge**. At that point there is nothing left to block, which is why the shared block carries no quality-gate step: a red gate behind the merge would only colour `main` red without having prevented anything, and a permanent red is a thing people learn to ignore — taking the credibility of `validate-charts` with it. A gate becomes the right thing under one of two conditions: pull-request analysis is available, or a deployment hangs off the workflow. Until then, read the findings in SonarQube; nothing in this repository turns red because of them.
**No exclusions, for the first round.** Including the Helm templates, which are not valid YAML because of `{{ }}`. What the Helm and YAML analysers make of them is only learnable by letting them run over them once, and excluding them up front would discard that answer along with the bulk of the repository. If the first run produces noise genuinely not worth reading, the exclusions go into `sonar-project.properties` with the finding they answer named next to them — not before.
**Prerequisites**, both on the instance and neither in a file here: the repository **variable `SONAR_HOST_URL`** (a variable and not a secret on purpose — the server address is not a credential, and as a variable it stays readable in the log, which is what makes a wrong one diagnosable) and the **secret `SONAR_TOKEN`**, which reaches the block through `secrets: inherit`. The block guards both and names the setting and its place when one is empty. A *missing* `SONAR_TOKEN` never gets that far: the block declares it `required`, and a missing required secret fails job **creation** on this instance, so the run would show no steps at all — the same quirk as everywhere else here.
**Three things about this path are unmeasured**, and the first run after this change is what answers them: whether `node24` is in the runner image (the scan action is a JavaScript action declaring `runs: using: node24`, so a failure will read as an action problem rather than an image one), whether the scanner download gets through — the action fetches the Sonar Scanner CLI from `binaries.sonarsource.com` at run time, ~59 MiB, and a 17 MB download once failed on this runner for MTU reasons (see "What a GitHub runner had been providing quietly") — and whether a repository **variable** reaches a workflow called from another repository, which is the same open question the publish route has for `OP_CONNECT_HOST`. `workflow_dispatch` is on the hull for exactly this: retrying any of them otherwise costs a dummy commit on `main`.
## Dependency updates (Renovate)
Renovate runs as the shared `jlab-renovate-bot` instance on the Gitea side, which also serves the other repositories there — see "Renovate on Gitea" below. **There is no Renovate workflow in this repo, and none should be added.** (The `jlab-renovate` GitHub App that used to do this is switched off along with the rest of GitHub Actions.) What updates is unaffected either way: the config is **`renovate.json5`** in the repo root (JSON5 for the inline reasoning; requires Renovate >= 41 for `managerFilePatterns`/`customType`). There must be exactly one config file — do not add a second `renovate.json` or `.github/renovate.json`.
+32
View File
@@ -0,0 +1,32 @@
# Project-specific half of the SonarQube analysis. The workflow is the shared
# block in workflow-util/pipelines and is identical for every repository that
# uses it; everything that is about THIS repository lives here.
#
# The key carries the owner. The instance serves two owners (jlab-cluster and
# suer), so a bare "helm-charts" would be free now and collide the moment the
# second owner has a repository of the same name - and a key cannot be changed
# afterwards without losing the project's history.
sonar.projectKey=jlab-cluster_helm-charts
sonar.projectName=jlab-cluster/helm-charts
# The whole tree, which is also the scanner's default - stated rather than
# implied, because the absence of exclusions below is a decision and an empty
# file would not show that.
sonar.sources=.
# NO EXCLUSIONS, deliberately, for the first round.
#
# The interesting case is the Helm templates: because of {{ }} they are not
# valid YAML, and what the Helm and YAML analysers do with them is something we
# can only find out by letting them run over them once. Excluding them up front
# would throw away the only chance to learn it, and it would exclude the 26
# chart template directories that are the bulk of this repository.
#
# The same goes for the secret detection, which is the most valuable analyser
# here: the premise of this repository is that no credential ever lands in it -
# every secret is a 1Password OnePasswordItem reference - and until now nothing
# checked that premise.
#
# If the first run produces noise that is genuinely not worth reading, the
# exclusions belong here, with the finding they answer named in a comment. Not
# before: an exclusion written on a guess hides whatever else matched it.