Ich nutze Forgejo als Git Server, und seit kürzlich habe ich Forgejo auch duch einen Runner erweitert.
Jetzt habe ich einen ersten Task für meinen Runner erzeugt, den ich gern aufschreiben möchte.
Die Aufgabe
Meine Idee: Sobald ich einen tag in ein Repo pushe, soll das folgende passieren:
- Baue ein Container Image, basierend auf dem bereitgestellten Dockerfile
- Pushe dieses Image in meine lokale Container Registry. Der Tag des Images sollte dem Tag des originalen Images entsprechen.
- Keine unsicheren Secrets!
Ein bisschen mehr Detail: Ich muss mein Apache Airflow Image erweitern durch einige virtuelle Python Environments, und per Dokumentation mache ich das durch Erweiterung des Images. Das ist für mich ein gutes Beispiel um zu lernen.
Beachte: Airflow kommt in diesem Artikel eher am rande vor, darüber schreibe ich später vielleicht. Hier fokussiere ich mich auf den Forgejo Actions-Teil.
Das Repo
Das Airflow Image liegt in meinem Repo im Verzeichnis ./airflow-image
und enthält zwei wesentliche Dateien: Die requirements.txt
, und das Dockerfile
. Das Requirements-File ist nicht sehr aufregend, aber im Dockerfile sind ein paar Vorbereitungen notwendig, da ich den Tag den ich gepushed habe nutzen möchte als Tag für das Base Image und für das daraus entstehende Image. Daher schaut mein Dockerfile wie folgt aus:
FROM apache/airflow:AIRFLOW_VERSION_PLACEHOLDER
# ... Mache schlaue Dinge
Forgejo Actions
Für die Forgejo Action erzeugen wir im Repo ein weiteres Verzeichnis /.forgejo/workflows
. Zusätzlich könnte es möglich sein dass Actions für das Repo erst aktiviert werden muss; das get über die Weboberfläche im Repo unter Settings - Units.
Im workflows
Verzeichnis erstellen wir jetzt die Datei build-airflow-image.yaml
mit dem folgenden Inhalt:
name: build-airflow-image
on:
push:
tags:
- airflow-*
jobs:
build:
runs-on: ubuntu-22.04
steps:
- name: Checkout the repo
uses: actions/checkout@v4
- name: Setup Docker Buildx
uses: docker/setup-buildx-action@v1
- name: Extract the correct tag
id: extract_tag
run: echo "::set-output name=tag::$(echo ${{ env.GITHUB_REF_NAME }} | grep -oP '(?<=airflow-v)[^/]+')"
- name: Set the correct tag in Dockerfile
run: sed -i 's/AIRFLOW_VERSION_PLACEHOLDER/${{ steps.extract_tag.outputs.tag }}/' airflow-image/Dockerfile
- name: Login to Container Registry
uses: docker/login-action@v3
with:
registry: harbor.tech-tales.blog
username: robot$chris+airflow-pusher
password: ${{ secrets.HARBOR_PASSWORD }}
- name: Build and Push
uses: docker/build-push-action@v6
with:
push: true
tags: harbor.tech-tales.blog/chris/airflow:${{ steps.extract_tag.outputs.tag }}
context: airflow-image
file: airflow-image/Dockerfile
Was passiert:
name
: Einfach einen Namen definieren; ich glaube dass dieser hauptsächlich auf die Darstellung wirkt.on
: Hier definieren wir, wann die Action passieren soll. In meinem Fall soll sie bei Tags ausgeführt werden die mitairflow-
beginnen, also zum Beispielairflow-v2.10.4
.jobs.build.steps
: Hier passiert die tatsächliche Arbeit.Checkout the repo
undSetup Docker Buildx
sind vordefinierte Actions, die unsere Arbeitsumgebung aufsetzen.Extract the correct tag
: Das ist ein cooler Einzeiler, der den korrekten Tag extrahiert. Wir starten mit dem hinteren Teil des Befehls:echo ${{ env.GITHUB_REF_NAME }} | grep -oP '(?<=airflow-v)[^/]+'
. Hier bekommen wir den Github Ref Namen als Input, also zum Beispielairflow-v2.10.4
. Darauf passiert eingrep
, der im Wesentlichen denairflow-v
-Teil entfernt. Übrig bleibt der korrekte Tag (2.10.4
).
Außerhalb passiert noch einecho
. Dieses setzt den Output (::set-output
) mit dem namentag
und dem Wert2.10.4
.
Eigentlich passiert hier nur ein bisschen Magie mit Shell und regulären Expressions.
Beachte: Ich nutze dieses Repo für mehrere Dinge; zum Beispiel liegen auch alle meine DAG Files darin. Das ist der Grund, warum der Tag kompliziert sein muss.- Als nächstes definieren wir die korrekte Version im Dockerfile. Wir hatten ja die Zeile
FROM apache/airflow:AIRFLOW_VERSION_PLACEHOLDER
darin, die möchten wir jetzt nachFROM apache/airflow:2.10.4
ändern.
Hier hilft der vorige Schritt: Der korrekte Tag steht jetzt in der Variablensteps.extract_tag.outputs.tag
. Fehlt also nur noch einsed
-Befehl… - Der letzte Schritt baut das Image und schreibt es in die Image Registry. Ich habe Harbor bereits laufen, und nutze das hier. Der Schritt ist nicht allzu kompliziert, da alle Komplexität bereits in der bereitgestellten Action passiert: Login bei der Container Registry, baue das Image mit definiertem Dockerfile, tagge es korrekt und pushe es. Erneut kommt der vorher extrahierte Tag vor.
Secrets
Für die Container Registry benötigen wir einen User, da ich keine anonymen Pushes auf die Registry erlaube. Dafür benötigen wir zwei Schritte:
- Öffne im Harbor UI die Bibliothek auf die du pushen möchtest (in meinem Fall also
harbor.tech-tales.blog/chris
, die Bibliothek heißt dannchris
). Finde dort deRobot Accounts
Tab und erzeuge einen neuen Roboter. Wähle einen Namen der dir gefällt. Ich habe keinen Ablauf für den Account definiert; das kannst du gerne zu deiner Zufriedenheit anpassen.
Im zweiten Schritt des Setup wirst du gefragt, was der Robot machen können soll. Ich habe Repository Pull und Repository Push Permissions gegeben - Push ist offensichtlich benötigt, und Push geht scheinbar nicht ohne Pull.
Nach der Bestätigung bekommst du den Namen und das Passwort angezeigt. Der Name lautet in meinem fallrobot$chris+airflow-pusher
, wie im Actions File ersichtilch. Das Passwort ist einfach nur ein Passwort. - Gehe jetzt ins Forgejo Interface und öffne das Repo an dem du gerade arbeitest. Gehe nach Settings - Actions - Secrets. Erzeuge ein neues Secret mit dem Namen
HARBOR_PASSWORD
und als Wert das Passwort des Roboters. Beachte dass du das Secret nicht mehr sehen wirst; Forgejo sichert es verschlüsselt.
Fertig!
Wie nutze ich dieses Setup jetzt: Ich tracke die Airflow Releases. Wenn ein neues Release veröffentlich wird, erzeuge ich im Repo einen Tag mit dem Namen airflow-vx.y.z
und pushe den Tag ins Repo. Dadurch startet die Action und erzeugt mein Image, so wie vorgesehen. Als Abschluss (und zur Zeit noch manuell) aktualisiere ich dann meine Airflow Installation.