DevOps mit GitHub - Teil 1: GitHub Packages mit Gradle

Im ersten Teil dieser Serie werden wir uns damit beschäftigen, wie wir GitHub Packages als private Package-Registry für unsere Gradle-Projekte nutzen können.

Also published in English at medium.com.

Vor ziemlich genau einem Jahr kündigte GitHub die Beta für ihr neuestes Produkt an – die GitHub Package Registry. Sie sollte das zentrale Repository für Artefakte werden, die aus dem Code entstehen, der bei GitHub gehostet wird. Neben npm sollten Docker, Maven, NuGet und RubyGems unterstützt werden.

Sie ist ein weiterer Baustein in GitHubs Strategie, den gesamten Weg von der Erstellung des Codes bis zur Auslieferung zu begleiten. Gleichzeitig wurde GitHub Actions angekündigt, mit denen wir uns im nächsten Teil der Serie beschäftigen werden.

Mittlerweile wurde das Produkt in GitHub Packages umbenannt und die Beta-Phase beendet. In diesem Blogbeitrag werden wir einen ersten Blick darauf werfen, wie GitHub Packages in Verbindung mit Gradle funktioniert.

Was ist GitHub Packages?

Während die meisten Artefakte auf MavenCentral zu finden sind, haben viele große Unternehmen zusätzlich interne Package Registries aufgebaut, in denen ihre internen Bibliotheken liegen.

GitHub Packages bietet genau das an, indem jedem GitHub-Account eine Registry bereitgestellt wird und jedes Repository gleichzeitig auch als Registry für Artefakte dienen kann.

Der Service ist für öffentliche Repositories kostenfrei, private Repositories erhalten 500MB Speicher und 1GB Traffic pro Monat umsonst. Kostenpflichtige Tarife haben höhere Inklusivvolumina. Zusätzlicher Speicher und Traffic kann kostenpflichtig dazugebucht werden mehr.

Wie kann ich es in mein Gradle-Projekt einbauen?

In den nächsten Abschnitten werden wir Schritt für Schritt ein neues Gradle-Multi-Module-Projekt anlegen und GItHub Packages integrieren. Wir verwenden dafür IntelliJ, aber eclipse sollte ähnliche Funktionalität bereitstellen.

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

project

Dafür legen wir zunächst ein neues Projekt „devops-github-packages“ mit zwei Modulen an: „devops-github-packages-main“ und „devops-github-packages-library“. Das main-Modul wird später eine Abhängigkeit auf das library-Modul haben, welches direkt aus GitHub Packages geladen wird.

Anschließend müssen wir die build.gradle im library-Modul anpassen. Dort aktivieren wir das Plugin „maven-publish“ und konfigurieren das Ziel-Repository:

plugins {
id 'java-library'
// Erlaubt das Publishen der Artefakte im Rahmen des Builds
id 'maven-publish'
}
sourceCompatibility = JavaVersion.VERSION_11
group 'io.flowsquad.blog'
version '1.0.0'
repositories {
mavenCentral()
}
// Konfiguriert das Publishen
publishing {
repositories {
// Das Ziel-Repository
maven {
// Der Name kann beliebig gewählt werden
name = "GitHubPackages"
// Die URL des Repositories, in dem die Artefakte veröffentlicht werden sollen
url = "https://maven.pkg.github.com/FlowSquad/devops-github-packages"
credentials {
// Die Zugangsdaten (weiter unten beschrieben)
username = project.findProperty("gpr.user")
password = project.findProperty("gpr.key")
}
}
}
publications {
gpr(MavenPublication) {
from(components.java)
}
}
}
view raw build.gradle hosted with ❤ by GitHub

Die Zugangsdaten, die hier angegeben werden, müssen wir für dieses Modul in einer gradle.properties-Datei konfigurieren. Als Username verwenden wir dabei den GitHub-Login und als Key unseren Personal Access Token konfiguriert werden. Diesen legen wir im nächsten Schritt an.

gpr.user=githubUser
gpr.key=XXX
view raw gradle.properties hosted with ❤ by GitHub

Um einen Personal Access Token anzulegen, müssen wir zunächst in den Profileinstellungen in GitHub links unten auf „Developer settings“ klicken. Dort wählen wir „Personal access tokens“ und dann „Generate new token“. Als Namen wählen wir „GitHub Packages Access Token“. Dieser benötigt folgende Scopes: repo, write:packages, read:packages und read:org (falls das Ziel-Repository innerhalb einer Organisation liegt).

Anschließend klicken wir auf „Generate token“ und kopieren den angezeigten Token in die oben erstellte gradle.properties-Datei. Der Token darf natürlich niemals eingecheckt werden, da er wie ein Passwort funktioniert.

Im library-Modul legen wir nun eine neue Klasse „LibraryClass“ an, die den Code enthält, den wir später aus dem main-Modul heraus aufrufen werden:

package io.flowsquad.blog.devops.github.packages.library;
public class LibraryClass {
public String utilityMethod() {
return "Hi, I'm a very expensive library method and do a lot of heavy calculations!";
}
}
view raw LibraryClass.java hosted with ❤ by GitHub

Wenn wir jetzt das Kommando „./gradlew :devops-github-packages-library:publish“ ausführen, baut Gradle unsere Bibliothek und veröffentlicht sie auf GitHub. Wir sollten sie dann im Reiter „Packages“ unseres Repositories sehen können:

