Wie man Featurebranches mit Jenkins baut

Meine Güte! Wie viel Zeit habe ich bereits in dieses Thema investiert und am Ende ist die Lösung meines Problems dann doch so simpel…

Aber fangen wir von vorn an. Der moderne Entwickler arbeitet in der Regel mit git und im optimalen Fall nach dem gitflow Konzept bzw. mit Featurebranches. Ist der Code fertig gestellt, wird ein für den Branch ein Review durchgeführt und davor bzw. gleichzeitig wird durch eine CI – in meinem Fall Jenkins – geprüft, ob dieser Branch denn so überhaupt noch lauffähig ist. Im konkreten Beispiel ist Atlassian Bitbucket dabei der Git host, es werden Pull Requests verwendet und um diese mergen zu können, muss Jenkins melden, dass der Code erfolgreich integriert werden konnte.

Jenkins ist dabei so eingestellt, dass es einen Job gibt, der auf sämtliche Branches eines Repositories lauscht. Sobald es einen Push gibt, checkt Jenkins diesen Code-Stand aus und baut ihn. Wenn alles gut ist meldet er das an Bitbucket zurück. Das Problem ist nun: was ist, wenn der Build wegen eines Problems auf der Buildnode, fehlendem Internet oder warum auch immer fehlschlägt? Dann wird man nie den Merge vollziehen können, da die Rückmeldung von Jenkins fehlt. Man kann nun einfach irgendeinen weiteren Commit machen, diesen pushen und es geht von neuem los. Oder aber, man macht es folgendermaßen:

Der ursprüngliche Job (“Job-01”), der auf alle Branches gelauscht hat, wird so umgebaut, dass er parametrisiert ist (Parametername “Branchname” z.B.) und im git Bereich wird angegeben , dass nur der Branch “$Branchname” gebaut werden soll. Anschließen legt man einen weiteren Job an (“Job-02”), der auf das gleiche repo sowie Veränderungen in allen Branches lauscht.

Dieser Job wiederum macht nichts anderes, als bei einer erkannten Änderung den parametrisierten Job  (“Job-01”) auszuführen und den entprechenden Branch als Parameter zu übergeben.

Nun haben wir erstmal den gleichen Zustand wie vorher – aber zusätzlich die Möglichkeit, den parametrisierten Job von Hand und unter Angabe des gewünschten Branches zu bauen. Also eben z.B., wenn der Job vorher fehlgeschlagen ist. Dieser wiederum kann dann seinen Status auch an Bitbucket melden.

Jenkins OSX Slave für das automatisierte Bauen von iOS Apps

Die Aufgabe ist simpel: ich möchte in meine Jenkins Build Umgebung einen Rechner mit Mac OSX einbinden, der für mich automatisiert iOS Apps bauen soll. Für die Vereinfachung verwende ich dazu das xctool von Facebook, welches das Handling von XCode und co. übernimmt.

Mein Vorgehen war folgendes: Jenkins User auf dem Zielsystem eingerichtet und anschließend den Jenkins Slave per ssh vom Master aus gestartet. Das funktioniert auch alles wunderbar, aber sobald man im Build versucht auf eine Gui Applikation zuzugreifen – das macht z.B. der Aufruf der Unit Tests, weil diese den iPhone Simulator benötigen – knallt xctool ohne eine Fehlermeldung mit einem Exit Code 1 weg. Das Problem an der Stelle ist einfach, dass es in OSX einen Unterschied macht, ob man ein Programm per ssh oder direkt in einer Shell auf dem System startet. Die Variante über ssh darf eben nicht auf GUI Applikationen zugreifen. Und somit laufen die Tests nicht.

Korrekterweise muss man den Jenkins Slave als per JNLP starten. Dazu stellt man im Master in den Slave Einstellungen die „Startmethode“ auf „Starte Slave Agenten über JNLP“. Nach dem Speichern wir einem in der Übersicht des Knotens ein Shell Command angezeigt, den man auf dem Slave ausführen soll:

java -jar slave.jar -jnlpUrl http://[PFAD_ZUM_JENKINS_MASTER]/computer/[SLAVE_NAME]/slave-agent.jnlp -secret [SECRET_HASH]

In diesem Command ist die Datei „slave.jar“ verlinkt und kann heruntergeladen werden. Genau das sollte man auf dem OSX Slave machen und die Datei irgendwo auf der Platte ablegen. Anschließen startet man das Programm „Automator“. Dort wählt man „Applikation“ aus und sucht anschließend in der Bibliothek nach „Shell Script ausführen“. Diesen Eintrag zieht man rechts in die graue Fläche.

