DevOps mit GitHub - Teil 2: Packages bauen und veröffentlichen mit GitHub Actions

Im zweiten Teil dieser Serie werden wir uns damit beschäftigen, wie wir GitHub Actions als Build-Pipeline nutzen können. Bei jedem Commit wird aus unserem Gradle-Projekt ein Artefakt gebaut, das anschließend automatisch in GitHub Packages veröffentlicht wird.

Also published in English at medium.com.

Zeitgleich mit der Ankündigung für GitHub Packages wurde auch GitHub Actions angekündigt. Damit wird es möglich, auf Events mit unterschiedlichen Aktionen zu reagieren. Dabei werden zahlreiche unterschiedliche Events unterstützt.

Mit GitHub Packages haben wir uns bereits im ersten Teil dieser Serie beschäftigt. Den entsprechenden Post findet ihr hier. Dieser Teil baut auf dem Projekt auf, das wir im ersten Blogbeitrag erstellt haben.

Was ist GitHub Actions?

Im Grunde lässt sich GitHub Actions als eine Pipeline beschreiben, die bestimmte Aktionen als Reaktion auf die bereits erwähnten Events ausführt. Während herkömmliche Pipelines wie bspw. Jenkins normalerweise nur auf neue Commits reagieren und daraufhin einen Build ausführen, ist GitHub Actions breiter aufgestellt.

Auch wenn die vermutlich am häufigsten genutzten Events tatsächlich das Pushen neuer Commits und das Erstellen neuer Pull Requests sind, können damit auch Aktionen angelegt werden, die bspw. beim Anlegen eines neuen Issues ausgeführt werden. Alle verfügbaren Events sind hier aufgelistet.

Der Service ist für öffentliche Repositories kostenfrei, private Repositories erhalten 2000 Build-Minuten pro Monat umsonst. Kostenpflichtige Tarife haben höhere Inklusivminuten. Zusätzliche Minuten können kostenpflichtig hinzugebucht werden mehr. Aktionen werden normalerweise in Linux-Containern ausgeführt. Aktionen in Windows- oder macOS-Containern werden mit Faktor 2 bzw. 10 abgerechnet.

Wie funktioniert es?

Eine GitHub Action besteht aus einer YAML-Datei, die im Projekt-Repository abgelegt wird. Darin wird definiert, welche Schritte ausgeführt werden. GitHub prüft für jedes Event, ob eine passende Action definiert ist, und führt diese aus.

Neben einfachen Bash-Befehlen können auch Actions aus dem Marketplace verwendet werden, die häufig benötigte Aktionen ausführen wie das Auschecken von Quellcode oder das Bereitstellen einer bestimmten Java-Version.

Eigene Actions können per NodeJS definiert werden und in eigenen Repositories wiederverwendet werden. Alternativ können diese im Marketplace veröffentlicht und somit allen Anwendern zugänglich gemacht werden.

Wie kann ich es in mein Gradle-Projekt einbauen?

Wir bauen auf dem Projekt auf, das wir im letzten Teil der Serie bereits angelegt haben. Den Code dafür findet ihr hier. Alternativ könnt ihr der Schritt-für-Schritt-Anleitung aus dem letzten Teil folgen.

Im letzten Post haben wir bereits ausprobiert, wie man die gebauten Artefakte manuell in GitHub Packages veröffentlichen kann. Nun werden wir das mit GitHub Actions kombinieren und bei jedem Commit automatisch ausführen.

Sämtlicher Code, den wir in den folgenden Abschnitten erstellen, ist auch in diesem Repository zu finden: https://github.com/FlowSquad/devops-github-actions

Die Action erstellen

Dafür legen wir zunächst den Ordner .github/workflows im Root-Verzeichnis unseres Projekts an. Darin erstellen wir die Datei publish.yaml. Diese definiert die Action und sieht wie folgt aus:

name: Build & Publish
on: # Defines the events that this action responds to
push: # Run whenever new commits are pushed
branches: \[ master ] # Only if target branch is master
jobs:
build:
runs-on: ubuntu-latest # The container to run this action on
steps:
- name: Checkout sources
uses: actions/checkout@v2 # Use action from marketplace
- uses: actions/setup-java@v1
with: # Pass parameters to action
java-version: '11.0.4'
java-package: jdk
architecture: x64
- name: Build and publish JAR # Used to execute gradle commands
run: gradle \\
-Pgpr.key=\${{ secrets.GITHUB_TOKEN }} \\
-Pgpr.user=\$GITHUB_ACTOR \\
:devops-github-packages-library:build \\
:devops-github-packages-library:publish
view raw publish.yaml hosted with ❤ by GitHub