github header

github

Nun kümmern wir uns um das main-Modul. Auch hier muss die build.gradle angepasst werden. Der Dependencies-Block enthält das Library-Modul, das wir vorhin gebaut haben. Wir könnten natürlich auch direkt das Modul verlinken, aber wir wollen ja GitHub Packages testen, nicht wahr? ;)

plugins {
id 'java'
}
sourceCompatibility = JavaVersion.VERSION_11
group 'io.flowsquad.blog'
version '1.0.0'
repositories {
mavenCentral()
maven {
// Der Name kann beliebig gewählt werden
name = "GitHubPackages"
// Die URL des Repositories, in dem die Artefakte veröffentlicht wurden
url = "https://maven.pkg.github.com/FlowSquad/devops-github-packages"
credentials {
// Die Zugangsdaten (weiter unten beschrieben)
username = project.findProperty("gpr.user")
password = project.findProperty("gpr.key")
}
}
}
dependencies {
compile "io.flowsquad.blog:devops-github-packages-library:1.0.0"
}
view raw build.gradle hosted with ❤ by GitHub

Auch in diesem Modul müssen wir die Zugangsdaten bereitstellen, genauso wie wir das bereits für das library-Modul getan haben. Dafür kopieren wir die gradle.properties-Datei, die wir bereits erstellt haben, in das main-Modul.

Vorsicht: Ein Access-Token ist immer notwendig, um Artefakte herunterzuladen, auch wenn diese in einem öffentlichen Repository liegen!

Jetzt fehlt noch die Main-Klasse, in der wir die Utility-Methode aufrufen, die wir in der Library-Klasse erstellt haben:

package io.flowsquad.blog.devops.github.packages.main;
import io.flowsquad.blog.devops.github.packages.library.LibraryClass;
public class MainClass {
public static void main(String[] args) {
System.out.println(new LibraryClass().utilityMethod());
}
}
view raw MainClass.java hosted with ❤ by GitHub

Wenn wir die Anwendung nun starten, lädt Gradle das Artefakt von GitHub herunter, das wir vorher gebaut haben, baut damit das Main-Modul und führt es aus. Wenn alles klappt, sollten wir folgende Meldung auf der Konsole sehen:

console

Wir haben jetzt erfolgreich ein Artefakt auf GitHub Packages veröffentlicht, es heruntergeladen und verwendet. Allerdings gibt es einige Fallstricke, die man kennen sollte, bevor man die Package Registry produktiv einsetzt:

  1. Snapshots funktionieren nicht
    Auch wenn in der Dokumentation steht, dass Snapshots unterstützt werden, konnten wir sie nicht zum Laufen bringen. Während die ersten Builds problemlos funktionieren, werden alte Versionen nicht gelöscht. Stattdessen werden immer neue Artefakte zum selben Release hinzugefügt, was irgendwann nicht mehr funktioniert. Ab diesem Zeitpunkt werden alte Snapshots geladen.
  2. Mehrere Artefakte pro Release funktionieren nicht
    Sobald man versucht, neben der Bibliothek selbst auch noch JavaDoc- und Sources-Artefakte hochzuladen, wird das Repository instabil. Selbst das Laden existierender Artefakte oder das Veröffentlichen neuer Bibliotheksversionen scheitern dann mit einem 400-Fehler. In unserem Fall half nur noch das Löschen des Artefakts oder Repositories.
  3. Öffentliche Versionen können nicht gelöscht werden
    Es ist zwar möglich, Artefakt-Versionen in privaten Repositories zu löschen, bei öffentlichen Repositories erlaubt GitHub das allerdings nicht. Deshalb sollte man sich vorher gut überlegen, was man veröffentlicht und was man für sich behält ;)
  4. Der Token benötigt die korrekten Scopes
    Der Personal Access Token, der zum Zugriff auf die Artefakte benötigt wird, muss exakt die oben aufgelisteten Scopes haben, damit alles ordnungsgemäß funktioniert. Falls einer davon fehlt, erhält man Fehlermeldungen, die häufig nicht auf das Fehlen einer Berechtigung schließen lassen!
  5. Dynamische Versionen verhindern das Release
    Falls man Spring Boot und die dynamischen Versionen verwendet, schlägt der Release fehl, weil die Versionen nicht statisch sind. Das hat zwar nicht direkt etwas mit GitHub Packages zu tun, ist aber trotzdem gut zu wissen. Um das zu beheben, muss man die build.gradle so anpassen, dass im Publishing-Block auch noch ein versionMapping vorhanden ist:
// Siehe oben
// Konfiguriert das Publishen
publishing {
repositories {
// Siehe oben
}
publications {
gpr(MavenPublication) {
from(components.java)
// Behebt den Fehler mit den dynamischen Versionen von Spring Boot
versionMapping {
usage('java-api') {
fromResolutionOf('runtimeClasspath')
}
usage('java-runtime') {
fromResolutionResult()
}
}
}
}
}
view raw build.gradle hosted with ❤ by GitHub

Im zweiten Teil der Blog-Serie werden wir uns damit beschäftigen, wie wir GitHub Actions verwenden können, um unsere Build-Pipeline so zu automatisieren, dass immer automatisch die neueste Version veröffentlicht wird sobald wir neuen Code einchecken. Bis zum nächsten Mal!