Aus Linux-Magazin 10/2014

Perl-Skript hilft Builds und Tests zu automatisieren

© Andrew Koh Kien Ting, 123RF

Statt den Continuous-Integration-Server Jenkins im Browser mittels Mausklicks und Texteingaben für Builds zu konfigurieren, legen faule Tipper die nötigen Daten im Source-Control-System ab und lassen ein Perl-Skript die Handlangerarbeit tun.

Continuous Integration (CI) und die damit einhergehende Produktivitätssteigerung sind heute feste Größen in der Entwicklungsabteilung. Rein mit dem Code ins Source-Control-System, einen Pull-Request abgesetzt, schnell mal einen Mitstreiter drüberschauen lassen – schon erfasst das Räderwerk der CI-Pipeline die Änderung, unterwirft sie der stetig wachsenden Testsuite – und schwups ist alles auf einmal veröffentlicht. CI-Server wie Jenkins [2] oder Teamcity [3], die sich die neuesten Source-Versionen eines Projekts schnappen und einen Build mit allen Test anwerfen, erfreuen sich wachsender Beliebtheit.

Doch jedes Mal zwei Minuten beim Aufsetzen eines neuen Buildprojekts in der bräsigen Jenkins-Oberfläche zubringen, das zermürbt auch noch den motiviertesten Entwickler. Wie Abbildung 1 zeigt, gilt es, die notwendigen Kästchen zu finden und zu aktivieren und einige Textfelder auszufüllen.

Abbildung 1: Jenkins führt gerade Build und Test des auf Github abgelegten CPAN-Projekts Log4perl aus.

Abbildung 1: Jenkins führt gerade Build und Test des auf Github abgelegten CPAN-Projekts Log4perl aus.

Wenn jedes Perl-Projekt die gleiche Kommandosequenz zum Starten des Buildprozesses verlangt, ist es überflüssig, sie bei jedem neuen Projekt unverändert einzutippen. Auch bleibt die Frage: Was passiert, falls ein paar Dutzend Projekte auf einen neuen CI-Server umziehen, geht dann der Tipp- und Klickwahnsinn von vorne los?

Lieber minimal

Dem Perlmeister schwebt ein eher minimalistischer und konventioneller Ansatz vor, wie ihn etwa Travis-CI [4] schon seit Langem praktiziert. Im hier diskutierten Fall gibt deshalb eine kleine Datei im Sourcecode des Projekts an, von welchem Git-Repository Jenkins den Quellcode abholen kann und unter welchem Namen das Projekt auf dem Jenkins-Server laufen soll.

Das hier vorgestellte Perl-Skript baut anschließend die Jenkins-Konfiguration als XML-Dokument zusammen und setzt sie als POST-Request über das Jenkins-API an den Server ab. Und schon ist das Projekt dort eingebaut, ganz ohne irgendwelche Tipparbeit.

Woher nehmen und nicht stehlen?

Doch wie kommt man an das richtige Format der XML-Daten, ohne die Dokumentation oder gar den Sourcecode des Servers lesen zu müssen? Ein einmal von Hand auf der Jenkins-Oberfläche eingetipptes Projekt wie in Abbildung 1 gibt das Geheimnis preis: Der Entwickler hat hier »Git« als Option für das Sourcecode-Repository aktiviert und ins dann erscheinende Textfeld die URL zu Github eingetragen.

Unter dem Namen »log4perl« ist das Projekt auf dem Jenkins-Server aktiv und das Skript aus Listing 2 kann die zugehörigen XML-Daten einfach über das API auslesen. Das CPAN-Modul Jenkins::API bietet dazu die Methode »project_config()« an, die den Server auf Port 8080 des lokalen Hosts kontaktiert und unter Angabe des Projektnamens die Konfiguration erfragt.

Listing 2

jenkins-project-xml

1 #!/usr/local/bin/perl -w
2 use strict;
3 use Jenkins::API;
4
5 my $jenkins = Jenkins::API->new(
6   { base_url => 'http://localhost:8080' });
7
8 print $jenkins->project_config(
9     "log4perl" ), "\n";

Zunächst sollte ein Skript wie in Listing 1 allerdings prüfen, ob der Jenkins-Server überhaupt unter dem voreingestellten Port auf Anfragen reagiert. Klappt dies, kann sich Listing 2 erkundigen, wie denn die Konfiguration eines bestimmten Projekts aussieht.

Listing 1

jenkins-ok

