3
sonar analysis
Justin Klein edited this page 2026-10-02 00:22:38 +00:00

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 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. Die ist dort vorhanden: node24 ist in Lauf 375 gemessen, siehe Gemessenes-Verhalten.

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).

Das war die erwartete 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 Download kommt durch. In Lauf 375 waren Archiv und Signatur in etwa drei Sekunden da; die knotenseitige Behebung hält beim Dreifachen der damaligen Größe. Nachzulesen unter Gemessenes-Verhalten. Wenn ein Lauf dieses Bausteins trotzdem einmal beim Download hängt oder abbricht, ist die MTU-Geschichte 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 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 — 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.

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, 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 stand hier mit vier Punkten. Drei davon hat Lauf 375 des ersten Abnehmers (jlab-cluster/helm-charts, Push auf main) beantwortet, alle drei positiv — node24 im Runner-Image, der 59-MiB-Scanner-Download, und dass eine Repository-Variable einen aus einem fremden Repository aufgerufenen Workflow erreicht. Sie stehen jetzt dort, wo Gemessenes hingehört: auf Gemessenes-Verhalten, mit je einem Abschnitt. Der dritte hat dabei dieselbe Frage für OP_CONNECT_HOST in publish-helm-chart mitbeantwortet.

Lauf 399 des ersten Abnehmers hat den Baustein inzwischen vollständig durchlaufen — 44 Sekunden, ANALYSIS SUCCESSFUL, mit Bericht auf dem Server. Damit ist am Baustein selbst nichts mehr offen; was bleibt, ist eine Eigenschaft der jeweiligen Instanz und des jeweiligen Projekts:

Offen bleibt genau einer, und es ist der inhaltliche:

  1. Was eine Analyse überhaupt meldet, insbesondere aus Helm-Templates. Der 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.

    Lauf 375 hat das nicht beantwortet, obwohl der Scan lief: Der Bericht wurde erzeugt — 603 kB — und nie hochgeladen, weil der Upload an einem falschen Projektschlüssel scheiterte. Das Protokoll nennt keine Einzelbefunde. Es braucht also einen erfolgreichen Lauf, nicht nur einen gestarteten.

    Lauf 399 war dieser erfolgreiche Lauf — und auch er beantwortet die Frage nicht aus dem Protokoll heraus. Ein Scanner-Protokoll nennt grundsätzlich keine Einzelbefunde, es nennt die Adresse des Berichts. Und die Instanz antwortet auf api/measures/component anonym mit 401, während die Oberfläche 200 liefert — die richtige Einstellung, aber sie heißt: Die Zahlen bekommt nur, wer ein Konto dort hat. Diese Frage ist damit keine Messung, die noch aussteht, sondern eine, die aus einer Pipeline grundsätzlich nicht zu beantworten ist. Wer wissen will, ob Helm-Templates Rauschen erzeugen, sieht auf das Dashboard.

Eine Sache, die nicht auf der Liste stand und auf ihr hätte stehen müssen: welche Analysatoren die Instanz tatsächlich hat. Analysatoren sind server-seitige Plugins; der Scanner lädt eine CLI, keine Sprachen. Eine Sprachliste aus der Dokumentation beschreibt die neueste SonarQube-Fassung und sagt über eine konkrete Instanz nichts. Lauf 375 lud zwei Qualitätsprofile, json und yaml; gearbeitet haben der IaC-Kubernetes-Sensor und die Geheimnis-Erkennung. Shell und GitHub Actions waren nicht dabei, obwohl der erste Abnehmer dafür geplant war. Jeder Aufrufer dieses Bausteins muss also damit rechnen, dass ein Teil seines Baums ungeprüft bleibt — und was geprüft wird, steht erst nach dem ersten Lauf fest. Dieser Baustein kann daran nichts ändern, und sonar-project.properties auch nicht.