Langform zu sonar-analysis aufnehmen

Neue Seite in der Reihe der Bausteine, dazu die Zeile in der Tabelle auf Home.

Die Seite traegt, was im README keinen Platz hat: warum die Analyse eine
Beobachtung und bewusst kein Torwaechter ist und unter welchen zwei Bedingungen
sich das aendert, warum fetch-depth: 0 nicht optional ist, warum kein
Docker-Runner noetig ist (die Scan-Action ist eine JavaScript-Action), was der
Scanner zur Laufzeit holt und warum dieser Download die wahrscheinlichste
Stelle fuer den ersten Fehlschlag ist, sowie die Trennung von Variable und
Secret.

Am Ende die Liste dessen, was an diesem Baustein noch nicht gemessen ist -
node24 im Runner-Image, der Scanner-Download, ob vars in einem von fremd
aufgerufenen Workflow ankommt, und was der erste Lauf aus den Helm-Templates
meldet. Sie steht dort, damit niemand sie zweimal zusammentragen muss, und
gehoert nach dem ersten Lauf auf Gemessenes-Verhalten umgezogen.
jklein committed 2026-10-01 23:33:27 +00:00
1 parent c2a7fda791
commit a1a4b7e924
2 files changed
+175

No files matched your search

+1
@@ -10,6 +10,7 @@ Repositories über `uses:` aufgerufen (`on: workflow_call`).
| --- | --- | | --- | --- |
| [docker-build-push](docker-build-push) | Baut ein Docker-Image und schiebt es als Multi-Arch-Manifest in eine Registry. Mit abgeschaltetem Push dient derselbe Baustein als Bau-Prüfung. | | [docker-build-push](docker-build-push) | Baut ein Docker-Image und schiebt es als Multi-Arch-Manifest in eine Registry. Mit abgeschaltetem Push dient derselbe Baustein als Bau-Prüfung. |
| [publish-helm-chart](publish-helm-chart) | Packt ein Helm-Chart und lädt es in eine klassische, indexbasierte Helm-Registry. | | [publish-helm-chart](publish-helm-chart) | Packt ein Helm-Chart und lädt es in eine klassische, indexbasierte Helm-Registry. |
| [sonar-analysis](sonar-analysis) | Schickt den Baum des aufrufenden Repositories durch eine SonarQube-Analyse. Eine Beobachtung des Zustands, bewusst kein Torwächter. |
Dazu eine Seite quer zu beiden: [Gemessenes-Verhalten](Gemessenes-Verhalten) — Dazu eine Seite quer zu beiden: [Gemessenes-Verhalten](Gemessenes-Verhalten) —
die Eigenheiten dieser Forge, die hier Geld gekostet haben, mit dem Lauf oder die Eigenheiten dieser Forge, die hier Geld gekostet haben, mit dem Lauf oder
+174
@@ -0,0 +1,174 @@
# sonar-analysis
`.gitea/workflows/sonar-analysis.yml` — schickt den Baum des aufrufenden
Repositories durch eine SonarQube-Analyse. Die Eingabe-Tabelle, das
Aufrufbeispiel und die Voraussetzungen stehen im README; diese Seite begründet
den Baustein und benennt, was an ihm **nicht** gemessen ist.
Der Baustein ist bewusst klein: ein Wächter, ein Checkout, die offizielle
Scan-Action. Alles Projektspezifische — Projektschlüssel, Quellpfade,
Ausschlüsse — steht in der `sonar-project.properties` des aufrufenden
Repositories. Genau deshalb ist er teilbar: Der Workflow ist für jedes
Repository identisch, und ein zweiter Abnehmer kostet eine Datei mit drei
Zeilen.
## Die Analyse ist eine Beobachtung, kein Torwächter
Das ist die wichtigste Eigenschaft des Bausteins, und sie folgt aus dem
Auslöser.
Die SonarQube Community Build analysiert **ausschließlich den Hauptzweig**. Eine
PR-Analyse gibt es dort nicht, der Aufruf hängt deshalb an einem Push auf
`main`, und damit erscheint ein Befund **nach** dem Merge. Zu diesem Zeitpunkt
ist nichts mehr zu blockieren.
Daraus folgt das fehlende Quality-Gate, und es fehlt absichtlich. Ein rotes Gate
hinter dem Merge färbte nur den Hauptzweig rot, ohne irgendetwas verhindert zu
haben — und einen Dauerzustand lernt man zu ignorieren. Der Schaden wäre nicht
das Gate selbst, sondern dass es die Glaubwürdigkeit der übrigen Prüfungen
mitnimmt: Wer gewöhnt ist, dass ein rotes `main` nichts bedeutet, liest auch die
nächste echte Rotfärbung als Rauschen.
Sinnvoll wird ein Gate unter genau einer von zwei Bedingungen, und beide ändern
die Lage wirklich:
- **PR-Analyse ist verfügbar.** Dann blockiert das Gate einen Merge, und das ist
eine Entscheidung, die es tragen kann.
- **Ein Deployment hängt an diesem Workflow.** Dann bewacht es etwas Konkretes.
Bis dahin ist das Hinzufügen eines Gates keine Nachbesserung, sondern eine
Verschlechterung. Im Workflow steht das als Kommentar an der Stelle, an der der
Schritt stünde — damit niemand ihn als Versehen nachträgt.
## `fetch-depth: 0` ist nicht optional
Die eine Checkout-Option, ohne die die Analyse ihren Zweck verliert.
Ein flacher Klon — die Vorgabe — liefert dem Scanner keine Historie. Er kann
Befunde dann keiner Änderung zuordnen, sondern schreibt sie alle dem letzten
Commit zu. Was damit unbrauchbar wird, ist nicht eine Randfunktion, sondern das
Meiste, wofür man SonarQube überhaupt aufstellt: die Zuordnung zu Autor und
Änderung, der Zeitraum „neuer Code", jedes datumsbasierte Maß. Ein Bericht, in
dem alles gleich alt und von derselben Person ist, sagt nichts.
Der Preis ist ein langsamerer Checkout. Das ist der ganze Preis.
## Warum kein Docker-Runner nötig ist
Der Nachbarbaustein [docker-build-push](docker-build-push) braucht den Runner
mit dem Label `ubuntu-docker`, weil er einen Docker-Socket benutzt. Dieser hier
braucht ihn nicht, und das ist nachgesehen und nicht vermutet: Die Scan-Action
deklariert `runs: using: node24`. Sie ist damit eine **JavaScript-Action**, läuft
im Node des Runners und nicht in einem Container. Kein Socket, kein Daemon,
`ubuntu-latest` genügt.
Das hat eine Kehrseite, die man im Blick behalten muss. Eine JavaScript-Action
verlangt ihre Node-Hauptversion **im Runner-Image**. Ob diese dort vorhanden
ist, ist **nicht geprüft** — siehe unten.
## Was der Scanner zur Laufzeit holt
Die Action bringt den Sonar-Scanner-CLI nicht mit. Sie **lädt ihn bei jedem
Lauf** von `binaries.sonarsource.com` und prüft dessen GPG-Signatur. Das ist
nichts Kleines: Bei der Messung für diese Seite lag das Linux-Archiv bei rund
**59 MiB** (`Content-Length` 61 626 644).
Damit ist das die wahrscheinlichste Stelle für den ersten Fehlschlag auf dieser
Infrastruktur, und zwar aus einem konkreten, bereits einmal bezahlten Grund. Im
README des ersten Abnehmers steht gemessen: Auf diesem Runner kam einmal eine
**17-MB-Datei nicht durch**, weil eine Docker-Brücke mit MTU 1500 gegen ein
Pod-Netz mit 1450 lief. Kleine Antworten passten in ein Paket, die große nicht —
und es sah aus wie ein gesperrter Host, nicht wie ein MTU-Problem. Behoben wurde
das auf der Knoten-Seite, nicht in einem Workflow.
Der Scanner-CLI ist ein Mehrfaches dieser 17 MB. Wenn ein Lauf dieses Bausteins
beim Download hängt oder abbricht, ist das die erste Spur, die man verfolgt —
und man sucht sie auf der Knoten-Seite, nicht im YAML.
Zwei weitere Folgen des Laufzeit-Downloads, nebenbei: Der Baustein hängt an der
Erreichbarkeit eines fremden Hosts, und er hängt an einer Signaturprüfung. Beide
können unabhängig von allem hier scheitern.
## Die Adresse ist eine Variable, das Token ein Secret
`SONAR_HOST_URL` ist eine **Variable** des aufrufenden Repositories,
`SONAR_TOKEN` ein **Secret**. Die Trennung ist keine Formsache:
Die Adresse des Servers ist kein Zugangsdatum. Als Variable bleibt sie im
Protokoll lesbar — und erst das macht eine falsche Adresse überhaupt
diagnostizierbar. Als Secret wäre sie maskiert, und ein Fehler gegen eine
maskierte Adresse ist nicht zu deuten; genau diese Lage hat bei
[publish-helm-chart](publish-helm-chart) einen eigenen Maskierungsschritt
erzwungen.
Beides gehört dem Aufrufer und nicht diesem Repository. Ein geteilter Baustein
trägt weder die Infrastruktur-Adresse eines einzelnen Mandanten noch dessen
Token — dieselbe Hausregel, aus der bei den anderen Bausteinen der
Eigentümer-Zwang folgt.
## Der Wächter, und was er nicht fängt
Der Preis der vorigen Entscheidung ist eine Voraussetzung, die man vergessen
kann, und eine nicht gesetzte Variable kommt als **leere Zeichenkette** an. Eine
leere Adresse scheitert nicht dort, wo sie gesetzt wird, sondern einen Schritt
später in der Scan-Action, gegen eine Adresse, die niemand sieht. Deshalb steht
der Wächter zuerst und benennt die Einstellung, den Ort und die erwartete Form.
Dasselbe Muster wie bei `OP_CONNECT_HOST` in
[publish-helm-chart](publish-helm-chart) — es ist inzwischen die Hausform.
Der Wächter prüft **beide** Werte, bevor er abbricht. Ein Repository, das keinen
von beiden eingerichtet hat, erfährt das sonst in zwei Läufen statt in einem,
und der zweite kostet genauso viel Aufmerksamkeit wie der erste.
Beim Token fängt er nur zwei der drei Fälle: ein vorhandenes, aber leeres
Secret, und einen Aufrufer, der `secrets: inherit` vergessen hat. Beide kommen
als leere Zeichenkette an. Ein **ganz fehlendes** Secret kommt nie so weit — der
Workflow deklariert es als `required`, und ein fehlendes Pflicht-Secret lässt auf
dieser Instanz die **Job-Erzeugung** scheitern, nicht den Job; der Lauf steht
dann ohne Schritte da. Nachzulesen unter
[Gemessenes-Verhalten](Gemessenes-Verhalten).
Dass `required: true` hier trotzdem richtig ist, folgt der dort aufgeschriebenen
Linie: **Jeder** Aufrufer braucht das Token, ohne Token gibt es keine Analyse.
Ein Lauf ohne Schritte ist dann der verständliche Fall. `required: false` wäre
nur richtig, wenn ein Teil des Bausteins ohne das Secret arbeiten könnte — hier
tut keiner das.
## Gepinnt auf Commits, nicht auf Zweige
Beide Actions sind auf einen Commit gepinnt, nicht auf einen Zweig und nicht auf
eine Hauptversion. Begründung und der Fall, an dem sie hängt, stehen unter
[Gemessenes-Verhalten](Gemessenes-Verhalten), Abschnitt „Wandernde
Action-Verweise": Ein beweglicher Verweis holt bei jedem Lauf den neuesten Stand
und ändert das Verhalten aller Aufrufer, ohne dass hier etwas angefasst wurde.
## Was an diesem Baustein nicht gemessen ist
Diese Liste ist der eigentliche Grund für die Seite. Sie steht hier, damit
niemand sie zweimal zusammentragen muss, und sie gehört abgearbeitet, sobald der
erste Lauf da ist — die Ergebnisse dann auf
[Gemessenes-Verhalten](Gemessenes-Verhalten).
1. **Ist `node24` im Runner-Image vorhanden?** Die Scan-Action verlangt es. Wenn
nicht, scheitert sie — und zwar mit einer Meldung über die Action, nicht über
das Image. Wer das ohne diese Zeile liest, sucht den Fehler in der Action.
2. **Kommt der Scanner-Download durch?** Siehe oben; die wahrscheinlichste
Stelle, und eine mit Vorgeschichte.
3. **Kommt `vars.SONAR_HOST_URL` in einem aus einem fremden Repository
aufgerufenen Workflow an?** Dass der Variablen-Kontext der des Aufrufers
bleibt, ist dokumentiert, auf dieser Forge-Fassung aber nicht gemessen —
dasselbe Offene wie bei `OP_CONNECT_HOST` in
[publish-helm-chart](publish-helm-chart), wo es ebenfalls noch unbelegt ist.
Wenn es nicht ankommt, ist das immerhin laut: Der Wächter greift und benennt
die Variable. Das Gegenstück dort hat sich für die andere Richtung
entschieden — der Eigentümer ist eine **Eingabe**, weil er im Aufrufer
ausgewertet und nicht auf eine Kontext-Semantik gesetzt wird. Käme `vars`
nicht an, wäre dieselbe Umstellung auch hier der Weg: die Adresse als
Eingabe.
4. **Was der erste Lauf überhaupt meldet**, insbesondere aus Helm-Templates. Der
Helm-Analysator trifft dort auf Dateien, die wegen `{{ }}` kein gültiges YAML
sind. Was er damit macht, bekommt man nur heraus, indem man ihn einmal
darauf laufen lässt — deshalb beginnt der erste Abnehmer **ohne Ausschlüsse**.
Das Ergebnis entscheidet, ob Ausschlüsse nachträglich nötig sind, und es
gehört in die `sonar-project.properties` des jeweiligen Repositories, nicht
in diesen Baustein.