01 #!/usr/local/bin/perl -w
02 use strict;
03 use Jenkins::API;
04
05 my $jenkins = Jenkins::API->new(
06   { base_url => 'http://localhost:8080' });
07
08 if( $jenkins->check_jenkins_url() ) {
09   print "ok\n";
10 } else {
11   print "not ok\n";
12 }

Konfigurationssalat

Von der Methode »project_config()« kommt im Erfolgsfall ein XML-Salat zurück, der unter anderem die von Hand in das Web-UI eingetragenen Werte der URL zum Git-Repository des Projekts offenbart (Abbildung 2).

Abbildung 2: Im XML-Salat der Jenkins-Konfiguration findet sich die auf dem UI eingestellte URL zum Github-Repository des Projekts »[% $git_url %]«.

Abbildung 2: Im XML-Salat der Jenkins-Konfiguration findet sich die auf dem UI eingestellte URL zum Github-Repository des Projekts »[% $git_url %]«.

Mit dieser Vorlage kann nun ein Skript wie in Listing 3 ein Template mit XML-Struktur zusammenstellen und es mittels Variablen (zum Beispiel »[% git_url %]« ) aus dem Template-Toolkit-Modul vom CPAN an vorgegebene Projekte anpassen (Abbildung 3). Das Skript liest die Minimalkonfiguration jedes Projekts aus den Yaml-Daten in Listing 4, bestehend aus dem Namen des Projekts (zum Beispiel Log4perl) und der URL zu den Sourcen auf Github.

Listing 4

jenkins-projects-sync

01 #!/usr/local/bin/perl -w
02 use strict;
03 use YAML qw( LoadFile );
04 use Jenkins::API;
05 use Template;
06 use Log::Log4perl qw(:easy);
07
08 my $projects_data_file =
09   "jenkins-projects.yml";
10 my $jenkins_url = "http://localhost:8080";
11 my $projects_tmpl_file =
12   "project-jenkins.tmpl";
13
14 Log::Log4perl->easy_init($DEBUG);
15
16 my $tt = Template->new();
17
18 my $jenkins = Jenkins::API->new(
19   { base_url => $jenkins_url });
20
21 my $data = LoadFile( $projects_data_file );
22
23 for my $project ( @$data ) {
24   my $xml = $jenkins->project_config(
25     $project->{ name } );
26
27   $tt->process( $projects_tmpl_file,
28     $project, \my $new_xml ) or
29       $tt->error;
30
31   if( $xml =~ /^<\?xml/ ) {
32     INFO "Job for $project->{ name } ",
33          "already exists.";
34
35     if( $new_xml ne $xml ) {
36       INFO
37         "Updating job $project->{ name }";
38       $jenkins->set_project_config(
39         $project->{ name },
40         $new_xml ) or die;
41     }
42     next;
43   }
44
45   INFO "Creating new job for ",
46     "$project->{ name }.";
47
48   $jenkins->create_job(
49     $project->{ name }, $new_xml ) or die;
50 }

Listing 3

jenkins-projects.yml

01 ---
02 -
03     name: log4perl
04     git_url: https://github.com/mschilli/log4perl.git
05 -
06     name: libwww-perl
07     git_url: https://github.com/libwww-perl/libwww-perl.git
08 -
09     name: algorithm-bucketizer
10     git_url: https://github.com/mschilli/algorithm-bucketizer-perl.git
Abbildung 3: In dem aus dem XML generierten Template ersetzt der Template-Prozessor Variablen wie hier die URL zum Git-Repository des Projekts.

Abbildung 3: In dem aus dem XML generierten Template ersetzt der Template-Prozessor Variablen wie hier die URL zum Git-Repository des Projekts.

Nur das unbedingt Notwendige

Stellt das Skript fest, dass die bereits auf dem Jenkins-Server gespeicherten Konfigurationsdaten identisch mit den lokal aus dem Template generierten sind, lässt es das aktuelle Projekt in Ruhe und schreitet zum nächsten. Weichen die Konfigurationen voneinander ab, überschreibt es die Server-Konfiguration mit der neu lokal genierten. Sieht es, dass das Projekt noch nicht auf dem Jenkins-Server existiert, ruft es die Methode »create_job()« auf, um dort einen neuen Buildprozess einzurichten.

Der Ablauf des Skripts erzeugt also unter Umständen folgende Ausgabe:

2014/08/05 21:20:18 Job for log4perl already exists.
2014/08/05 21:20:18 Updating job log4perl2014/08/05 21:20:18 Creating new job for libwww-perl.
2014/08/05 21:20:18 Creating new job for algorithm-bucketizer.

