GitHub-Link in einer Spring-Boot-Anwendung

Keine Kommentare

Vor einer Weile habe ich beschrieben, wie man eine Spring-Boot-Anwendung bei Heroku deployt. Da ich bei Heroku für diese Anwendung automatische Deployments vom Master Branch aktiviert habe, möchte ich auf einen Blick sehen können, von welchem Commit die laufende Anwendung gebaut und deployt wurde. Deshalb werde ich in diesem Blogpost zeigen, wie man einen Link zu einem Commit auf GitHub zu einer Spring-Boot-Webanwendung hinzufügt. Da ich das Beispiel aus dem letzten Blogpost wieder verwende, werden alle Beispiele Apache maven als Build Tool verwenden und Spring Boot als Application Framework. Darüber hinaus zeige ich, wie man den Build so konfiguriert, dass das ganze lokal und beim Deployment nach Heroku funktioniert.

Statischen Link hinzufügen

Die einfachste Möglichkeit um Informationen dieser Art in einem Web View anzuzeigen, ist die Internationalisierungsfunktionen von Thymeleaf zu verwenden. Der Vorteil ist, dass dann kein zusätzliche Code zu den Controllern hinzugefügt werden muss, um den Commit Link anzuzeigen. Spring Boot bindet automatisch eine Datei mit dem Name messages.properties ein, wenn diese direkt unter src/main/resources liegt. Die Übersetzungen in dieser Datei sind dann auch für Thymeleaf Views verfügbar. Für den Anfang füge ich erstmal nur statische Informationen hinzu:

Ich möchte nicht, dass der ganze SHA-1 Commit Hash in meiner Anwendung angezeigt wird. Deshalb habe ich ihn abgekürzt. Des Weiteren habe ich entschieden, den ganzen Link zu Github in der Properties-Datei zusammenzusetzen und nicht in der View. So kann ich den Link in verschiedenen Templates wiederverwenden. Außerdem ist die Information darüber, wie Commit Links aufgebaut werden, an einer Stelle gekapselt. Um die neuen Daten in der Anwendung anzuzeigen, muss das home.html Template erweitert werden:

Nachdem die Anwendung das nächste Mal gestartet wird, wird der statische Link bereits angezeigt:

Spring Boot Heroku Demo Anwendung mit statischem Link

Commit Hash mit maven extrahieren

Einen Link zu einem fest definierten Commit zu haben ist gut, aber leider wird die Information nach dem nächsten Commit inaktuell. Deshalb muss der Commit Hash dynamisch während des Buildprozess gesetzt werden. Dies kann mit Hilfe vom buildnumber-maven-plugin in Verbindung mit Resource filtering erreicht werden. Ich habe die folgende Konfiguration zum maven Build hinzugefügt und so das buildNumber Property im Build verfügbar gemacht (die scm Konfiguration wird ebenfalls für das buildnumber-maven-plugin benötigt):

Die resources-Konfiguration sieht etwas seltsam aus, oder? Aber es ist wichtig es genau so zu machen. An dieser Stelle aktiviere ich das filtering nur für die Datei messages.properties. Für alle anderen Ressourcen deaktiviere ich es. Andernfalls würden auch den Thymeleaf views gefiltert werden. Da Thymeleafs Expression Language ebenfalls mit der ${...} Notation arbeitet, besteht die Möglichkeit, dass Ausdrücke ersetzt werden, bei denen das nicht gewollt ist. Mit der neuen Buildkonfiguration kann die messages.properties Datei folgendermaßen geändert werden:

Anm.: Dieser Blogpost behandelt Spring Boot 1.2.x. Ab Version 1.3 muss beim Resource Filtering das @ als Delimiter für Build Properties verwendet werden. Ab Version 1.3 muss es also git.commit.hash=@buildNumber@ heißen. Siehe auch Spring Boot 1.3 Release Notes.

Kapseln von SCM-Informationen

Wenn man sich den Wert des git.commit.link-Property anschaut, sieht man, dass hier Informationen aus dem scm-Block des POM dupliziert sind. Aus diesem Grund ist es sinnvoll, diesen Wert mithilfe des project.scm.url Buildproperties zu erzeugen. Eine Möglichkeit, das zu tun, ist den Wert von git.commit.link zu ${project.scm.url}/commit/${buildNumber} zu ändern. Das ist okay, aber ich finde es besser, alle Properties, die zur Buildzeit feststehen, an einer zentralen Stelle zu definieren, und diese Stelle ist das POM. Aus diesem Grund habe ich die folgende Konfiguration zu meinem POM hinzugefügt:

So ist die Information, welcher SCM Hosting Service für das Projekt verwendet wird, im Build gekapselt. Nachdem das POM so geändert wurde, sieht die messages.properties-Datei so aus:

Den Commit Hash abkürzen

Nachdem wir die Anwendung das nächste Mal gestartet haben, können wir uns davon überzeugen, dass der Commit Link gerendert wird.(*) Aber was müssen wir feststellen? Anders als beim initialen Entwurf wird jetzt der gesamte Commit Hash in die Seite gerendert, was ziemlich unleserlich ist.

Anwendung rendert den gesamten Commit Hash

Schauen wir mal, was wir da tun können! Eine Möglichkeit, den Commit Hash abzukürzen ist, dies in Java zu tun: Wir könnten den HomeController verwenden, um den Wert zu kürzen und ihn dann dem Modell hinzufügen. Ich habe das exemplarisch in diesem kleinen Gist implementiert. Diese Lösung hat aber einige Nachteile:

  1. Das Modell wird mit Informationen verschmutzt, die nicht mit der Anzeige von Records zu tun haben (was im Moment die einzige Zuständigkeit vom HomeController ist).
  2. Es wird kompliziert, den Commit Hash an unterschiedlichen Stellen wieder zu verwenden, da er jetzt an den HomeController gekoppelt ist.
  3. In MVC ist es Aufgabe des View zu entscheiden, wie Werte gerendert werden, nicht die des Controllers.

