Ontwikkeling van een plug-in voor Jenkins

Jenkins (een fork van het project Hudson) is een Continuous Integration platform. Het platform is met name bedoeld voor het herhaaldelijk uitvoeren en monitoren van build-taken; het geautomatiseerd bouwen en testen van applicaties. De vele vrij beschikbare plug-ins maken het erg eenvoudig om de functionaliteit van Jenkins verder uit te breiden. Een voorbeeld hiervan is de integratie met andere systemen (zoals Sonar, Jira of CloudBees) of het wijzigen van de look & feel. Het is mogelijk om een complete Continuous Delivery pipeline te bouwen door de juiste plug-ins te gebruiken.

Dit artikel legt uit hoe een plug-in voor Jenkins in elkaar steekt en wat ervoor nodig is om zelf een plug-in te schrijven. De voornaamste aspecten worden behandeld, zoals:

  • configuratie van de plug-in;
  • koppeling met de applicatie build (op welk moment tijdens de build wordt de plug-in uitgevoerd);
  • opslaan van resultaten;
  • integratie met de Jenkins UI voor configuratie en tonen van de resultaten.

 

Als voorbeeld voor dit artikel wordt uitgegaan van een performance plug-in, die testresultaten verzameld en bij verslechtering van de performance de build zal laten falen.

 

Opbouw Jenkins Plug-in

Voordat we inhoudelijk ingaan op de opbouw van onze plug-in, allereerst iets meer informatie over de werking van Jenkins en de algemene opbouw van plug-ins.

 

Binnen Jenkins kunnen één of meerdere projecten (zogenaamde build-jobs) worden gedefinieerd. Een project kun je zien als de specifieke configuratie over hoe een build moet worden uitgevoerd. Tevens biedt het de context waarbinnen een plug-in wordt aangeroepen. Zo worden configuratie, meta-data en de historie van uitgevoerde build-taken specifiek per project opgeslagen. Een ‘build’ is een stappenplan dat door Jenkins wordt uitgevoerd, inclusief de bijbehorende voorbereiding en afronding.

 

Een individuele build bestaat uit meerdere stappen die in de project-configuratie deels zijn terug te herkennen. Zo bevat een build naast directe configuratie ook nog opties voor pre-build steps, post-build steps en post-build actions. Plug-ins kunnen inhaken op deze fases om op die manier de build te beïnvloeden en/of mede de uitkomst te bepalen. Naast deze ‘build steps’ bevat Jenkins nog tientallen andere zogenaamde ‘extension points’, waarmee verschillende facetten kunnen worden aangepast. Zo bestaan onder andere extension points voor versie-beheer systemen, autorisatie strategieën, build triggers en views.

 

Voor de ontwikkeling van een plug-in is het voldoende om een specifieke interface of abstract class te implementeren en/of uit te breiden en een annotatie toe te voegen aan de class om door Jenkins te worden herkend.

 

De in dit artikel beschreven plug-in moet na de daadwerkelijke build zelf worden uitgevoerd, als post-build action. Het idee is dat tijdens de build, load op onze applicatie wordt gezet –bijvoorbeeld door het uitvoeren van JMeter scripts– en data over de response van de applicatie onder die load wordt geproduceerd. De te ontwikkelen plug-in verzamelt al deze data,  bewaart deze als onderdeel van de build-historie en bepaalt of de performance verslechterd is. In dit voorbeeld biedt de performance tool een RESTful interface om de performance data aan het eind van de build eenvoudig te kunnen uitlezen.

 

Om te bepalen of de performance verslechterd is, moeten builds ten opzichte van elkaar worden vergeleken. Het is binnen Jenkins mogelijk om zelf bestanden in de workspace op te slaan die bij een specifieke build horen. Het voordeel is dat (zolang de build binnen Jenkins bewaard blijft) bron-bestanden altijd opnieuw kunnen worden uitgelezen.

