CI-CD: was der erste Sonar-Lauf gemessen hat, und der richtige Schluessel

Der Projektschluessel ist JLab-Helm-Charts, gelesen am Projekt auf dem Server.
Der bisherige war geraten und hat die Meldung erzeugt, die ein nicht
vorhandenes Projekt und ein Token ohne Recht gleichermassen erzeugen - dass der
Server die zwei Faelle nicht unterscheidet, steht jetzt dabei. Dazu, warum kein
sonar.projectName gesetzt wird.

Die Analysator-Tabelle stand aus einer Sprachliste und versprach mehr, als die
Instanz liefert. Gemessen in Lauf 375: zwei Qualitaetsprofile, json und yaml;
gearbeitet haben der IaC-Kubernetes-Sensor und die Geheimnis-Erkennung ueber
391 Dateien. Shell und GitHub Actions liefen nicht, und nichts in diesem
Repository kann das aendern - Analysatoren sind server-seitige Plugins. Die
Folge ist benannt: Die fuenf ci/-Skripte und die dreissig Workflow-Dateien
werden nicht analysiert.

Die Liste der drei offenen Fragen ist eine Liste der drei Antworten geworden,
alle positiv. Die dritte beantwortet dieselbe Frage fuer OP_CONNECT_HOST, und
damit sind an der Publish-Strecke zwei der drei genannten Punkte belegt - offen
bleibt der Upload, der nie stattgefunden hat.
jklein committed 2026-10-02 00:16:08 +00:00
1 parent eef939472e
commit 069d4e3aaa
1 file changed
+47 -20
+47 -20
@@ -64,9 +64,11 @@ A change to the hulls or to the shared block **touches no chart directory, so me
The end-to-end check is a single manual `workflow_dispatch` of any `publish-<chart>.yml` — this is what those dispatch triggers are for, and after the move they are the only way to start a publish by hand. The registry replaces an already published version on re-upload (HTTP 201, below), so it is a safe test of the whole path. Only the maintainer can trigger it; the bot account cannot dispatch workflows.
Three things only that run can prove, and they are the whole remaining risk of the move: that a cross-repository `uses:` resolves on this instance, that a repository variable reaches a workflow called from another repository, and that the upload itself still lands.
Three things were listed here as only that run could prove, and they were the whole remaining risk of the move: that a cross-repository `uses:` resolves on this instance, that a repository variable reaches a workflow called from another repository, and that the upload itself still lands.
The middle one is no longer exclusive to this route: the [SonarQube analysis](#sonarqube-analysis) reads `SONAR_HOST_URL` from `vars` inside a block in that same shared repository, so its first run answers the question for both. The other two stay with the dispatch.
**The first two are now proven, and not by this route.** The [SonarQube analysis](#sonarqube-analysis) is the same shape — a hull in this repository calling a block in `workflow-util/pipelines` and reading a repository variable inside it — and its first run (run 375, a push to `main`) got as far as a running scanner. That could not have happened unless the cross-repository `uses:` had resolved *and* `vars.SONAR_HOST_URL` had arrived: the block guards the variable and exits before the scan when it is empty. Same forge version, same mechanism, so the answer carries over to `OP_CONNECT_HOST` in the publish block without a second measurement.
**What stays unproven is the third, and only the third: the upload itself has never run.** No publish has gone through the shared block, so nothing has yet confirmed that the packaged `.tgz` still lands in the registry from there — and that is also the one of the three whose failure would be quiet rather than loud. The dispatch above is still the way to answer it.
> **The registry accepts a re-upload of an already published version** (HTTP 201) and replaces the stored artifact. A republish of an existing version is therefore still silent.
@@ -209,17 +211,27 @@ Practical consequence for whoever reads a chart PR: the analysis will never be i
### What is analysed
The languages were checked against SonarQube's list, not assumed — the fit turned out better than expected, Shell included.
Measured in run 375, not read off a language list. The earlier version of this section *was* read off a language list and promised more than the instance delivers — the correction below is the whole point of the section now.
| 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 server loaded exactly **two quality profiles, `json` and `yaml`**. The sensors that did work were the **IaC Kubernetes sensor** (3.4 s) and the **secrets detection** (`TextAndSecretsSensor`) over all **391 indexed files**.
**The last row is the reason to bother.** The premise of this repository is that a credential never lands in it — every secret is a 1Password `OnePasswordItem` reference, materialised by the operator in the cluster — and until now *nothing checked that premise*. Every other row is a second opinion on something `helm lint --strict`, the render test or Trivy already touch; this one covers an assumption that was carried for two years on discipline alone.
| What is in this repository | Analyser | Runs here? |
| --- | --- | --- |
| 238 YAML files and 26 Helm template directories | IaC Kubernetes, YAML | yes |
| 26 `values.schema.json` | JSON | yes |
| the whole tree, 391 indexed files | Secrets | yes |
| the 5 shell scripts under `ci/` — the whole CI logic | Shell | **no** |
| the 30 workflow files | GitHub Actions | **no** |
**Why the last two do not run, and why no change here can make them:** analysers are **server-side plugins**. The scanner downloads a CLI, not a set of languages; what gets analysed is decided by what the SonarQube instance has installed. The language list that promised Shell and GitHub Actions describes the newest SonarQube release, and this instance does not carry those plugins. Nothing in `sonar-project.properties` and nothing in the shared block changes that.
**The consequence, said plainly because it inverts the original expectation:** the files that carry the actual logic of this repository — the five shell scripts under `ci/` and the thirty workflow files — are **not analysed at all**. What *is* analysed is the declarative part: IaC rules over the charts, and secret detection over everything. For the scripts, `shellcheck` and review remain the only check, as they were before Sonar existed here.
Whether the instance is upgraded or has those plugins added is a decision for whoever operates SonarQube, not something this repository settles. Noted here so the gap is known, not as a task on this side.
**The secrets row is the reason to bother, and it does run.** The premise of this repository is that a credential never lands in it — every secret is a 1Password `OnePasswordItem` reference, materialised by the operator in the cluster — and until now *nothing checked that premise*. The two surviving rows above it are a second opinion on something `helm lint --strict`, the render test or Trivy already touch; this one covers an assumption carried for two years on discipline alone.
**What it reports is still unknown.** Run 375 built the report — 603 kB — and never uploaded it, because the upload is exactly where the wrong project key failed. The log names no individual findings. The first successful run is what shows them, and that is the most interesting part of this whole addition.
### No exclusions, for the first round
@@ -234,24 +246,39 @@ If the first run produces noise genuinely not worth reading, the exclusions go i
| Name | Art | |
| --- | --- | --- |
| `SONAR_HOST_URL` | repository variable | Set on the instance. A **variable and not a secret** on purpose: a server address is not a credential, and as a variable it stays readable in the log — which is the only thing that makes a wrong address diagnosable. Masked, it would produce a failure against an address nobody can see, the exact situation that forced an extra masking step into the publish block. |
| `SONAR_TOKEN` | repository secret | Confirmed present by the maintainer. Reaches the block through `secrets: inherit`. Needs the right to run analyses in SonarQube, and the right to create a project if the first run is to create it. |
| `sonar-project.properties` | file in the repo root | Project key `jlab-cluster_helm-charts`. The key carries the owner because the instance serves two (`jlab-cluster` and `suer`); a bare `helm-charts` is free today and collides the day the second owner has a repository of that name — and a key cannot be changed afterwards without losing the project's history. |
| `SONAR_TOKEN` | repository secret | Confirmed present by the maintainer, and it reached the scanner in run 375. Reaches the block through `secrets: inherit`. Needs the **"Execute Analysis"** permission on the project in SonarQube. |
| `sonar-project.properties` | file in the repo root | Project key **`JLab-Helm-Charts`**, and **no `sonar.projectName`**. See below — both halves of that line were learned the hard way. |
Secrets and variables are not readable from outside, including by the bot: `tea actions secrets ls` answers `user should be the owner of the repo`, and variables are not listed at all. So "set" here means confirmed by the maintainer, not verified by a tool — and the block's guard is what turns a wrong assumption into a named error instead of a puzzling failure.
The guard covers an empty variable, an empty secret and a forgotten `secrets: inherit`, and it checks both values before it exits, so a repository that has set up neither learns both in one run. What it cannot cover is a **missing** required secret: that fails job **creation** on this instance, not the job, and the run then shows no steps at all — the same quirk as everywhere else here, and the understandable case for this block, because without a token there is no analysis to run.
### Three things the first run answers
### The project key, and what the first run cost
None of these is measured, and the merge of the change that added the hull is itself the first run — the trigger is a push to `main`, and a merge is such a push. Unlike the [publish route](#proving-the-path-after-a-change-to-the-publishing-route), which needs a manual dispatch because it touches no chart directory, this one proves itself on arrival.
**Project keys are not derivable.** The key here is `JLab-Helm-Charts`, because that is what the project already has on the SonarQube instance; it was given by the maintainer and read off the server. The first version of this file carried a *guessed* key, `jlab-cluster_helm-charts`, reasoned from the instance serving two owners — plausible, and wrong, and the run was red for that one reason:
1. **Is `node24` in the runner image?** The scan action declares `runs: using: node24` and is therefore a JavaScript action. If it is absent, the failure will read as a problem with the action, not with the image — which is the whole reason this is written down.
2. **Does the scanner download get through?** The action fetches the Sonar Scanner CLI from `binaries.sonarsource.com` at run time and checks its GPG signature; measured at ~59 MiB. This is the likeliest first failure, and it has history: a 17 MB download once failed on this runner because a Docker bridge at MTU 1500 met a pod network of 1450 — see [What a GitHub runner had been providing quietly](#what-a-github-runner-had-been-providing-quietly). That was fixed on the node side, and the scanner is a multiple of it.
3. **Does a repository variable reach a workflow called from another repository?** The same open question the publish route still carries for `OP_CONNECT_HOST`, and this run answers it for both. If it does not arrive, the answer is loud: the guard fires and names the variable. The repair would be the one already made for the owner segment over there — pass the address in as an input, evaluated in the caller.
```
ERROR You're not authorized to analyze this project or the project doesn't
exist on SonarQube and you're not authorized to create it.
```
Plus the one that is not a yes/no: **what the analysis actually reports**, in particular out of the Helm templates. That is what decides whether exclusions are needed, and it belongs in `sonar-project.properties` when it does.
**That message does not distinguish a wrong key from a missing permission** — the server answers the same way for both, which is precisely what made a guessed key expensive to diagnose. Now that the key is known to be right, the same error means the other half: the token lacks **"Execute Analysis"** on that project.
`workflow_dispatch` is on the hull for the retries these may need. Without it every attempt would cost a dummy commit on `main`. Only the maintainer can trigger it — the bot account cannot dispatch workflows.
**And no `sonar.projectName`.** Once the project exists on the server, the display name belongs to the server. A name sent along with the analysis overwrites it on every run — so a rename made in SonarQube would silently snap back, with no failure anywhere to report it. The reason sits as a comment in `sonar-project.properties`, because otherwise the next person adds the line back as an obvious omission.
### Three things the first run answered — all three positively
Run 375, a push to `main`. It was red at the upload, but everything before the upload ran, which is what makes it a measurement.
1. **Is `node24` in the runner image?** **Yes** — the scan action is a JavaScript action declaring `runs: using: node24`, and it ran. Had it been absent, the failure would have read as a problem with the action rather than with the image, which is why this was written down in advance.
2. **Does the scanner download get through?** **Yes** — the archive and its GPG signature, ~59 MiB, in roughly **three seconds**. This was the expected first failure, and it had history: a 17 MB download once failed on this runner because a Docker bridge at MTU 1500 met a pod network of 1450 (see [What a GitHub runner had been providing quietly](#what-a-github-runner-had-been-providing-quietly)). The node-side fix holds at better than three times that size; the MTU incident does not repeat.
3. **Does a repository variable reach a workflow called from another repository?** **Yes.** The guard passed and the scanner started, which it could not have done against an empty `vars.SONAR_HOST_URL` — the block checks the variable and exits first. **This also answers it for `OP_CONNECT_HOST` in the publish block:** same forge version, same mechanism, a repository variable read inside a workflow called from another repository. No second measurement needed there, and the contingency plan — passing the address in as an input, as was done for the owner segment — is not needed either.
All three are recorded on [`Gemessenes-Verhalten`](https://git.jk-dev.de/workflow-util/pipelines/wiki/Gemessenes-Verhalten) in `workflow-util/pipelines`, which is where measurements about the forge itself belong; they are repeated here only because the run happened on this side.
**Still open, and it is the interesting one: what the analysis actually reports.** The report was built (603 kB) and never uploaded, so no finding — from the Helm templates or from the secrets detection — has been seen yet. That is also what decides whether exclusions are needed, and they would go into `sonar-project.properties`.
`workflow_dispatch` is on the hull for exactly these retries, and it is what proves the corrected key: one dispatch, no dummy commit on `main`. Only the maintainer can trigger it — the bot account cannot dispatch workflows.
## `ci/helm-dependencies.sh` — the step they all share