Ein reusable Workflow, der in zwanzig Repos gleichzeitig läuft, ist praktisch, bis du ihn änderst. Sobald mehrere Teams uses: org/.github/workflows/build.yml@main schreiben, zieht jeder Push auf main sofort in jede Pipeline ein. Für einen zentral gepflegten Workflow ist das keine Kleinigkeit: ein neuer Input, ein geänderter Default, ein umbenannter Job, und plötzlich bricht ein Build, den niemand angefasst hat. Die Lösung ist nicht, Reusable Workflows zu meiden, sondern sie zu versionieren wie jede andere Bibliothek auch, mit klaren Versionsgrenzen und einem Weg, Breaking Changes zu erkennen, bevor sie ausrollen.

Drei Wege, eine Version zu referenzieren

GitHub Actions kennt drei Referenzformen für uses:, und sie unterscheiden sich in Stabilität, nicht nur in Syntax.

uses: org/workflows-repo/.github/workflows/build.yml@main
uses: org/workflows-repo/.github/workflows/build.yml@v1
uses: org/workflows-repo/.github/workflows/build.yml@a1b2c3d4e5f6789...

Ein Branch-Referenz ist im Grunde ein Dauerabo auf jede Änderung, inklusive der kaputten. Eine SHA-Referenz ist das andere Extrem, absolut stabil, aber jedes Update muss der Konsument manuell nachziehen, Commit für Commit. Für sicherheitskritische Kontexte ist SHA-Pinning trotzdem die richtige Wahl, weil ein Tag im Nachhinein umgebogen werden kann, ein SHA nicht. Für den Alltag zwischen beiden Extremen liegt der brauchbare Kompromiss bei Major-Tags.

Major-Tags richtig pflegen

Die Konvention aus dem Actions-Ökosystem sieht so aus: v1 ist kein fester Commit, sondern ein beweglicher Zeiger auf die neueste v1.x.y. Ein neues Feature-Release verschiebt v1 mit, ein Breaking Change bekommt v2, und v1 bleibt stehen, wo es war. Konsumenten, die auf v1 zeigen, bekommen so automatisch jedes Bugfix- und Feature-Release, aber nie unangekündigt ein anderes Verhalten.

Technisch ist ein Major-Tag ein Tag wie jeder andere, du musst ihn nur bei jedem Release neu setzen:

git tag -f v1 v1.4.0
git push origin v1 --force

Der force-Push auf einen Tag ist beabsichtigt, nicht versehentlich, deshalb gehört dieser Schritt in ein Release-Skript und nicht in die freie Hand jedes Committers. Ein einfacher Release-Workflow kann das automatisieren:

name: release
on:
push:
tags:
- 'v[0-9]+.[0-9]+.[0-9]+'
jobs:
move-major-tag:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Major-Tag aktualisieren
run: |
MAJOR=$(echo "${GITHUB_REF_NAME}" | cut -d. -f1)
git tag -f "$MAJOR" "${GITHUB_REF_NAME}"
git push origin "$MAJOR" --force

Damit zieht jedes v1.x.y-Release automatisch den v1-Zeiger nach, ohne dass jemand daran denken muss.

Breaking Changes erkennen, bevor Konsumenten sie erkennen

Was zählt eigentlich als Breaking Change bei einem Workflow? Drei Kategorien decken die meisten Fälle ab: ein Pflicht-Input kommt neu dazu, ein bestehender Input ändert seine Bedeutung, oder ein Output oder Job-Name verschwindet oder wird umbenannt. Auch das Ändern eines Default-Werts zählt dazu, denn ein Konsument, der sich stillschweigend auf den alten Default verlassen hat, merkt die Änderung erst am Ergebnis, nicht am Diff. Alle diese Fälle sind für den Autor des Workflows oft unsichtbar, weil sein eigener Aufruf längst angepasst ist. Sichtbar werden sie erst in den Repos, die den Workflow konsumieren, und dort meistens erst, wenn der Build schon rot ist.

Der Punkt, an dem sich das vermeiden lässt, ist vor dem Merge, nicht danach.

Ein Test-Repo als Frühwarnsystem

Der zuverlässigste Weg, das zu prüfen, ist ein kleines, dediziertes Repo, das ausschließlich dazu da ist, den Reusable Workflow von der Konsumentenseite aus aufzurufen, einmal auf dem aktuellen Branch, einmal auf jedem gepflegten Major-Tag.

name: verify-reusable-workflow
on:
workflow_dispatch:
pull_request:
jobs:
test-main:
uses: org/workflows-repo/.github/workflows/build.yml@main
with:
target: test-fixture
test-v1:
uses: org/workflows-repo/.github/workflows/build.yml@v1
with:
target: test-fixture
test-v2:
uses: org/workflows-repo/.github/workflows/build.yml@v2
with:
target: test-fixture

Ein Fixture-Repo statt einer Sammlung realer Projekte hat einen klaren Vorteil: es ist minimal, schnell und ausschließlich unter deiner Kontrolle. Schlägt der Job gegen main fehl, während der Job gegen v1 grün bleibt, weißt du sofort, dass die Änderung auf main einen Breaking Change enthält, noch bevor sie in einen Tag gegossen wird. Das Test-Repo lässt sich zusätzlich per workflow_dispatch von Hand anstoßen, wenn du vor einem Release noch einmal gezielt prüfen willst, oder per Cron regelmäßig laufen lassen, um mitzubekommen, wenn sich das Verhalten einer verwendeten Action unter dir ändert, auch ohne eigene Codeänderung.

Was das in der Praxis bedeutet

Drei Regeln reichen für die meisten Setups: Major-Tags für die breite Mehrheit der Konsumenten, SHA-Pinning dort, wo Reproduzierbarkeit oder Sicherheit Vorrang hat, und ein Test-Repo, das jeden gepflegten Major-Tag gegen den aktuellen Stand hält. Der Aufwand dafür ist überschaubar, ein paar Zeilen Release-Automatisierung und ein Repo mit drei Jobs. Was er verhindert, ist der Fall, dass zwanzig Teams gleichzeitig merken, dass ihr Build kaputt ist, weil jemand einen Workflow geändert hat, den sie nur über main eingebunden hatten. Genau das ist der Unterschied zwischen einem zentralen Workflow, der Vertrauen aufbaut, und einem, den jedes Team lieber vendored, um ihn nicht mehr aktualisieren zu müssen.

Zwei verwandte Themen aus dieser Serie: wie ein Reusable Workflow überhaupt sauber gebaut wird, steht im Post Reusable Workflows in GitHub Actions sauber bauen. Die Abgrenzung zu Composite Actions steht in Composite Actions richtig schneiden.

Governance-Fragen dieser Art, wer eine zentrale Abhängigkeit ändern darf und wie ein Rollout kontrolliert bleibt, sind auch auf digital-business.blog Thema, dort mit Blick auf ganze Lieferprozesse statt auf eine einzelne Pipeline.

Hinterlasse einen Kommentar

Diese Seite verwendet Akismet, um Spam zu reduzieren. Erfahre, wie deine Kommentardaten verarbeitet werden..