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.
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. |
|
||||
| [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) —
|
||||
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.
|
||||
Reference in new issue
Block a user