Files
pipelines/README.md
T
jklein 0f7cd04c70 SonarQube-Analyse als geteilten Baustein aufnehmen
Neuer Baustein sonar-analysis.yml, on: workflow_call. Ein Waechter fuer die
Voraussetzungen des Aufrufers, Checkout mit vollstaendiger Historie, dann die
offizielle Scan-Action. Beide Actions auf einen Commit gepinnt.

Projektspezifisches steht in der sonar-project.properties des aufrufenden
Repositories, nicht hier - damit ist der Workflow fuer jedes Repository
identisch und ein zweiter Abnehmer kostet eine Datei mit drei Zeilen.

Kein Quality-Gate-Schritt, und das absichtlich: Die Community Build analysiert
nur den Hauptzweig, der Ausloeser liegt damit nach dem Merge, und dort ist
nichts mehr zu blockieren. Ein rotes main ohne Wirkung nimmt die
Glaubwuerdigkeit der uebrigen Pruefungen mit. Die zwei Bedingungen, unter denen
ein Gate richtig waere, stehen im README und als Kommentar an der Stelle, an
der der Schritt stuende.

SONAR_HOST_URL ist eine Variable des Aufrufers, SONAR_TOKEN sein Secret. Die
Adresse bleibt als Variable im Protokoll lesbar - erst das macht eine falsche
Adresse diagnostizierbar. Der Waechter prueft beide, bevor er abbricht, damit
ein Repository ohne beide das in einem Lauf erfaehrt.

