SonarQube-Analyse als geteilten Baustein aufnehmen #8
No files matched your search
@@ -0,0 +1,145 @@
|
||||
name: SonarQube Analysis
|
||||
|
||||
# Called through "uses:" only, never triggered here - there is nothing in this
|
||||
# repository worth analysing, and a dispatch here would run against these files.
|
||||
#
|
||||
# The caller decides WHEN the analysis runs, through its own "on:". On the
|
||||
# SonarQube Community Build only the main branch is analysed, so that is in
|
||||
# practice a push to main, and the consequence belongs said out loud: a finding
|
||||
# appears AFTER the merge. This block observes a state, it does not guard a
|
||||
# gate - see the note on the missing quality-gate step at the bottom.
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
# Both inputs are type: string, and that is not an oversight waiting to be
|
||||
# cleaned up. On this Gitea version (measured on 1.27.0, issue #4) a
|
||||
# boolean workflow_call input does not reach the called workflow at all -
|
||||
# neither its default nor an explicitly passed value - while the run stays
|
||||
# green. Every input of every block here is therefore a string, even one
|
||||
# that is a switch in meaning. Whoever "fixes" that later brings the fault
|
||||
# back silently.
|
||||
args:
|
||||
required: false
|
||||
type: string
|
||||
default: ""
|
||||
description: >-
|
||||
Additional arguments for the Sonar Scanner CLI, passed through to the
|
||||
scan action's "args" input. Everything project-specific belongs in the
|
||||
sonar-project.properties of the calling repository, not here; this is
|
||||
for the rest, e.g. -Dsonar.verbose=true for one diagnostic run.
|
||||
# The scan action's own default for projectBaseDir is ".", and this input
|
||||
# repeats it instead of defaulting to the empty string: an empty value
|
||||
# would be passed on and would override the action's default rather than
|
||||
# leave it alone.
|
||||
project-base-dir:
|
||||
required: false
|
||||
type: string
|
||||
default: "."
|
||||
description: >-
|
||||
Directory the analysis starts from, i.e. sonar.projectBaseDir. The
|
||||
sonar-project.properties is looked up relative to it.
|
||||
secrets:
|
||||
# required: true, and that is a real decision on this forge, not a
|
||||
# formality: a missing secret that the called workflow declares as
|
||||
# required fails JOB CREATION, not the job - the run then stands there
|
||||
# with no steps at all and nothing in it names the cause. Measured in
|
||||
# jlab-cluster/helm-charts, written up on the wiki page
|
||||
# "Gemessenes-Verhalten".
|
||||
#
|
||||
# It is the right setting here all the same, because every caller needs
|
||||
# the token: without it there is no analysis to run, so a run without
|
||||
# steps is the understandable case - exactly this prerequisite is missing.
|
||||
# (required: false would be right only if a part of the block worked
|
||||
# without it.)
|
||||
SONAR_TOKEN:
|
||||
required: true
|
||||
jobs:
|
||||
sonar-analysis:
|
||||
name: SonarQube Analysis
|
||||
# ubuntu-latest is enough, and that is worth a line because the sibling
|
||||
# block docker-build-push needs the ubuntu-docker runner: the scan action
|
||||
# declares "runs: using: node24", so it is a JavaScript action and runs in
|
||||
# the runner's Node, not in a container. No Docker socket is involved, and
|
||||
# ubuntu-docker is the only runner here that has one.
|
||||
#
|
||||
# What it does need is node24 in the runner image. That is NOT verified; if
|
||||
# it is absent the action fails, and it fails with a message about the
|
||||
# action rather than about the image.
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
# Both settings belong to the CALLING repository, not to this file: a
|
||||
# shared block does not carry one tenant's infrastructure address, and it
|
||||
# certainly does not carry anyone's token. The price of that is two
|
||||
# prerequisites that can be forgotten, and an unset variable arrives as an
|
||||
# empty string.
|
||||
#
|
||||
# An empty SONAR_HOST_URL does not fail here - it fails one step later,
|
||||
# inside the scan action, against an address nobody can see. So the guard
|
||||
# comes first and names the setting, the place to set it and the shape of
|
||||
# the value. The publish block needed the same pattern for
|
||||
# OP_CONNECT_HOST.
|
||||
#
|
||||
# The token is checked in the same step even though a MISSING one never
|
||||
# gets this far (see the secrets block above): what this catches is a
|
||||
# secret that exists and is empty, and a caller that forgot
|
||||
# "secrets: inherit". Both arrive as an empty string, both would otherwise
|
||||
# surface as an unauthenticated request against the server.
|
||||
#
|
||||
# Both are checked before either exits, so a repository that has set up
|
||||
# neither learns both in one run instead of in two.
|
||||
- name: "Check the calling repository's prerequisites"
|
||||
env:
|
||||
SONAR_HOST_URL: ${{ vars.SONAR_HOST_URL }}
|
||||
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
|
||||
run: |-
|
||||
missing=0
|
||||
if [ -z "$SONAR_HOST_URL" ]; then
|
||||
echo "::error::the variable SONAR_HOST_URL is not set in the calling repository (Settings -> Actions -> Variables, or on the organisation). It must hold the address of the SonarQube server, e.g. https://sonar.example.com - no trailing path. This workflow deliberately does not hardcode a host."
|
||||
missing=1
|
||||
fi
|
||||
if [ -z "$SONAR_TOKEN" ]; then
|
||||
echo "::error::the secret SONAR_TOKEN is empty in the calling repository (Settings -> Actions -> Secrets, or on the organisation), or the caller did not pass it on with 'secrets: inherit'. It must hold a SonarQube token permitted to run analyses - and to create the project, if the first run is supposed to create it."
|
||||
missing=1
|
||||
fi
|
||||
[ "$missing" -eq 0 ] || exit 1
|
||||
# fetch-depth: 0, and this is the one checkout option the analysis cannot
|
||||
# do without. With the default shallow clone the scanner has no history to
|
||||
# attribute lines to, so every finding is reported against the last commit
|
||||
# instead of the change that introduced it: blame, the "new code" period
|
||||
# and every date-based measure become unusable. The clone is slower, which
|
||||
# is the whole price.
|
||||
#
|
||||
# Pinned to a commit, not to a tag or a branch: a moving reference pulls
|
||||
# the newest state into every run of every caller, and the resulting
|
||||
# failure shows up at a caller that changed nothing (issue #5).
|
||||
- name: "Checkout Repository"
|
||||
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
|
||||
with:
|
||||
fetch-depth: 0
|
||||
# The action downloads the Sonar Scanner CLI from binaries.sonarsource.com
|
||||
# at run time and verifies its GPG signature. That download is around
|
||||
# 59 MiB, and it is the most likely place for a first failure on this
|
||||
# infrastructure - the wiki page for this block says why.
|
||||
- name: "SonarQube Scan"
|
||||
uses: SonarSource/sonarqube-scan-action@7006c4492b2e0ee0f816d36501671557c97f5995 # v8.1.0
|
||||
env:
|
||||
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
|
||||
# A variable of the caller, not a secret: the address of the server is
|
||||
# not a credential, and as a variable it stays readable in the log,
|
||||
# which is what makes a wrong one diagnosable at all.
|
||||
SONAR_HOST_URL: ${{ vars.SONAR_HOST_URL }}
|
||||
with:
|
||||
args: ${{ inputs.args }}
|
||||
projectBaseDir: ${{ inputs.project-base-dir }}
|
||||
# There is deliberately NO quality-gate step here, and it is missing on
|
||||
# purpose rather than by oversight.
|
||||
#
|
||||
# With an after-the-merge trigger there is nothing left to block. A red
|
||||
# gate would only colour the main branch red, and a permanent red is a
|
||||
# thing people learn to ignore - at which point the gate has cost the
|
||||
# credibility of every other check without having stopped anything.
|
||||
#
|
||||
# It becomes the right thing under one of two conditions: pull-request
|
||||
# analysis is available (then the gate blocks a merge, which is a decision
|
||||
# it can actually carry), or a deployment hangs off this workflow (then it
|
||||
# guards something concrete). Until then, adding it is a regression.
|
||||
@@ -269,6 +269,91 @@ Der Registry-Zugang selbst liegt in 1Password
|
||||
(`GitHub-Actions/jlab-registry-access`: `username`, `password`, `charts-url`) und
|
||||
muss in Gitea nicht hinterlegt werden.
|
||||
|
||||
### `.gitea/workflows/sonar-analysis.yml`
|
||||
|
||||
Schickt den Baum des aufrufenden Repositories durch eine **SonarQube-Analyse**.
|
||||
Der Baustein checkt mit vollständiger Historie aus und ruft dann die offizielle
|
||||
Scan-Action auf; alles Projektspezifische — Projektschlüssel, Ausschlüsse,
|
||||
Quellpfade — steht in der `sonar-project.properties` des aufrufenden
|
||||
Repositories, nicht hier. Läuft auf `ubuntu-latest`.
|
||||
|
||||
**Die Analyse ist kein Torwächter.** Es gibt bewusst keinen
|
||||
Quality-Gate-Schritt: Auf der SonarQube Community Build wird nur der Hauptzweig
|
||||
analysiert, der Auslöser liegt damit **nach** dem Merge, und dort ist nichts
|
||||
mehr zu blockieren. Ein rotes Gate färbte nur `main` rot, und einen Dauerzustand
|
||||
lernt man zu ignorieren. Sinnvoll wird es erst, wenn PR-Analyse verfügbar ist
|
||||
oder ein Deployment an diesem Workflow hängt — bis dahin ist ein Gate hier eine
|
||||
Verschlechterung und keine vergessene Zeile. Ein Befund ist eine Beobachtung des
|
||||
Zustands, und wer ihn sehen will, schaut in SonarQube.
|
||||
|
||||
#### Eingaben
|
||||
|
||||
| Eingabe | Pflicht | Vorgabe | Bedeutung |
|
||||
| ------------------ | ------- | ------- | --------- |
|
||||
| `args` | nein | — | Zusätzliche Argumente für den Sonar-Scanner-CLI, unverändert an die Scan-Action weitergegeben. Für einen einmaligen Diagnoselauf, etwa `-Dsonar.verbose=true` |
|
||||
| `project-base-dir` | nein | `.` | Verzeichnis, in dem die Analyse beginnt (`sonar.projectBaseDir`); die `sonar-project.properties` wird relativ dazu gesucht |
|
||||
|
||||
Beide Eingaben sind `type: string`. Das ist kein Versehen: Boolesche
|
||||
`workflow_call`-Eingaben kommen auf dieser Gitea-Version nicht an — bei grünem
|
||||
Lauf. Siehe
|
||||
[Issue #4](https://git.jk-dev.de/workflow-util/pipelines/issues/4) und die
|
||||
Begründung im Abschnitt zu `push` oben.
|
||||
|
||||
Die Vorgabe von `project-base-dir` ist `.` und nicht leer, weil die Scan-Action
|
||||
selbst `.` als Vorgabe hat: Ein leerer Wert würde durchgereicht und überschriebe
|
||||
diese Vorgabe, statt sie stehen zu lassen.
|
||||
|
||||
#### Aufrufbeispiel
|
||||
|
||||
```yaml
|
||||
name: SonarQube
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
|
||||
jobs:
|
||||
sonar:
|
||||
uses: workflow-util/pipelines/.gitea/workflows/sonar-analysis.yml@v4
|
||||
secrets: inherit
|
||||
```
|
||||
|
||||
`secrets: inherit` ist notwendig — der Workflow braucht `SONAR_TOKEN` aus dem
|
||||
aufrufenden Repository.
|
||||
|
||||
Der Baustein löst sich nicht selbst aus. Wann analysiert wird, entscheidet die
|
||||
Hülle im aufrufenden Repository über ihr eigenes `on:` — üblicherweise ein
|
||||
`push` auf `main`, weil die Community Build ausschließlich den Hauptzweig
|
||||
analysiert.
|
||||
|
||||
#### Voraussetzungen im aufrufenden Repository
|
||||
|
||||
| Name | Art | Zweck |
|
||||
| ------------------------- | ------------------- | ----- |
|
||||
| `SONAR_HOST_URL` | Variable | Adresse des SonarQube-Servers, etwa `https://sonar.example.com` — ohne Pfad dahinter |
|
||||
| `SONAR_TOKEN` | Secret | Token mit dem Recht, Analysen auszuführen; soll das Projekt beim ersten Lauf selbst entstehen, zusätzlich das Recht zum Anlegen. Kommt über `secrets: inherit` |
|
||||
| `sonar-project.properties` | Datei im Repository | Trägt mindestens `sonar.projectKey`. Der Schlüssel sollte den Eigentümer enthalten (`<owner>_<repo>`), weil die Instanz mehrere Eigentümer bedient und ein nackter Repository-Name beim zweiten kollidiert |
|
||||
|
||||
Dass die Adresse eine **Variable** ist und kein Secret, ist Absicht: Sie ist
|
||||
kein Zugangsdatum, und als Variable bleibt sie im Protokoll lesbar — erst das
|
||||
macht eine falsche Adresse überhaupt diagnostizierbar.
|
||||
|
||||
Beides gehört dem aufrufenden Repository, nicht diesem Baustein — ein geteilter
|
||||
Baustein trägt weder die Infrastruktur-Adresse eines einzelnen Mandanten noch
|
||||
dessen Token. Der Preis sind zwei Voraussetzungen, die man vergessen kann, und
|
||||
eine nicht gesetzte Variable kommt als leere Zeichenkette an. Deshalb prüft der
|
||||
erste Schritt beide und benennt beim Abbruch die Einstellung samt Ort; geprüft
|
||||
wird vollständig, bevor abgebrochen wird, damit ein Repository ohne beide das in
|
||||
einem Lauf erfährt und nicht in zwei.
|
||||
|
||||
Für `SONAR_TOKEN` fängt der Wächter allerdings nur den Fall eines leeren oder
|
||||
nicht durchgereichten Secrets. Ein **ganz fehlendes** kommt nie so weit: Der
|
||||
Workflow deklariert es als `required`, und ein fehlendes Pflicht-Secret lässt auf
|
||||
dieser Instanz die Job-Erzeugung scheitern — der Lauf steht dann ohne Schritte
|
||||
da. Das ist hier der verständliche Fall, denn ohne Token gibt es keine Analyse;
|
||||
nachzulesen im Wiki unter
|
||||
[Gemessenes-Verhalten](https://git.jk-dev.de/workflow-util/pipelines/wiki/Gemessenes-Verhalten).
|
||||
|
||||
## Auf einen Tag zeigen, nicht auf `@main`
|
||||
|
||||
Aufrufe bitte immer an einen Tag binden:
|
||||
@@ -294,15 +379,17 @@ stehen; wer umstellt, wechselt auf `@v2` und schreibt `push: 'true'` bzw.
|
||||
### Die Nummer bezeichnet den Zustand des Repositories
|
||||
|
||||
Tags gelten hier **repository-weit**, nicht je Datei. `v3` ist der Stand, ab dem
|
||||
es `publish-helm-chart.yml` gibt; unter `v1` und `v2` existiert die Datei nicht,
|
||||
ein Aufruf auf `@v2` findet sie also nicht.
|
||||
es `publish-helm-chart.yml` gibt, `v4` der Stand, ab dem es
|
||||
`sonar-analysis.yml` gibt; unter `v1` und `v2` existiert keine der beiden
|
||||
Dateien, ein Aufruf auf `@v2` findet sie also nicht.
|
||||
|
||||
Das hat eine Folge, die man kennen muss, weil sie sonst als Fehler gelesen wird:
|
||||
Wer künftig `docker-build-push.yml` ändert, erhöht auf `v4` — und ein Aufruf von
|
||||
Wer künftig `docker-build-push.yml` ändert, erhöht auf `v5` — und ein Aufruf von
|
||||
`publish-helm-chart.yml@v3` bleibt gültig und richtig, obwohl sich an dieser
|
||||
Datei nichts geändert hat. Die Nummer ist keine Dateiversion. Umgekehrt zwingt
|
||||
eine brechende Änderung an einem Baustein die Aufrufer der anderen zu nichts:
|
||||
Sie bleiben auf ihrem Tag stehen, bis sie bewusst nachziehen.
|
||||
|
||||
`v3` selbst ist additiv. Der neue Baustein ändert an `docker-build-push.yml`
|
||||
nichts, bestehende Aufrufer auf `@v2` bleiben unberührt.
|
||||
`v3` und `v4` sind beide additiv. Sie nehmen je einen Baustein hinzu und ändern
|
||||
an den vorhandenen nichts; bestehende Aufrufer auf `@v2` bzw. `@v3` bleiben
|
||||
unberührt.
|
||||
Reference in new issue
Block a user