Der Workflow kommt aus jlab-cluster/helm-charts, wo er 26 duenne Huellen bedient. Er laedt ein gepacktes Chart in eine klassische, indexbasierte Helm-Registry: ein einzelnes HTTP-POST, kein OCI-helm-push. Zwei Dinge sind beim Umzug anders als in der Vorlage. Der Eigentuemer ist eine Pflichteingabe registry-owner vom Typ string, ohne Vorgabe. Bisher wurde er aus github.repository_owner abgeleitet; als Eingabe wird er im aufrufenden Repository ausgewertet, wo der Wert gemessen ist. Das folgt der Hausregel, dass ein Eigentuemer-Segment nie geraten wird, und haengt den Baustein an keine Kontext-Semantik. type: string wie alle Eingaben hier, siehe #4. OP_CONNECT_HOST kommt aus einer Variablen des aufrufenden Repositories statt fest verdrahtet aus dem Baustein — ein geteilter Baustein traegt nicht die Infrastruktur-Adresse eines einzelnen Mandanten. Ein erster Schritt prueft die Variable und benennt sie beim Abbruch, statt zwei Schritte spaeter an einer leeren Adresse zu scheitern. Alles andere ist unveraendert uebernommen, samt der Kommentare, die es begruenden: curl --config - statt --user, --fail-with-body, der gebaute Dateiname mit test -f, die :?-Waechter je Tresor-Feld, die Leerzeichen-Pruefung an den Zugangsdaten, das Maskieren des Hosts und die drei Formwaechter an charts-url. Der workflow_dispatch-Block ist entfallen: Ein Dispatch hier liefe gegen dieses Repository, in dem kein Chart liegt. Die Huellen des Aufrufers tragen ihn. ci/helm-dependencies.sh bleibt ein Skript des aufrufenden Repositories; der Checkout holt dessen Baum, also laeuft es weiter aus dessen Kopie. Part of jlab-cluster/helm-charts#66
303 lines
14 KiB
Markdown
303 lines
14 KiB
Markdown
# 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`).
|
|
|
|
## 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](#prüfmodus-ohne-push).
|
|
|
|
#### 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](#prüfmodus-ohne-push) |
|
|
| `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](https://git.jk-dev.de/workflow-util/pipelines/issues/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
|
|
|
|
```yaml
|
|
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:
|
|
|
|
```yaml
|
|
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](https://git.jk-dev.de/workflow-util/pipelines/issues/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
|
|
|
|
```yaml
|
|
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.
|
|
|
|
## Auf einen Tag zeigen, nicht auf `@main`
|
|
|
|
Aufrufe bitte immer an einen Tag binden:
|
|
|
|
```yaml
|
|
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; unter `v1` und `v2` existiert die Datei nicht,
|
|
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
|
|
`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.
|