Beide Eingaben sind type: string, auch project-base-dir: Boolesche
workflow_call-Eingaben kommen auf dieser Gitea-Version nicht an (Issue #4), und
die Regel gilt fuer jede Eingabe hier, damit sie nicht je Datei neu entschieden
wird.

Die Langform - warum fetch-depth: 0 nicht optional ist, warum kein
Docker-Runner noetig ist, was der Scanner zur Laufzeit holt und was an diesem
Baustein noch nicht gemessen ist - steht im Wiki unter sonar-analysis.

Der Baustein ist ab v4 verfuegbar; die Tag-Nummer bezeichnet wie bisher den
Zustand des Repositories und nicht die Version einer Datei.
2026-10-01 23:33:05 +00:00

19 KiB

pipelines

Geteilte CI/CD-Bausteine für die Gitea-Instanz git.jk-dev.de. Die Workflows hier werden nicht selbst ausgelöst, sondern von anderen Repositories über uses: aufgerufen (on: workflow_call).

Diese Datei ist die Kurzreferenz für Aufrufer: was ein Baustein tut, seine Eingaben, ein Aufrufbeispiel, die Voraussetzungen. Wer einen Aufruf schreibt, braucht nur das. Die Langform — warum die Dinge so sind und was dafür gemessen wurde — steht im Wiki, eine Seite je Baustein und eine für die Eigenheiten dieser Forge.

Bausteine

.gitea/workflows/docker-build-push.yml

Baut ein Docker-Image und schiebt es als Multi-Arch-Manifest (linux/amd64,linux/arm64) in eine Registry, vorgabegemäß git.jk-dev.de. Läuft auf einem Runner mit dem Label ubuntu-docker.

Gepusht wird unter fünf Tags:

<registry>/<image-owner>/<container-name>:latest
<registry>/<image-owner>/<container-name>:<version-number>
<registry>/<image-owner>/<container-name>:<major>
<registry>/<image-owner>/<container-name>:<major>.<minor>
<registry>/<image-owner>/<container-name>:<major>.<minor>.<patch>

Der Checkout holt Submodule mit; die Zugangsdaten dafür und für die Registry kommen zur Laufzeit aus 1Password Connect.

Mit push: 'false' entfällt der Push — der Baustein baut dann nur. Damit nutzt die CI eines Projekts dieselbe Bau-Logik wie das Deployment, statt sie zu duplizieren. Siehe Prüfmodus.

Eingaben

Eingabe Pflicht Vorgabe Bedeutung
container-name ja — Name des Images
dockerfile ja — Pfad zum Dockerfile
docker-context ja — Build-Kontext
image-owner ja — Eigentümer-Segment des Images, also der mittlere Teil von <registry>/<owner>/<name>
push nein 'true' Zeichenkette 'true' oder 'false'. Ob das Image gepusht wird. Bei 'false' werden keine Zugangsdaten aus 1Password geholt und es findet kein Registry-Login statt — siehe Prüfmodus
registry nein git.jk-dev.de Registry, für die getaggt und — beim Push — bei der angemeldet wird
version-number nein 0.0.0 Vollständige Version, wird als Tag gesetzt
version-number-major nein 0 Major-Anteil für die mitwandernden Tags
version-number-minor nein 0 Minor-Anteil
version-number-patch nein 0 Patch-Anteil
timeout-minutes nein 45 Harte Grenze für den Build-Job
platforms nein linux/amd64,linux/arm64 Zielplattformen der Manifest-Liste
build-args nein — Zusätzliche Build-Argumente, ein NAME=wert je Zeile

Dass image-owner Pflicht ist und registry nicht, ist Absicht: Der Eigentümer ist je Projekt verschieden und soll nie stillschweigend geraten werden — genau das war der Fehler des früher fest verdrahteten suer, unter dem die Images aller Projekte landeten. Die Registry ist dagegen praktisch immer dieselbe.

push ist eine Zeichenkette und kein Boolean — und das ist Absicht. Auf dieser Gitea-Version (gemessen an 1.27.0) werden boolesche workflow_call-Eingaben nicht an den aufgerufenen Workflow durchgereicht: Weder ihre Vorgabe noch ein ausdrücklich übergebenes push: true kamen an, inputs.push blieb leer, und der Baustein baute immer im Prüfmodus — bei grünem Lauf. Alle Eingaben vom Typ string kamen im selben Lauf korrekt an. Details in Issue #4.

Deshalb bitte immer in Anführungszeichen schreiben: push: 'true' bzw. push: 'false'. Ein unquotiertes push: false ist im YAML ein Boolean und wird verschluckt.

Brechende Änderung gegenüber v1. Aufrufer, die push als YAML-Boolean übergeben, müssen auf @v2 wechseln und dabei auf die Schreibweise mit Anführungszeichen umstellen. Wer @v1 weiterverwendet, behält das alte Verhalten samt Fehler.

Die vier Versionsangaben dienen ausschließlich der Tag-Liste. Sie sind keine Pflicht mehr, damit ein Prüflauf keine Platzhalter erfinden muss; wer pusht, übergibt sie wie bisher. Ihre Vorgabe ist bewusst 0.0.0 und nicht leer — die Tag-Liste wird auch ohne Push gebildet, und ein leerer Wert ergäbe das ungültige …/<name>:.

Aufrufbeispiel

jobs:
  image:
    uses: workflow-util/pipelines/.gitea/workflows/docker-build-push.yml@v2
    with:
      version-number: 2026.09.1
      version-number-major: '2026'
      version-number-minor: '09'
      version-number-patch: '1'
      container-name: bk-dev-website
      dockerfile: ./Dockerfile
      docker-context: .
      image-owner: bekemeyer-klein
      push: 'true'
    secrets: inherit

secrets: inherit ist notwendig — der Workflow braucht OP_CONNECT_TOKEN aus dem aufrufenden Repository.

Prüfmodus ohne Push

Mit push: 'false' baut der Baustein nur und veröffentlicht nichts. Es werden dann keine Zugangsdaten aus 1Password geholt und es findet kein Registry-Login statt. Die CI eines Projekts nutzt damit dieselbe Bau-Logik wie das Deployment, statt sie zu duplizieren.

Weder Secrets noch Versionsangaben sind dafür nötig:

jobs:
  build-check:
    uses: workflow-util/pipelines/.gitea/workflows/docker-build-push.yml@v2
    with:
      container-name: bk-dev-website
      dockerfile: ./Dockerfile
      docker-context: .
      image-owner: bekemeyer-klein
      push: 'false'

Die Anführungszeichen sind nicht kosmetisch: push: false ohne sie ist ein YAML-Boolean und kommt im Baustein nicht an (siehe oben). Es griffe dann die Vorgabe 'true', der Lauf versuchte also trotz push: false zu veröffentlichen — sofern Gitea die Typabweichung nicht ohnehin als Fehler zurückweist.

Private Submodule funktionieren in diesem Modus nicht. Der Checkout-Token stammt aus demselben 1Password-Schritt, der ohne Push übersprungen wird. Der Checkout fällt deshalb auf den Standard-Token des Jobs zurück. Der genügt für das ausführende Repository, nicht aber für die Submodule in eigenen privaten Repositories — deren Klon scheitert mit Repository not found. Projekte mit privaten Submodulen können push: false daher nicht verwenden; für Projekte ohne Submodule ist der Modus unproblematisch.

secrets: inherit kann hier entfallen. Es zu belassen schadet nicht — der Workflow greift ohne Push auf kein Secret zu.

Voraussetzungen im aufrufenden Repository

Name Art Zweck
OP_CONNECT_HOST Variable Adresse des 1Password-Connect-Servers im Cluster. Nur beim Push nötig
OP_CONNECT_TOKEN Secret Zugang zu diesem Connect-Server. Nur beim Push nötig
DOCKER_HOST Variable Nur nötig, wenn der Runner-Daemon rootless läuft und sein Socket nicht unter /var/run/docker.sock liegt (dann z. B. unix:///run/user/1000/docker.sock). Ist die Variable nicht gesetzt, nutzt Docker seinen Default.

Die eigentlichen Zugänge für Checkout und Registry liegen in 1Password (GitHub-Actions/jlab-git-checkout-token, GitHub-Actions/jlab-registry-access) und müssen in Gitea nicht hinterlegt werden.

.gitea/workflows/publish-helm-chart.yml

Packt ein Helm-Chart und lädt es in eine klassische, indexbasierte Helm-Registry. Das ist kein OCI-helm push: Es gibt keine Registry-Sitzung, sondern ein einzelnes HTTP-POST des gepackten .tgz, je Anfrage authentifiziert. Läuft auf ubuntu-latest.

Die Ziel-URL wird zusammengesetzt, nicht gespeichert:

<charts-url>/<registry-owner>/helm/api/charts

<charts-url> ist die Basis bis /api/packages und liegt in 1Password (GitHub-Actions/jlab-registry-access, Feld charts-url), <registry-owner> ist die Pflichteingabe. Damit steht im aufrufenden Repository kein Wert, der auf die Registry zeigt — nicht einmal ein Pfadsegment —, und ein zweiter Eigentümer auf derselben Instanz braucht keine zweite Einstellung.

Eingaben

Eingabe Pflicht Vorgabe Bedeutung
chart-name ja — Name des Charts, zugleich der Name seines Verzeichnisses unterhalb von chart-location
registry-owner ja — Eigentümer-Segment der Ziel-URL, also der <owner> in <basis>/<owner>/helm/api/charts
chart-location nein . Verzeichnis, in dem das Chart-Verzeichnis liegt

Dass registry-owner Pflicht ist und keine Vorgabe hat, folgt derselben Hausregel wie image-owner oben: Ein Eigentümer-Segment wird nie geraten. Aufrufer übergeben ${{ github.repository_owner }}; der Wert wird damit im aufrufenden Repository ausgewertet, wo er gemessen ist (jlab-cluster, Lauf 282 von validate-charts.yml). Dass der Kontext auch in einem von fremd aufgerufenen Workflow der des Aufrufers bleibt, ist dokumentiert, auf dieser Gitea-Fassung aber nicht gemessen — und ein Chart, das unter dem falschen Eigentümer veröffentlicht wird, ist nichts, wofür man sich auf eine Kontext-Semantik verlassen müsste. Als Eingabe hängt der Baustein an keiner.

Alle Eingaben sind type: string, auch dort, wo künftig ein Schalter hinzukommt: Boolesche workflow_call-Eingaben werden auf dieser Gitea-Version nicht durchgereicht — bei grünem Lauf. Siehe Issue #4 und die Begründung im Abschnitt zu push oben.

Ausgaben

Ausgabe Bedeutung
chart-version Die veröffentlichte Chart-Version, gelesen aus version: in Chart.yaml

Aufrufbeispiel

jobs:
  publish-outline:
    name: Publish Outline Helm Chart
    uses: workflow-util/pipelines/.gitea/workflows/publish-helm-chart.yml@v3
    with:
      chart-name: 'outline'
      registry-owner: ${{ github.repository_owner }}
    secrets: inherit

secrets: inherit ist notwendig — der Workflow braucht OP_CONNECT_TOKEN aus dem aufrufenden Repository.

Der Baustein löst sich nicht selbst aus. Wann veröffentlicht wird, entscheidet die Hülle im aufrufenden Repository über ihr eigenes on: — üblicherweise ein push auf main, eingeschränkt auf das Chart-Verzeichnis, dazu ein workflow_dispatch für den Fall, dass von Hand nachgeschoben werden muss.

Form des Tresor-Felds charts-url

Das Feld enthält nur die Basis:

https://<host>/api/packages                             # so
https://<host>/api/packages/<owner>/helm/api/charts      # nicht so

Drei Wächter weisen eine andere Form namentlich zurück: wenn der Wert auf /api/charts endet, wenn er das Segment /helm enthält, und wenn er nicht auf /api/packages endet. Der Grund ist die Lesbarkeit des Fehlers — 1Password maskiert die URL im Protokoll, ein 404 gegen eine maskierte Adresse wäre nicht zu deuten. Der Eigentümer wird aus demselben Grund geprüft: Ein leerer Wert ergäbe <basis>//helm/api/charts, also wieder einen nackten 404.

Voraussetzungen im aufrufenden Repository

Name Art Zweck
OP_CONNECT_HOST Variable Adresse des 1Password-Connect-Servers im Cluster, etwa https://op-connect.example.com
OP_CONNECT_TOKEN Secret Zugang zu diesem Connect-Server, kommt über secrets: inherit
ci/helm-dependencies.sh Datei im Repository Ausführbares Skript, das die deklarierten Abhängigkeiten eines Charts nach charts/ holt

OP_CONNECT_HOST ist bewusst eine Variable des aufrufenden Repositories und kein fest verdrahteter Wert: Ein geteilter Baustein trägt nicht die Infrastruktur-Adresse eines einzelnen Mandanten. Der Preis dafür ist eine Voraussetzung, die man vergessen kann, und eine nicht gesetzte Variable kommt als leere Zeichenkette an. Deshalb prüft der erste Schritt sie und benennt beim Abbruch die Einstellung samt Ort — statt zwei Schritte später an einer leeren Adresse zu scheitern.

ci/helm-dependencies.sh ist ein Skript des aufrufenden Repositories, und das bleibt nach dem Umzug hierher so: Der Checkout holt das Repository, zu dem der Lauf gehört — das des Aufrufers —, also ist ${{ github.workspace }} dessen Arbeitsverzeichnis. Der Aufruf steht vor helm package, weil helm package mit found in Chart.yaml, but missing in charts/ directory abbricht, solange eine deklarierte Abhängigkeit nicht geholt ist. Für ein Chart ohne dependencies: tut das Skript nichts, vorhanden und ausführbar muss es trotzdem sein; fehlt es, sagt der Lauf genau das.

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

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.

Auf einen Tag zeigen, nicht auf @main

Aufrufe bitte immer an einen Tag binden:

uses: workflow-util/pipelines/.gitea/workflows/docker-build-push.yml@v2   # so
uses: workflow-util/pipelines/.gitea/workflows/docker-build-push.yml@main # nicht so

Mit @main ändert jede Anpassung hier unangekündigt das Verhalten sämtlicher Projekte — und der Fehler zeigt sich dann dort, wo niemand etwas geändert hat. Ein Tag macht das Übernehmen einer neuen Fassung zu einer bewussten Entscheidung im jeweiligen Projekt.

Änderungen hier bekommen deshalb einen neuen Tag. Bricht eine Änderung bestehende Aufrufe, wird der Major-Anteil erhöht.

v2 ist genau so ein Fall: Die Umstellung von push auf type: string bricht Aufrufe, die den Wert als YAML-Boolean übergeben. v1 bleibt unverändert stehen; wer umstellt, wechselt auf @v2 und schreibt push: 'true' bzw. push: 'false'.

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