Geteilte Docker-Build-and-Push-Pipeline anlegen #2
No files matched your search
@@ -1,18 +1,43 @@
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
# Die vier Versionsangaben dienen ausschliesslich der Tag-Liste. Ohne Push
|
||||
# gibt es nichts zu taggen, deshalb sind sie nicht mehr Pflicht — ein
|
||||
# reiner Prueflauf muesste sonst Platzhalter erfinden. Bestehende Aufrufer
|
||||
# uebergeben sie unveraendert weiter; required von true auf false zu
|
||||
# senken ist rueckwaertskompatibel.
|
||||
#
|
||||
# Die Vorgabe ist 0 bzw. 0.0.0 und nicht etwa leer: docker/build-push-action
|
||||
# gibt die Tag-Liste auch ohne Push als --tag an den Build weiter. Ein
|
||||
# leerer Wert ergaebe "<registry>/<owner>/<name>:" und liesse den Build an
|
||||
# "invalid reference format" scheitern. 0.0.0 ist ein gueltiger, sichtbar
|
||||
# unechter Wert.
|
||||
version-number:
|
||||
required: true
|
||||
required: false
|
||||
default: 0.0.0
|
||||
type: string
|
||||
version-number-major:
|
||||
required: true
|
||||
required: false
|
||||
default: '0'
|
||||
type: string
|
||||
version-number-minor:
|
||||
required: true
|
||||
required: false
|
||||
default: '0'
|
||||
type: string
|
||||
version-number-patch:
|
||||
required: true
|
||||
required: false
|
||||
default: '0'
|
||||
type: string
|
||||
push:
|
||||
|
jklein marked this conversation as resolved
Outdated
|
||||
description: >-
|
||||
Whether the built image is pushed. With push: false no credentials are
|
||||
fetched from 1Password and no registry login happens, so the workflow
|
||||
doubles as a build check. Caveat: the checkout token comes from the
|
||||
same 1Password step, so without push the checkout falls back to the
|
||||
job token — which cannot clone private submodules.
|
||||
required: false
|
||||
default: true
|
||||
type: boolean
|
||||
container-name:
|
||||
required: true
|
||||
type: string
|
||||
@@ -22,14 +47,25 @@ on:
|
||||
docker-context:
|
||||
required: true
|
||||
type: string
|
||||
# image-owner und registry bilden zusammen das Praefix des Images. Dass
|
||||
# das eine Pflicht ist und das andere nicht, ist Absicht: der Eigentuemer
|
||||
# ist je Projekt verschieden und soll nie stillschweigend geraten werden —
|
||||
# genau das war der Fehler des frueheren, fest verdrahteten "suer", unter
|
||||
# dem die Images aller Projekte landeten. Die Registry ist dagegen
|
||||
# praktisch immer dieselbe und darf deshalb eine Vorgabe haben.
|
||||
image-owner:
|
||||
description: >-
|
||||
Owner segment of the pushed image, i.e. the middle part of
|
||||
|
jklein marked this conversation as resolved
jklein
commented
soll konfigurierbar sein mit default auf git.jk-dev.de soll konfigurierbar sein mit default auf git.jk-dev.de
|
||||
<registry>/<owner>/<container-name>. 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.
|
||||
Deliberately has no default: every caller states its owner explicitly.
|
||||
required: true
|
||||
type: string
|
||||
registry:
|
||||
description: >-
|
||||
Registry the image is tagged for and, when pushing, logged in to.
|
||||
required: false
|
||||
default: suer
|
||||
default: git.jk-dev.de
|
||||
type: string
|
||||
timeout-minutes:
|
||||
description: 'Hard limit for the build job. Without one a wedged build runs into the 6h default.'
|
||||
@@ -54,8 +90,12 @@ on:
|
||||
|
||||
name: build and push Docker Image
|
||||
|
||||
env:
|
||||
REGISTRY: git.jk-dev.de
|
||||
# Einen env-Block auf Workflow-Ebene gibt es nicht mehr: sein einziger Eintrag
|
||||
# war REGISTRY, und der ist jetzt eine Eingabe. Dort haette er auch nicht
|
||||
# bleiben koennen — env auf Workflow-Ebene wird ausgewertet, bevor es einen
|
||||
# inputs-Kontext gibt. Registry und Eigentuemer werden deshalb an den beiden
|
||||
# Stellen gelesen, an denen der Kontext sicher vorhanden ist: im
|
||||
# Login-Schritt und in der Tag-Liste.
|
||||
|
||||
jobs:
|
||||
build-image:
|
||||
@@ -80,8 +120,24 @@ jobs:
|
||||
# (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.
|
||||
#
|
||||
# Ohne Push werden gar keine Zugangsdaten geholt. Das hat eine Folge, die
|
||||
# man kennen muss: der Checkout-Token faellt damit ebenfalls weg, der
|
||||
# Checkout nutzt dann den Standard-Token des Jobs — und der kann die
|
||||
# privaten Submodul-Repositories nicht klonen. Projekte MIT privaten
|
||||
# Submodulen koennen den Modus push: false daher nicht nutzen; ihr Klon
|
||||
# scheitert an "Repository not found". Fuer Projekte ohne Submodule ist
|
||||
# der Modus unproblematisch.
|
||||
#
|
||||
# Zur Bedingung: ein boolescher workflow_call-Input kann je nach
|
||||
# Schreibweise des Aufrufers als echter Boolean oder als String ankommen.
|
||||
# `inputs.push` allein waere bei dem String 'false' wahr (nichtleere
|
||||
# Strings sind truthy), `inputs.push == 'true'` waere beim echten Boolean
|
||||
# true falsch (ein Vergleich ueber Typgrenzen laeuft ueber Zahlen, 'true'
|
||||
# wird dabei zu NaN). Die Kombination aus beidem deckt alle vier Faelle ab.
|
||||
- name: Zugangsdaten aus 1Password laden
|
||||
id: op-secrets
|
||||
if: ${{ inputs.push && inputs.push != 'false' }}
|
||||
uses: 1password/load-secrets-action@v2
|
||||
with:
|
||||
# Die Werte sollen nur als Step-Outputs entstehen und nicht in die
|
||||
@@ -104,7 +160,15 @@ jobs:
|
||||
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 }}
|
||||
#
|
||||
# Ist der 1Password-Schritt uebersprungen (push: false), sind seine
|
||||
# Outputs leer. Ein leer uebergebenes token wuerde die Vorgabe der
|
||||
# Action (github.token) ueberschreiben und den Checkout ohne jeden
|
||||
# Zugang laufen lassen — er scheiterte schon am eigenen Repository.
|
||||
# Der Fallback macht daraus den regulaeren Job-Token. Der genuegt
|
||||
# fuer das ausfuehrende Repository, nicht fuer private Submodule
|
||||
# (siehe Kommentar am 1Password-Schritt).
|
||||
token: ${{ steps.op-secrets.outputs.CHECKOUT_TOKEN || github.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.
|
||||
@@ -118,10 +182,15 @@ jobs:
|
||||
- name: Set up Docker Buildx
|
||||
id: buildx
|
||||
uses: docker/setup-buildx-action@master
|
||||
# Ohne Push gibt es nichts anzumelden — und die Zugangsdaten waeren
|
||||
# ohnehin leer, weil der 1Password-Schritt uebersprungen wurde. Ein
|
||||
# Login-Versuch mit leerem Benutzer scheiterte mit einer nichtssagenden
|
||||
# Meldung; dieselbe Bedingung wie oben verhindert das.
|
||||
- name: Authenticate To Registry
|
||||
if: ${{ inputs.push && inputs.push != 'false' }}
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
registry: ${{ inputs.registry }}
|
||||
username: ${{ steps.op-secrets.outputs.REGISTRY_USER }}
|
||||
password: ${{ steps.op-secrets.outputs.REGISTRY_TOKEN }}
|
||||
- name: Build
|
||||
@@ -132,15 +201,22 @@ jobs:
|
||||
file: ${{ inputs.dockerfile }}
|
||||
platforms: ${{ inputs.platforms }}
|
||||
build-args: ${{ inputs.build-args }}
|
||||
push: true
|
||||
# Dieselbe Normalisierung wie bei den if:-Bedingungen. Das Ergebnis
|
||||
# ist immer genau "true" oder "false" — die Action liest den Wert als
|
||||
# Boolean und akzeptiert nichts dazwischen.
|
||||
push: ${{ inputs.push && inputs.push != 'false' }}
|
||||
# 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.
|
||||
#
|
||||
# Ohne Push bleibt die Liste rein lokal: die Action gibt sie als --tag
|
||||
# an den Build weiter, veroeffentlicht aber nichts. Die Versionen
|
||||
# tragen dann ihre Vorgabe 0.0.0.
|
||||
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 }}
|
||||
${{ inputs.registry }}/${{ inputs.image-owner }}/${{ inputs.container-name }}:latest
|
||||
${{ inputs.registry }}/${{ inputs.image-owner }}/${{ inputs.container-name }}:${{ inputs.version-number }}
|
||||
${{ inputs.registry }}/${{ inputs.image-owner }}/${{ inputs.container-name }}:${{ inputs.version-number-major }}
|
||||
${{ inputs.registry }}/${{ inputs.image-owner }}/${{ inputs.container-name }}:${{ inputs.version-number-major }}.${{ inputs.version-number-minor }}
|
||||
${{ inputs.registry }}/${{ inputs.image-owner }}/${{ inputs.container-name }}:${{ inputs.version-number-major }}.${{ inputs.version-number-minor }}.${{ inputs.version-number-patch }}
|
||||
@@ -9,38 +9,55 @@ hier werden nicht selbst ausgelöst, sondern von anderen Repositories über
|
||||
### `.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`.
|
||||
(`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:
|
||||
|
||||
```
|
||||
git.jk-dev.de/<image-owner>/<container-name>:latest
|
||||
git.jk-dev.de/<image-owner>/<container-name>:<version-number>
|
||||
git.jk-dev.de/<image-owner>/<container-name>:<major>
|
||||
git.jk-dev.de/<image-owner>/<container-name>:<major>.<minor>
|
||||
git.jk-dev.de/<image-owner>/<container-name>:<major>.<minor>.<patch>
|
||||
<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 |
|
||||
| ---------------------- | ------- | ------------------------- | --------- |
|
||||
| `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 `<registry>/<owner>/<name>` |
|
||||
| `image-owner` | ja | — | Eigentümer-Segment des Images, also der mittlere Teil von `<registry>/<owner>/<name>` |
|
||||
| `push` | nein | `true` | 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.
|
||||
|
||||
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
|
||||
@@ -62,12 +79,45 @@ jobs:
|
||||
`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@v1
|
||||
with:
|
||||
container-name: bk-dev-website
|
||||
dockerfile: ./Dockerfile
|
||||
docker-context: .
|
||||
image-owner: bekemeyer-klein
|
||||
push: false
|
||||
```
|
||||
|
||||
> **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 |
|
||||
| `OP_CONNECT_TOKEN` | Secret | Zugang zu diesem Connect-Server |
|
||||
| `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
|
||||
|
||||
Reference in new issue
Block a user
ist required, kein default