In das nun vorhandene Textfeld trägt man folgendes Shellscript ein:

cd [PFAD_ZUR_JAR_DATEI]
java -jar slave.jar -jnlpUrl http://[PFAD_ZUM_JENKINS_MASTER]/computer/[SLAVE_NAME]/slave-agent.jnlp -secret [SECRET_HASH]

Das ganze speichert ihr im Ordner Programme als „run_jenkins_slave“ ab. Nun geht ihr in die „Systemeinstellungen” -> “Benutzer und Gruppen“ und wählt bei Jenkins die „Startobjekte“ aus. Dort fügt ihr nun das eben erstellte Programm „run_jenkins_slave“ hinzu. Anschließend geht ihr noch auf „Anmeldeoptionen“ und stellt da den User „Jenkins“ für den automatischen Login ein.

Wenn ihr nun den OSX Rechner neu startet, dann sollte er automatisch einen Jenkins Slave starten und diesen beim Master registrieren. Nun sind wir genauso weit wie mit dem ssh Weg – aber: wenn man nun Jobs startet, dann dürfen diese auch auf GUI Applikationen zugreifen. Sprich, die Unit Tests mit dem iPhone Simulator laufen nun ohne Probleme durch. Der Jenkins Slave verhält sich in etwa so, als ob man direkt auf dem Desktop eine Shell startet und dann Befehle ausführt.

[Quicktip] Jenkins meckert mit einem reject HostKey trotz korrektem Eintrag in der known_hosts Datei

Ihr möchtet mit Jenkins auf einen anderen Server per ssh/scp zugreifen, habt auf der Shell bereits erfolgreich mit dem User Jenkins eine ssh Verbindung aufbauen können, aber im Build Prozess bekommt ihr folgenden Fehler:

com.jcraft.jsch.JSchException: reject HostKey: ...

Das Problem ist relativ simpel zu lösen – die Einträge in der ~/.ssh/known_hosts dürfen nicht verschlüsselt sein. Lösen könnt ihr das folgendermaßen:
– alten Hosts Eintrag löschen: ssh-keygen -R [SERVER_NAME]
– in eurer ssh_config den Parameter „HashKnownHosts“ auf “no” setzen
– per ssh auf den Server eine Verbindung aufbauen und die Frage, ob der Key hinzugefügt werden darf, mit ja beantworten

Nun sollte der Eintrag im Klartext in der known_hosts stehen und Jenkins sollte die Verbindung aufbauen können.

[Quicktip] Jenkins baut bereits gelöschte git Branches

Besonders wenn man einen Jenkins Job nicht auf einen bestimmten Branch einschränkt wird man dieses Problem schnell bemerken: Jenkins baut in bestimmten Fällen auf Basis von git Branches, die eigentlich bereits gelöscht sind. Der Fehler tritt auf, weil Jenkins per default das lokale git Repository nicht mit dem Origin bezüglich gelöschter Branches synchronisiert. 

Dieses Problem kann man relativ einfach beheben. Dazu geht ihr in die Konfiguration des betroffenen Jenkins Job und im Bereich “Source-Code-Management“ wählt ihr im Dropdown “Additional Behaviours“ den Punkt “Prune stale remote-tracking branches“. Damit wird vor dem Pull bzw. Fetch genau dieser Sync durchgeführt. Sprich, es werden lokal alle Branches gelöscht, die im Origin nicht mehr vorhanden sind.

[Quicktip] Globalen SSH Key in Atlassian Stash hinterlegen

Wenn ihr in Stash viele Repositories habt und ein CI Tool wie Jenkins oder Teamcity nutzt, dann möchtet ihr sicher nicht bei jedem einzelnen Repo den SSH Key des Tools hinterlegen. Da ich lange nach der entsprechenden Stelle in Stash gesucht habe und in den Settings nichts dazu zu finden ist, hier eine mögliche Lösung für das Problem:

Die eine globale Stelle für das Problem gibt es nicht, ABER ihr könnt pro Projekt Zugriffsschlüssel hinterlegen. Ruft dazu einfach das Projekt auf, geht dann in die Einstellungen und dort auf Zugriffsschlüssel. Alle hier hinterlegten Keys können nur lesend oder auch lesend und schreibend für ALLE Repositories dieses Projektes freigeschalten werden. So lange ihr also nicht über allzu viele Projekte verfügt, ist die Einrichtung schnell erledigt 😉