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 mit airflow- beginnen, also zum Beispiel airflow-v2.10.4.
  • jobs.build.steps: Hier passiert die tatsächliche Arbeit.
    1. Checkout the repo und Setup Docker Buildx sind vordefinierte Actions, die unsere Arbeitsumgebung aufsetzen.
    2. 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 Beispiel airflow-v2.10.4. Darauf passiert ein grep, der im Wesentlichen den airflow-v-Teil entfernt. Übrig bleibt der korrekte Tag (2.10.4).
      Außerhalb passiert noch ein echo. Dieses setzt den Output (::set-output) mit dem namen tag und dem Wert 2.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.
    3. 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 nach FROM apache/airflow:2.10.4 ändern.
      Hier hilft der vorige Schritt: Der korrekte Tag steht jetzt in der Variablen steps.extract_tag.outputs.tag. Fehlt also nur noch ein sed-Befehl…
    4. 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:

  1. Ö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 dann chris). Finde dort de Robot 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 fall robot$chris+airflow-pusher, wie im Actions File ersichtilch. Das Passwort ist einfach nur ein Passwort.
  2. 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.