Das erste Projekt musste aufgefrischt werden, während die anderen beiden sich noch gar nicht auf dem Server befanden und neu einzurichten sind.

Das Resultat zeigt Abbildung 4: Alle drei Perl-Projekte wurden identisch eingerichtet und unterscheiden sich nur durch ihre individuellen Github-URLs sowie die Projektnamen. Alle drei kann der User regelmäßig per Cronjob, mit einem Webhook bei eintrudelnden Commits auf Github oder durch einen manuellen Mausklick auf »Build Now« loslaufen lassen.

Abbildung 4: Drei verschiedene Projekte laufen auf einer Jenkins-Installation, allesamt per Perl-Skript eingespielt.

Abbildung 4: Drei verschiedene Projekte laufen auf einer Jenkins-Installation, allesamt per Perl-Skript eingespielt.

Virtuell installieren

Die Installation des Jenkins-Servers auf dem häuslichen Desktop des Autors fand in einer mit Vagrant erstellten Virtualbox-VM statt. Das sollte den Rechner sauber halten und bewirken, dass er sich zu Testzwecken bequem über vorher erstellte Snapshots auf einen leeren Jenkins-Server zurücksetzen lässt. Die Vagrant-Datei in Listing 5 nutzt das von »vagrantbox.es« heruntergeladene Ubuntu-12.04-Image und leitet den von Jenkins genutzten Port 8080 aus der VM an den Host weiter, damit der Anwender von dort aus den Jenkins-Server ansteuern kann.

Listing 5

Vagrant-File

1 Vagrant::Config.run do |config|
2   config.vm.box = "ubuntu-1204"
3   config.vm.forward_port 8080, 8080
4 end

Der nach den offiziellen Anweisungen [5] installierte CI-Server ist allerdings kurioserweise im letzten Jahrhundert steckengeblieben und kann von Haus aus nur mit altertümlichen CVS- und SVN-Repositories umgehen. Nach einiger Wartezeit zeigt der Server jedoch im Menü »Manage Jenkins | Manage Plugins | Available« dem Anwender ein »Git Plugin« (Abbildung 5) an, das er dann per Mausklick im Browser herunterladen und installieren kann (Abbildung 6).

Abbildung 5: Das Git-Plugin lässt sich über das »Manage Jenkins«-Menü herunterladen und installieren.

Abbildung 5: Das Git-Plugin lässt sich über das »Manage Jenkins«-Menü herunterladen und installieren.

Abbildung 6: Die komplette Installation des Git-Plugins für Jenkins dauert nicht länger als eine Minute und läuft zudem direkt im Webbrowser ab.

Abbildung 6: Die komplette Installation des Git-Plugins für Jenkins dauert nicht länger als eine Minute und läuft zudem direkt im Webbrowser ab.

Die Ubuntu-VM kommt ohne das Kommandozeilentool »git« daher, sodass der Leser auch noch mit »vagrant ssh« in die VM einsteigen muss, um das Tool anschließend mit »sudo apt-get install git« zu installieren. Die Anwendungen des CI-Servers beschränken sich nicht nur auf den Build und das Ausführen der Testsuite, sondern es lassen sich auch weitere Buildsteps definieren, die den eingecheckten Code zur Freude des Entwicklers ohne weitere menschliche Eingriffe oder Verzögerungen in die Produktion schubsen.

Online PLUS

In einem Screencast demonstriert Michael Schilli das Beispiel: https://www.linux-magazin.de/2014/10/plus

Der Autor

Michael Schilli arbeitet als Software-Engineer bei Yahoo in Sunnyvale, Kalifornien. In seiner seit 1997 erscheinenden Kolumne forscht er jeden Monat nach praktischen Anwendungen der Skriptsprache Perl. Unter mailto:mschilli@perlmeister.com beantwortet er gerne Fragen.

DIESEN ARTIKEL ALS PDF KAUFEN
EXPRESS-KAUF ALS PDFUmfang: 4 HeftseitenPreis €0,99
(inkl. 19% MwSt.)
LINUX-MAGAZIN KAUFEN
EINZELNE AUSGABE Print-Ausgaben Digitale Ausgaben
ABONNEMENTS Print-Abos Digitales Abo
TABLET & SMARTPHONE APPS Readly Logo
E-Mail Benachrichtigung
Benachrichtige mich zu:
0 Kommentare
Älteste
Neuste Beste Bewertung
Nach oben