Aus diesen Gründen habe ich mich entschieden, Thymeleafs String Utilities zu verwenden, um den Commit Hash zu kürzen. Den Ausdruck richtig hinzubekommen ist etwas tricky, deshalb zeige ich zuerst die Lösung und erkläre sie danach Schritt für Schritt:

Wie man sehen kann, ist das th:text der entscheidende Part. Das äußere ${...} weist Thymeleaf an den Rest als Expression zu interpretieren. Das ist wichtig mit Blick auf das, was dann folgt: #strings.substring(...) ruft Thymeleafs String Utilities auf. Ohne das ${...} würde Thymeleaf den Aufruf einfach als Text rendern. Als nächstes werden drei Parameter an die substring Funktion übergeben – '__#{git.commit.hash}__', 0 und 7. Die letzteren beiden sind einfach, es handelt sich um den Sart- und den Endindex für die substring-Funktion. Der erste Parameter ist der String, von dem ein Substring gebildet werden soll. Da wir einen String aus den messages.properties formatieren wollen, müssen wir Thymeleaf noch einmal anzeigen, dass hier eine spezielle Verarbeitung statt finden soll. Dafür sind die Unterstriche da. Mit diesem Code bekommen wir schließlich das gewünschte Ergebnis:

Git Commit Hash abgekürzt mit Hilfe von #strings.

(*) Es kann sein, dass das Resource filtering nicht funktioniert, wenn die Anwendung über die IDE gestartet wird. Das liegt daran, dass der Laufzeit-Classpath, den die IDE zusammenbaut, anders sein kann als der Classpath, der aufgebaut wird, wenn die Anwendung per java -jar app.jar gestartet wird.

Buildkonfiguration für Heroku

Dieses neue Feature online zu bringen ist nur einen git push entfernt, wenn man nach Heroku deployt. Tatsächlich zeigt sich aber folgendes Bild nach dem Deployment:

Kaputter Link nach dem Deployment bei Heroku

Aus irgend einem Grund funktioniert der Link nicht, wenn die Anwendung auf der Heroku-Buildinfrastruktur gebaut wird. Der erste Hinweis, woran es liegt, kann man dem Build log entnehmen:

Das buildnumber-maven-plugin kann die aktuelle Revision nicht von git ermitteln. Das bedeutet, dass das, was lokal funktioniert hat, in der Zielumgebung schief geht. Nach etwas Recherche konnte ich rausfinden, warum. Der Heroku slug compiler löscht das .git-Verzeichnis aus der Working Copy, bevor der Build gestartet wird. Aus diesem Grund kann die Revision nicht extrahiert werden. Glücklicherweise stellt Heroku die SOURCE_VERSION-Umgebungsvariable zur Buildzeit bereit, die den Commit Hash enthält. Damit das Extrahieren des Commit Hashs sowohl lokal als auch bei Heroku funktioniert, muss der Buildprozess erkennen, wo er ausgeführt wird. Das schreit nach verschiedenen Profilen. Also definieren wird ein Profil, das den Wert des buildNumber Properties auf den Wert der SOURCE_VERSION Umgebungsvariable setzt, wenn diese vorhanden ist:

Dieses Profil allein reicht noch nicht aus, da das buildnumber-maven-plugin immer noch ausgeführt wird und den Wert aus dem Profil überschreibt. Aus diesem Grund muss ein zweites Profil definiert werden, das standardmäßig läuft und das buildnumber-maven-plugin ausführt, aber nur, wenn das Heroku-Profil nicht aktiv ist. Dafür verwende ich die activeByDefault Anweisung. Diese sagt maven, dass ein Profil nur dann aktiv sein soll, wenn kein anderes Profil aktiv ist. Nachdem die Konfiguration des buildnumber-maven-plugin in das neue Profil verschoben wurde, sieht das POM so aus:

Und schlussendlich funktioniert der GitHub Commit Link lokal und auf Heroku. Das Gesamtbeispiel findet sich im spring-boot-heroku-demo Repository

Zusammenfassung

Das Hinzufügen der Build-Revision zum UI einer Anwendung kann Fehleranalysen vereinfachen. Kombiniert man Werkzeuge wie Apache maven, Spring Boot und Thymeleaf, ist es relativ leicht, dies zu erreichen. In diesem Blogpost habe ich gezeigt, wie man einen Git Commit Hash mithilfe von maven extrahiert und was man beachten muss, damit es lokal und beim Deployment nach Heroku funktioniert. Ich habe erklärt, wie der Wert mithilfe von Thymeleaf’s Internationalisierung geladen werden kann und wie der Wert mit dem #strings Utility Object abgekürzt werden kann. Mit diesen Informationen sollte man in der Lage sein, mein Beispiel auf seine eigene Anwendung und Umgebung anzupassen.

Benedikt Ritter arbeitet seit September 2013 als Software Craftsman bei der codecentric AG. Sein Können bringt er nicht nur in der Berufswelt zum Einsatz: Benedikt ist Member der Apache Software Foundation und Committer beim Apache Commons Projekt.

Share on FacebookGoogle+Share on LinkedInTweet about this on TwitterShare on RedditDigg thisShare on StumbleUpon

Kommentieren

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.