diff --git a/.gitea/workflows/docker-build-push.yml b/.gitea/workflows/docker-build-push.yml new file mode 100644 index 0000000..40b49aa --- /dev/null +++ b/.gitea/workflows/docker-build-push.yml @@ -0,0 +1,146 @@ +on: + workflow_call: + inputs: + version-number: + required: true + type: string + version-number-major: + required: true + type: string + version-number-minor: + required: true + type: string + version-number-patch: + required: true + type: string + container-name: + required: true + type: string + dockerfile: + required: true + type: string + docker-context: + required: true + type: string + image-owner: + description: >- + Owner segment of the pushed image, i.e. the middle part of + //. Used to be hardcoded to "suer", + which sent every image of every calling project to the same account. + Kept as the default so existing callers stay unchanged. + required: false + default: suer + type: string + timeout-minutes: + description: 'Hard limit for the build job. Without one a wedged build runs into the 6h default.' + required: false + default: 45 + type: number + platforms: + description: >- + Target platforms of the pushed manifest list. The build stages of all + dockerfiles are pinned to the build platform, so an extra platform only + adds its runtime stage, not another compile run. + required: false + default: linux/amd64,linux/arm64 + type: string + build-args: + description: >- + Extra build arguments for the image, one NAME=value per line. The docs + image uses it to stamp the released version into writerside.cfg. + required: false + default: '' + type: string + +name: build and push Docker Image + +env: + REGISTRY: git.jk-dev.de + +jobs: + build-image: + name: Build ${{ inputs.container-name }} Container + permissions: + packages: write + contents: read + runs-on: [ ubuntu-docker ] + timeout-minutes: ${{ inputs.timeout-minutes }} + env: + # Ohne Angabe suchen Buildx und die Docker-CLI unter + # unix:///var/run/docker.sock. Laeuft der Daemon des Runners rootless, + # liegt sein Socket stattdessen unter /run/user//docker.sock. Der + # Pfad kommt deshalb aus einer Repository-Variablen; ist sie nicht + # gesetzt, bleibt die Variable leer und Docker nutzt seinen Default. + DOCKER_HOST: ${{ vars.DOCKER_HOST }} + steps: + # Der Token, den Gitea jedem Job stellt, taugt fuer zwei Dinge nicht: + # Er gilt nur fuer das ausfuehrende Repository, waehrend die Submodule in + # eigenen, privaten Repositories liegen — deren Klon scheitert an + # "Repository not found". Und die Container-Registry lehnt ihn ab + # (container.Authenticate antwortet mit 401). Beides deckt je ein eigener + # Zugang ab, gepflegt in 1Password und hier zur Laufzeit ueber den + # Connect-Server im Cluster geholt; in Gitea liegt nur dessen Zugang. + - name: Zugangsdaten aus 1Password laden + id: op-secrets + uses: 1password/load-secrets-action@v2 + with: + # Die Werte sollen nur als Step-Outputs entstehen und nicht in die + # Umgebung aller folgenden Schritte wandern. + export-env: false + env: + OP_CONNECT_HOST: ${{ vars.OP_CONNECT_HOST }} + OP_CONNECT_TOKEN: ${{ secrets.OP_CONNECT_TOKEN }} + # Klont die privaten Submodul-Repositories. + CHECKOUT_TOKEN: op://GitHub-Actions/jlab-git-checkout-token/credential + # Eigener Zugang fuer die Registry — der Checkout-Token darf dort + # nicht schreiben. Benutzername und Token kommen aus demselben Item, + # damit sie zwangslaeufig zusammenpassen; die Registry prueft den + # Token gegen genau das Konto aus `username`. + REGISTRY_USER: op://GitHub-Actions/jlab-registry-access/username + REGISTRY_TOKEN: op://GitHub-Actions/jlab-registry-access/password + + - uses: actions/checkout@v4 + with: + submodules: true + # Die Action legt je Referenz einen Output an, benannt wie die + # Umgebungsvariable, in der die op://-Referenz stand. + token: ${{ steps.op-secrets.outputs.CHECKOUT_TOKEN }} + # Schlaegt der Buildx-Start fehl, ist die erste Frage immer, welcher + # Socket im Job-Container ueberhaupt ankommt. Der Schritt beantwortet sie, + # ohne den Job scheitern zu lassen. + - name: Docker-Socket ermitteln + run: | + echo "DOCKER_HOST=${DOCKER_HOST:-}" + ls -l /var/run/docker.sock /run/docker.sock 2>&1 || true + ls -l /run/user/*/docker.sock 2>&1 || true + docker version 2>&1 || true + + - name: Set up Docker Buildx + id: buildx + uses: docker/setup-buildx-action@master + - name: Authenticate To Registry + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ steps.op-secrets.outputs.REGISTRY_USER }} + password: ${{ steps.op-secrets.outputs.REGISTRY_TOKEN }} + - name: Build + uses: docker/build-push-action@v5 + with: + builder: ${{ steps.buildx.outputs.name }} + context: ${{ inputs.docker-context }} + file: ${{ inputs.dockerfile }} + platforms: ${{ inputs.platforms }} + build-args: ${{ inputs.build-args }} + push: true + # Der Eigentümer stand frueher als IMAGE_OWNER im env-Block des + # Workflows. Dort kann er nicht bleiben: env auf Workflow-Ebene wird + # ausgewertet, bevor es einen inputs-Kontext gibt. Der Wert wird + # deshalb hier unmittelbar aus inputs gelesen — an dieser Stelle ist + # der Kontext sicher vorhanden, anders als in jedem env-Block. + tags: | + ${{ env.REGISTRY }}/${{ inputs.image-owner }}/${{ inputs.container-name }}:latest + ${{ env.REGISTRY }}/${{ inputs.image-owner }}/${{ inputs.container-name }}:${{ inputs.version-number }} + ${{ env.REGISTRY }}/${{ inputs.image-owner }}/${{ inputs.container-name }}:${{ inputs.version-number-major }} + ${{ env.REGISTRY }}/${{ inputs.image-owner }}/${{ inputs.container-name }}:${{ inputs.version-number-major }}.${{ inputs.version-number-minor }} + ${{ env.REGISTRY }}/${{ inputs.image-owner }}/${{ inputs.container-name }}:${{ inputs.version-number-major }}.${{ inputs.version-number-minor }}.${{ inputs.version-number-patch }} diff --git a/README.md b/README.md index 24415e9..5b867ea 100644 --- a/README.md +++ b/README.md @@ -1 +1,92 @@ -# Hier stehen wichtige Infos! \ No newline at end of file +# 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 die Registry `git.jk-dev.de`. Läuft auf einem +Runner mit dem Label `ubuntu-docker`. + +Gepusht wird unter fünf Tags: + +``` +git.jk-dev.de//:latest +git.jk-dev.de//: +git.jk-dev.de//: +git.jk-dev.de//:. +git.jk-dev.de//:.. +``` + +Der Checkout holt Submodule mit; die Zugangsdaten dafür und für die Registry +kommen zur Laufzeit aus 1Password Connect. + +#### Eingaben + +| Eingabe | Pflicht | Vorgabe | Bedeutung | +| ---------------------- | ------- | ------------------------- | --------- | +| `version-number` | ja | — | Vollständige Version, wird als Tag gesetzt | +| `version-number-major` | ja | — | Major-Anteil für die mitwandernden Tags | +| `version-number-minor` | ja | — | Minor-Anteil | +| `version-number-patch` | ja | — | Patch-Anteil | +| `container-name` | ja | — | Name des Images | +| `dockerfile` | ja | — | Pfad zum Dockerfile | +| `docker-context` | ja | — | Build-Kontext | +| `image-owner` | nein | `suer` | Eigentümer-Segment des Images, also der mittlere Teil von `//` | +| `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 | + +#### Aufrufbeispiel + +```yaml +jobs: + image: + uses: workflow-util/pipelines/.gitea/workflows/docker-build-push.yml@v1 + 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 + secrets: inherit +``` + +`secrets: inherit` ist notwendig — der Workflow braucht `OP_CONNECT_TOKEN` aus +dem aufrufenden Repository. + +#### Voraussetzungen im aufrufenden Repository + +| Name | Art | Zweck | +| ------------------ | -------- | ----- | +| `OP_CONNECT_HOST` | Variable | Adresse des 1Password-Connect-Servers im Cluster | +| `OP_CONNECT_TOKEN` | Secret | Zugang zu diesem Connect-Server | +| `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. + +## 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@v1 # 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.