Statt den CI-Server Jenkins im Browser mittels Mausklicks und Texteingaben für Builds zu konfigurieren, legen faule Tipper die Daten im Source-Control-System ab und lassen ein Perlskript die Handlangerarbeit tun.
Continuous Integration (CI) und die damit einhergehende Produktivitätssteigerung kann man sich heute kaum noch wegdenken. Rein mit dem Code ins Source-Control-System, einen Pull-Request abgesetzt, schnell mal einen Mitstreiter drüberschauen lassen, und schon erfasst das Räderwerk der CI-Pipeline die Änderung, unterwirft sie der stetig wachsendenden Testsuite und schwupps ist alles auf einmal live.
CI-Server wie Jenkins oder Teamcity, die sich die neuesten Source-Versionen eines Projekts schnappen und einen Build mit Testsuite anwerfen, erfreuen sich wachsender Beliebtheit. Doch jedes Mal 2 Minuten beim Aufsetzen eines neuen Build-Projekts in der bräsigen Jenkins-Oberfläche zuzubringen zermürbt auch noch den motiviertesten Entwickler. Wie Abbildung 2 zeigt, gilt es, die notwendigen Kästchen zu finden und zu aktivieren und einige Textfelder auszufüllen. Und wenn jedes Perl-Projekt die gleiche Kommandosequenz zum Starten des Build-Prozesses 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?
Abbildung 1: Drei verschiedene Projekte laufen auf einer Jenkins-Installation, allesamt per Perlskript eingespielt. |
Eigentlich schwebt mir eher ein minimalistischer, konventionsgetriebener Ansatz vor, wie ihn etwa Travis-CI [3] seit langem praktiziert. Im vorliegenden Falls gibt eine kleine Datei im Sourcecode des Projekts an, von welchem Git-Repository Jenkins den Code abholen kann und unter welchem Namen das Projekt auf dem Jenkins-Server laufen soll. Ein heute vorgestelltes Perl-Skript baut dann die Jenkins-Konfiguration als XML-Dokument zusammen, und setzt sie als POST-Request über die Jenkins-API an den Server ab. Schon ist das Projekt dort eingebaut, ohne jegliche Tipparbeit.
Abbildung 2: Jenkins führt gerade Build und Test des auf Github abgelegten CPAN-Projekts Log4perl aus. |
Doch wie kommt man an das richtige Format der XML-Daten, ohne die Dokumentation
oder gar den Source-Code des Servers zu lesen? Ein einmal von Hand auf
der Jenkins-Oberfläche eingetipptes Projekt wie in Abbildung 2 gibt die
Geheimnisse ohne Fisematenten preis. Der Entwickler hat "Git" als Option für
das Source-Code-Repository aktiviert und ins dann erscheinende
Textfeld den URL zu Github eingetragen. Unter dem Namen "log4perl" ist
das Projekt auf dem Jenkins-Server aktiv und das Skript in Listing
2 kann die dazugehörigen XML-Daten einfach über die 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 ("log4perl") die Konfiguration erfragt.
Zunächst sollte ein Skript wie in Listing 1 allerdings erst einmal prüfen, ob der Jenkins-Server überhaupt unter dem voreingestellten Port auf Anfragen reagiert. Klappt dies, kann Listing 2 anfragen, wie denn die Konfiguration eines bestimmten Projektes aussieht.
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 }
Von der Methode project_config()
kommt im Erfolgsfall
ein XML-Salat zurück, der unter anderem die von Hand in die
Web-UI eingetragenen Werte der URL zum Git-Repository
des Projekts offenbart (Abbildung 3).
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";
Abbildung 3: Im XML-Salat der Jenkins-Konfiguration findet sich die auf der UI eingestellte URL zum Github-Repository des Projekts [% $git_url %]. |
Mittels dieser Vorlage kann nun ein Skript wie in Listing 3 ein Template
mit XML-Struktur erstellen und mittels Variablen (zum Beispiel
[% git_url %]
) aus dem Modul Template-Toolkit vom CPAN an vorgegebene
Projekte anpassen (Abbildung 4). Das Skript liest die Minimalkonfiguration
jedes Projekts aus den YAML-Daten in Listing 4, bestehend aus dem Kürzel
des Projekts (zum Beispiel "log4perl") und der URL zu den Sourcen
auf Github.
Abbildung 4: In dem aus dem XML generierten Template ersetzt der Template-Prozessor Variablen wie hier die URL zum Git-Repository des Projekts. |
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
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, überspielt 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 Build-Prozess einzurichten. Der Ablauf des Skripts
erzeugt also unter Umständen folgende Ausgabe:
$ ./jenkins-projects-sync 2014/08/05 21:20:18 Job for log4perl already exists. 2014/08/05 21:20:18 Updating job log4perl 2014/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 letzten zwei sich noch gar nicht auf dem Server befanden und neu eingerichtet werden mussten. Das Endresultat zeigt Abbildung 1 am Anfang des Artikels. 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, mittels eines Webhooks bei eintrudelnden Commits auf Github, oder durch einen manuellen Mausklick auf "Build Now" loslaufen lassen.
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 }
Die Installation des Jenkins-Servers auf meinem Desktop zuhause fand
in einer mittels Vagrant erstellten
VirtualBox-VM statt, um den Rechner sauber zu halten und zu
Testzwecken bequem über vorher eingefangene Snapshots auf einen leeren
Jenkins-Server zurücksetzen zu können. Die Vagrant-Datei in Listing 5
nutzt das von vagrantbox.es
heruntergeladene Ubunut-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.
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
([4]) installierte CI-Server ist allerdings kurioserweise im letzten
Jahrhundert steckengeblieben und kann von Haus aus nur mit CVS- und
SVN-Repositories umgehen. Nach einiger Wartezeit zeigt der Server
im Menü "Manage Jenkins"->"Manage Plugins"->"Available"
einen "Git Plugin" an, den der Anwender per Mausklick im Browser
herunterladen und installieren kann.
Abbildung 5: Der Git Plugin lässt sich über das "Manage Jenkins"-Menü herunterladen und installieren. |
Abbildung 6: Die Installation des Git-Plugins für Jenkins dauert nur eine Minute und läuft direkt im Web-Browser ab. |
Die Ubuntu-VM kommt allerdings ohne
das Kommandozeilentool git
daher, sodass der geneigte Leser auch noch
mit vagrant ssh
in die VM einsteigen muss, um das Tool 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 "Build Steps" definieren, die den
eingecheckten Code zur Freude des Entwicklers ohne weiteren menschliche
Eingriffe oder Verzögerungen in die Produktion schubsen.
Listings zu diesem Artikel: ftp://www.linux-magazin.de/pub/listings/magazin/2014/10/Perl
"The Docker Book: Containerization is the new virtualization", Kindle Edition, 2014, James Turnbull
"Erweiterte Testansicht", Michael Schilli, 2012, http://www.linux-magazin.de/Ausgaben/2012/06/Perl-Snapshot
Jenkins-Installation auf Ubuntu: https://wiki.jenkins-ci.org/display/JENKINS/Installing+Jenkins+on+Ubuntu