Daarnaast worden classes die onder andere de Action interface implementeren automatisch door Jenkins geserializeerd via XStream en opgeslagen op disk. Later zal de exacte werking duidelijk worden, voor nu is het voldoende om te weten dat we via deze Action objecten kunnen refereren aan voorgaande builds en daarmee bijbehorende resultaten eenvoudig terug kunnen vinden.

 

Action objecten zijn ook nog op een andere manier van belang, omdat ze als view binnen Jenkins benaderbaar zijn. Vanuit een Action object kunnen bijvoorbeeld grafieken worden gegenereerd en is interactie mogelijk (zoals configuratie) vanuit de Jenkins UI. Beide toepassingen zullen we verderop in dit artikel realiseren.

Jenkins gebruikt Jelly als view technologie (vergelijkbaar met JSP+JSTL) om HTML pagina’s te renderen voor specifieke Action classes. Een view roept simpelweg bepaalde methodes aan op de onderliggende classes. De hiërarchie van objecten en methodes bepaalt daarmee meteen ook de URL hiërarchie. Bijvoorbeeld voor een methode Hudson.getJob(String) mapped de URL /job/foobar op Hudson.getJob(“foobar”). (het Hudson object zelf mapped op de URL “/”). Voor volledige details verwijs ik naar het Stapler project (http://stapler.kohsuke.org); deze library verzorgt de koppeling tussen Java objecten en URLs.

 

Project set-up

Jenkins plug-ins zijn op zichzelf staande Java projecten welke met behulp van Maven kunnen worden gebouwd en getest. Maven 3 en Java JDK 6 zijn daarvoor de minimale vereisten. Daarnaast is het aan te raden om enkele repository-definities in je Maven settings.xml op te nemen. Dit voorkomt dat je de volledige plug-in naam inclusief group id op moet geven bij het Maven “mvn” commando. Zie Afbeelding 1 voor de benodigde configuratie.

afbeeldig 1

 

De volgende stap is het opzetten van een nieuw Maven project voor de plug-in. Gebruik hiervoor het volgende Maven commando:

 


$ mvn -cpu hpi:create

 

Na het succesvol uitvoeren van dit commando zou je al een werkende plug-in moeten hebben. Testen van je plug-in kan met het volgende commando:

 


$ mvn clean hpi:run

 

Hiermee wordt een embedded Jenkins-instantie opgestart en je plug-in daarin geïnstalleerd. Daarna is het mogelijk een nieuw project (build “job”) aan te maken en je plug-in op te nemen in het build-proces om hem op die manier te testen. Het is aan te raden om iedere keer dat je Jenkins embedded via Maven opstart, tussentijds de work/plugins directory op te schonen. Doe je dit niet, dan wordt mogelijk nog de vorige versie van je plug-in door Jenkins geladen.

 

Als ontwikkelomgeving worden zowel Eclipse als IntelliJ IDEA goed ondersteund. Aan te raden is om een Stapler plug-in te installeren voor je ontwikkelomgeving.

 

Benodigde classes

Naast dat we onze Jenkins plug-in als post-build action willen laten uitvoeren, willen we ook op project-niveau, overkoepelend over de individuele builds, informatie tonen over de performance van onze applicatie. Om precies te zijn, willen we een grafiek tonen binnen de Jenkins UI op zowel de pagina van het Jenkins project alsook op de pagina waar informatie is te vinden van een specifieke build. Afbeelding 2 toont een voorbeeld van de uiteindelijke grafiek op de project-pagina.

afbeelding 2

 

Uiteindelijk hebben we de volgende classes nodig;

 

PerformanceResultsPublisher – extends Recorder
De PerformanceResultsPublisher vormt het hart van onze plug-in. De Recorder abstract class is een specifieke ‘BuildStep’ die uitgevoerd wordt, nadat de build zelf succesvol is afgerond. Dit stelt je in staat om performance statistieken te verzamelen en alsnog de build als unstable of zelfs failed aan te merken, wanneer de performance is verslechterd.
 

PerformanceBuildAction – implements Action, StaplerProxy
Zoals eerder vermeld, zijn Actions van buitenaf via een webbrowser benaderbaar. Implementatie van de StaplerProxy maakt het mogelijk om de URL mapping door een ander ModelObject af te laten handelen, in dit geval de BuildActionResultsDisplay class.
 

BuildActionResultsDisplay – implements ModelObject
Een ModelObject is een object waar vanuit een URL naar kan worden verwezen. Via de Jelly pagina’s kan informatie worden getoond. In ons voorbeeld vormt deze class de koppeling tussen het huidige build resultaat en voorgaande resultaten.
 

PerformanceProjectAction – implements Action
Dit is een andere Action implementatie, maar dit maal op project niveau in plaats van individueel build niveau. De data die deze class aanlevert, wordt als grafiek in de Jenkins UI getoond wanneer de gebruiker de project status bekijkt.

 

In de volgende secties worden deze classes één voor één in meer detail behandeld.

Maar naast deze classes zijn ook overeenkomstige Jelly bestanden nodig om alle informatie (zoals data en grafieken) binnen Jenkins te tonen. Verschillende type Jelly bestanden zijn mogelijk en om alles correct te laten werken, moeten de Jelly bestanden volgens de juiste folder structuur worden opgeslagen. De package / folder van het Jelly bestand moet gelijk zijn aan de package naam van de Java class welke de data aanlevert, inclusief de naam van de class zelf. Bijvoorbeeld, om de uitkomst van een specifieke build te tonen, heb je het volgende Jelly bestand nodig:

 


/org/jenkins/perf/BuildActionResultsDisplay/index.jelly

 

Dit bestand mapped op de BuildActionResultsDisplay class (denk eraan, deze wordt geproxied door de PerformanceBuildAction doordat we StaplerProxy implementeren).

 

Andere type Jelly bestanden die we zullen gebruiken zijn;

 

  • config.jelly – voor configuratie van onze plug-in via de Jenkins UI. Dit Jelly bestand mapped op de PerformanceResultsPublisher (Recorder) class.
  • floatingBox.jelly – voor het weergeven van een ‘floating’ grafiek op de build- of de project-pagina. Deze grafiek zal de response tijden tonen van de applicatie, gedurende de performance test.
  • summary.jelly – kan een samenvatting (tekst) weergeven op de build- of de project-pagina.

 

Zoals je ziet, bepaalt de naam van het Jelly bestand de functie en/of waar het zal worden getoond binnen de Jenkins UI. Nu is het tijd om de classes in detail te bespreken…

 

PerformanceResultsPublisher

De PerformanceResultsPublisher (annex Recorder) vormt de basis van onze plug-in. Het in dit geval extenden van de Recorder abstract class, bepaalt automatisch voor Jenkins dat deze plug-in als post-build actie zal worden uitgevoerd.

 

Het is noodzakelijk dat vanuit deze plug-in class een getDescriptor() methode wordt geïmplementeerd welke een object retourneert dat extend van BuildStepDescriptor. Dit descriptor object dient om bepaalde informatie aan Jenkins aan te bieden, zoals beschikbare configuratie parameters en defaults voor de configuratievelden. Vanuit dit object is het zelfs mogelijk om configuratievelden te valideren op correctheid. Je kunt deze BuildStepDescriptor eenvoudig als interne class opnemen, Afbeelding 3 toont een snippet hiervan.

afbeelding 3

 

De door de gebruiker ingestelde configuratieparameters voor toepassing van de plugin binnen een bepaald project worden overigens automatisch door Jenkins opgeslagen, in een op zichzelf staand XML bestand.

 

De methoden die een FormValidation object retourneren, bieden een nette wijze om input te valideren en een melding daarvan te tonen aan de gebruiker. In het bovenstaande voorbeeld controleren we de verbinding naar de RESTful interface van onze performance tool, ter validatie dat de opgegeven configuratie geldig is. Dit is op de volgende wijze gekoppeld met het Jelly bestand (snippet van config.jelly) in Afbeelding 4:

afbeelding 4

 

Afbeelding 5 toont een screenshot van de Jenkins UI met de configuratie parameters, zoals deze vervolgens beschikbaar zijn voor de gebruiker.

afbeelding 5

 

De validateButton –tag in Afbeelding 4 gezien, genereert een knop welke de doTestPerformanceConnection methode van de PerformanceResultsPublisher class aanroept. Voor validatie van de afzonderlijke input-velden is het voldoende om een doCheckxxx methode op te nemen, waarbij “xxx” mapped op de juiste parameter naam. Door ten slotte een @QueryParam annotatie op te nemen bij de methode, zal de validatie automatisch worden aangeroepen bij invullen van het veld.

 

De gehele BuildStepDescriptior wordt op de volgende wijze aan de ResultsPublisher gebonden:

 

afbeelding 6

 

Let daarbij ook op de @Extension annotatie, die voor Jenkins nodig is om onze plug-in überhaupt te detecteren.

 

De actie die de plugin daadwerkelijk uitvoert tijdens iedere build wordt geïmplementeerd in de perform() methode die door Jenkins wordt aangeroepen iedere keer als een build wordt uitgevoerd. Vanuit deze methode wordt de werkelijke logica van de plug-in aangeroepen, zoals een REST client waarmee we de metrieken ophalen uit de performance tool. Ook moet een BuildAction object worden geïnstantieerd, zoals te zien in Afbeelding 7 om de uitkomsten van de acties van de plugin terug te melden aan Jenkins. Alle meldingen die in deze code naar de logger worden weggeschreven, zullen uiteindelijk terug te vinden zijn in de output van de build console.

 

afbeelding 7

 

PerformanceBuildAction

Het voornaamste aspect aan de PerformanceBuildAction class is dat deze een weak reference aanbiedt naar de BuildActionResultsDisplay class. De getTarget() methode bevat de implementatie voor de StaplerProxy. Het BuildActionResultsDisplay object dat geretourneerd wordt, biedt alle benodigde data aan behorende bij één individuele build. In dit geval komt de data oorspronkelijk uit onze performance tool. Vanuit deze data wordt bijvoorbeeld ook de grafiek gegenereerd, zoals te zien is in Afbeelding 8 en bijbehorende Jelly code in Afbeelding 9. Het uiteindelijke resultaat, de grafiek die getoond wordt binnen de Jenkins UI, is te zien in Afbeelding 10.

 

afbeelding 8

afbeelding 9

afbeelding 10

PerformanceProjectAction

Tenslotte de PerformanceProjectAction class, die vergelijkbaar is met de voorgaande PerformanceBuildAction class. Wat hier interessant is, is hoe we de informatie van alle voorgaande builds kunnen verkrijgen om zo tot een trendgrafiek te komen op project niveau, over alle builds heen. Het volgende code voorbeeld laat zien hoe een AbstractProject object kan worden gebruikt om een lijst van alle beschikbare builds op te halen, om vervolgens van iedere afzonderlijke build de opgeslagen performance rapportage uit te lezen:

 

afbeelding 11

 

Afronding

Ik hoop dat dit artikel iets meer inzicht heeft gegeven over hoe een Jenkins plug-in in elkaar steekt. Alleen de noodzakelijke classes voor Jenkins zijn behandeld, de classes voor integratie met de performance tool zijn bewust achterwege gelaten om alles zo overzichtelijk mogelijk te houden.

 

Jenkins biedt echter nog veel meer mogelijkheden tot uitbreiding, teveel om in één artikel te behandelen. Ga daarom vooral ook zelf experimenteren.

 

Source-code van deze plug-in is terug te vinden op GitHub; https://github.com/codecentric/Jenkins-AppDynamics-Plugin

(of hier; https://github.com/jenkinsci/appdynamics-plugin)

 

Informatie over Jenkins-CI hier; https://jenkins-ci.org/