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
|
(`GitHub-Actions/jlab-registry-access`: `username`, `password`, `charts-url`) und
|
||||||
muss in Gitea nicht hinterlegt werden.
|
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`
|
## Auf einen Tag zeigen, nicht auf `@main`
|
||||||
|
|
||||||
Aufrufe bitte immer an einen Tag binden:
|
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
|
### Die Nummer bezeichnet den Zustand des Repositories
|
||||||
|
|
||||||
Tags gelten hier **repository-weit**, nicht je Datei. `v3` ist der Stand, ab dem
|
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,
|
es `publish-helm-chart.yml` gibt, `v4` der Stand, ab dem es
|
||||||
ein Aufruf auf `@v2` findet sie also nicht.
|
`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:
|
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
|
`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
|
Datei nichts geändert hat. Die Nummer ist keine Dateiversion. Umgekehrt zwingt
|
||||||
eine brechende Änderung an einem Baustein die Aufrufer der anderen zu nichts:
|
eine brechende Änderung an einem Baustein die Aufrufer der anderen zu nichts:
|
||||||
Sie bleiben auf ihrem Tag stehen, bis sie bewusst nachziehen.
|
Sie bleiben auf ihrem Tag stehen, bis sie bewusst nachziehen.
|
||||||
|
|
||||||
`v3` selbst ist additiv. Der neue Baustein ändert an `docker-build-push.yml`
|
`v3` und `v4` sind beide additiv. Sie nehmen je einen Baustein hinzu und ändern
|
||||||
nichts, bestehende Aufrufer auf `@v2` bleiben unberührt.
|
an den vorhandenen nichts; bestehende Aufrufer auf `@v2` bzw. `@v3` bleiben
|
||||||
|
unberührt.
|
||||||
Reference in new issue
Block a user