Innerhalb der Action wird zunächst der Code ausgecheckt. Anschließend wird Java 11 bereitgestellt, ein Gradle-Build ausgeführt und das Ergebnis veröffentlicht.

Unser Projekt anpassen

Im Repository können wir außerdem die Dateien gradle.properties in den beiden Modulen entfernen. Zudem müssen wir die build.gradle-Dateien anpassen:

  1. Wir erhöhen die Version auf 1.0.1, um beim Publishen einen Versionskonflikt zu vermeiden. Diese Änderung muss in beiden Modulen und im Root-Projekt vorgenommen werden.
  2. Wir ersetzen die URL des Zielrepositories, damit es in unserem neuen Repository landet. Diese Änderung muss in beiden Modulen vorgenommen werden.
  3. Wir erhöhen die Version der Library-Dependency ebenfalls auf 1.0.1, um das neue Artefakt zu verwenden. Diese Änderung ist nur im main-Modul notwendig.

Authentifizierung bei GitHub Packages

Um erfolgreich pushen zu können, brauchen wir normalerweise einen entsprechenden Token, der uns den Zugriff erlaubt. Falls wir direkt im selben Repository veröffentlichen, benötigen wir diesen Token nicht.

Stattdessen können wir die von GitHub automatisch bereitgestellte Variable $GITHUB_ACTOR und das Secret GITHUB_TOKEN verwenden. Der GitHub Actor ist der Name des Accounts, welcher die Action ausgelöst hat. Der GitHub Token enthält einen temporären Token, welcher Berechtigungen für das aktuelle Repository enthält.

In ein anderes GitHub Packages Repository publishen

Falls es unser Ziel ist, in einem anderen Repository zu veröffentlichen, müssen wir die Properties gpr.user und gpr.token überschreiben und dafür den letzten Schritt in der publish.yaml anpassen.

# ...
- name: Build and publish JAR # Used to execute gradle commands
run: gradle \\
-Pgpr.key=\${{ secrets.GPR_KEY }} \\
-Pgpr.user=\${{ secrets.GPR_USER }} \\
:devops-github-packages-library:build \\
:devops-github-packages-library:publish
view raw publish.yaml hosted with ❤ by GitHub

Anschließend müssen wir in den Repository-Einstellungen zwei neue Secrets anlegen (GPR_USER und GPR_TOKEN) und darin die Zugangsdaten speichern, die wir bisher in der gradle.properties-Datei hatten.

Die Secrets schützen zwar die Credentials vor einem unerlaubten Kopieren, allerdings kann jeder mit Push-Zugriff auf das Repository sie im Rahmen einer Action verwenden.

Namenskonflikte verhindern

Da wir in ein anderes Repository als im letzten Teil der Serie publishen wollen, müssen wir den Package-Namen ändern. Andernfalls liefert GitHub einen Fehler 422 Unprocessable Entity zurück. Das liegt daran, dass Package-Gruppe und -Name innerhalb einer Organisation / eines Accounts eindeutig sein müssen.

Aus diesem Grund fügen wir folgende Zeile in der build.gradle des library-Moduls ein:

// Configures the publishing
publishing {
publications {
gpr(MavenPublication) {
artifactId 'devops-github-actions-library' // <-- Add this line
from(components.java)
// ...
}
}
}
view raw build.gradle hosted with ❤ by GitHub

Außerdem müssen wir die Dependency im main-Modul ebenfalls umbenennen in devops-github-actions-library.

Die Action ausführen

Sobald wir unsere Änderungen committen und nach GitHub pushen, wird die neue Action automatisch gestartet. Um dies zu prüfen, öffnen wir GitHub, wo wir neben unserer letzten Commit-Message einen kleinen gelben Punkt sehen. Dieser gibt an, dass die Action gerade läuft. Das Ergebnis wird nach Abschluss des Builds als grüner Haken oder rotes Kreuz dargestellt.

Um die Build-Details zu sehen, klicken wir in unserem Repository auf Actions, wählen unseren Commit aus und klicken links auf Build & Publish / build. Dort werden die Logs unserer Action live gestreamt, während sie läuft. Sobald der Build abgeschlossen ist, können wir wieder auf die Startseite unseres Repositories wechseln, um dort die neu erstellte Version zu betrachten.

Fazit

In diesem Teil der Serie haben wir das Bauen und Veröffentlichen eines Gradle-Projekts mithilfe von GitHub Actions betrachtet. Im nächsten Teil werden wir das Bauen eines Docker Images in GitHub Actions ausprobieren.