Jenkins e i plugins personalizzati

208b - installazione plugin

Jenkins e i plugins personalizzati

Il mio primo plugin: documentazione ufficiale

Dopo aver familiarizzato con il server Jenkins, vediamo, ora, come creare un semplice plugin per il nostro automation server preferito, che stampa un messaggio di “Hello World”.
Questo paragrafo con tutti i passi per eseguire il “primo plugin”,  è una traduzione della documentazione ufficiale che si puo’ consultare all’indirizzo: https://wiki.jenkins-ci.org/display/JENKINS/Plugin+tutorial.

Configurazione di Maven

Il primo passo consiste nell’abilitare il nostro Maven all’accesso al repository ufficiale di Jenkins, per farlo dobbiamo modificare il file ~/.m2/settings.xml  o in Windows %USERPROFILE%\.m2\settings.xml, aggiungendo le righe che servono, come nel listato sottostante.


<settings>
  <pluginGroups>
    <pluginGroup>org.jenkins-ci.tools</pluginGroup>
  </pluginGroups>

  <profiles>
    <!-- Give access to Jenkins plugins -->
    <profile>
      <id>jenkins</id>
      <activation>
        <activeByDefault>true</activeByDefault> <!-- change this to false, if you don't like to have it on per default -->
      </activation>
      <repositories>
        <repository>
          <id>repo.jenkins-ci.org</id>
          <url>http://repo.jenkins-ci.org/public/</url>
        </repository>
      </repositories>
      <pluginRepositories>
        <pluginRepository>
          <id>repo.jenkins-ci.org</id>
          <url>http://repo.jenkins-ci.org/public/</url>
        </pluginRepository>
      </pluginRepositories>
    </profile>
  </profiles>
  <mirrors>
    <mirror>
      <id>repo.jenkins-ci.org</id>
      <url>http://repo.jenkins-ci.org/public/</url>
      <mirrorOf>m.g.o-public</mirrorOf>
    </mirror>
  </mirrors>
</settings>

Creazione del progetto

A questo punto è possibile creare un nuovo progetto usando come Archetype: org.jenkins-ci.tools:maven-hpi-plugin.

207 - maven-new-project207b -maven-new-project2

Da console:

# mvn -U org.jenkins-ci.tools:maven-hpi-plugin:create

Il progetto così creato è in realtà già un piccolo plugin compilabile, installabile ed eseguibile senza alcuna modifica.

Vediamo assieme alcuni dei file creati dalla procedura:

208 - firstplugin1

  • file pom.xml: questo file contiene la configurazione di Maven, per far funzionare il nostro plugin dobbiamo modificarlo inserendo la corretta versione del nostro Jenkins dentro il campo /project/parent/version, (nel mio caso ho inserito 1.644)
  • risorse statiche: dentro la directory delle risorse maven, viene creato un sotto albero di directory corrispondente al nome del package e infine un ulteriore livello con il nome del file che  contiene il sorgente principale (HelloWorldBuilder in questo caso). All’interno di questa cartella troviamo le risorse visuali “collegate” al plugin stesso:
    • config.jelly: configurazione “per job” del plugin, i widget di questa form saranno visibili all’interno della configurazione di ogni singolo job che utilizzi il plugin,
    • global.jelly: configurazione “globale” del plugin, i widget di questa form saranno visibili all’interno della schermata “configura sistema”,
    • help-name.html : file con il messaggio di help per il parametro name,
    • help-useFrench.html : file con il messaggio di help per il parametro use-French,
    • index.jelly : semplice descrizione del plugin che viene visualizzata nella tabella dei plugin.
  • sorgenti java:
    • HelloWorldBuilder.java : in meno di 150 righe di codice si può vedere come accedere ai valori di configurazione globali (del sistema) e puntuali (del singolo job), come validare l’input delle form e come stampare del log.

Agli sviluppatori di Jenkins piace l’ordine quindi consiglio di prestare molta attenzione ai nomi dei sorgenti, dei packages e anche ai nomi dei parametri! Ad esempio i file di help sono automaticamente collegati al pulsante di aiuto dell’interfaccia, ma per associare il file al parametro si usa proprio il nome del parametro e il nome del file.

Bug jelly: al momento della stesura di questo articolo (versione Jenkins 1.644), esiste un piccolo bug che impedisce la corretta esecuzione dei test unitari associati alla compilazione del progetto. Questo bug (https://issues.jenkins-ci.org/browse/JENKINS-5135) richiede che in tutti i file .jelly venga aggiunta la seguente stringa:

<jelly escape-by-default='true'>

Installazione

La compilazione avviene tramite il classico goal Maven: mvn package.

Al termine della compilazione il risultato sarà un file con estensione hpi nella cartella target.

Il modo più semplice per installare il plugin è di accedere alla apposita form  dal menu:
Jenkins -> Configura Jenkins -> Gestisci Plugin -> [Tab] Avanzate.

208b - installazione plugin

In caso di re-installazione sarà necessario riavviare Jenkins.

Nota: c’è una checkbox in pedice alla pagina di “installazione dei plugins in corso” con una label poco indicativa, ma selezionando quella checkbox, il server Jenkins viene riavviato al termine dell’installazione.

208c - lista plugins

Hello World dentro un job

Dopo aver installato correttamente il nostro primo plugin:

  • sarà presente nella pagina di “configura sistema” la checkbox globale introdotta dal plugin (come da sorgente: global.jelly ),
  • in ogni nuovo job che creeremo sarà possibile aggiungere lo step “Say hello world” e configurarlo opportunamente.

Le opzioni di configurazione per singolo job rispecchiano la form del file sorgente config.jelly.

208d -firstplugin2Il primo consiglio, per chiunque scelga di continuare lo studio della materia è di leggere i sorgenti, prima del plugin demo e poi di altri plugin pubblici (magari quelli più simili all’obiettivo che si vuole raggiungere con il proprio).

Attenzione: Durante la stesura di questo articolo ho provato gli steps precedenti su diversi pc, con differenti configurazioni. Non sempre il risultato è stato raggiunto al primo tentativo e soprattutto il debugging non si è rivelato facile. La maggioranza dei problemi sono riconducibili alla (poca) integrazione di Maven con Eclipse che in più riprese mi ha: impedito la creazione del progetto [missing archetype] oppure mi ha anche fatto fare confusione tra le dipendenze (quando avevo più progetti aperti). Consiglio pertanto di utilizzare Maven da console e di fare massima attenzione ad ogni passaggio.

Secondo Plugin: creare parametri “multipli” per un job

Ora vedremo come implementare un tipo particolare di plugin che ci permette di configurare i parametri che l’utente deve fornire al momento dell’esecuzione del job.

209 - filesystem second plugin

Esistono già svariati tipi di parametri configurabili con una installazione vanilla di Jankins: formato stringa, formato password, formato booleano,…

Altri tipi possono essere aggiunti tramite plugin: File system parameter, Extended choice parameter,….

Generalmente, i plugin di  questo tipo leggono un input dalla UI di Jenkins e ne caricano il valore in una variabile d’ambiente, direttamente accessibile durante le fasi di build vere e proprie.

Il nostro esempio ci porta a soddisfare la seguente necessità: permettere la selezione multipla di file da una cartella del filesystem.

Per diversi motivi pratici si è scelto di codificare la scelta multipla nel seguente modo:

  • ${NOME_PARAMETRO}_0=primo file selezionato.txt,
  • ${NOME_PARAMETRO}_1=secondo file selezionato.zip,
  • ${NOME_PARAMETRO}_2=terzo file selezionato.xml,
  • ${NOME_PARAMETRO}_SIZE=3,

dove ${NOME_PARAMETRO} è il nome che si è scelto in fase di configurazione per il parametro.

Qualora la selezione sia vuota il valore di ${NOME_PARAMETRO}_SIZE sarà 0.

 

 

 

Definizione delle opzioni di configurazione

 

211 - jelly

Form config.jelly con la configurazione necessaria al plugin per essere eseguito.

Per questo plugin ho voluto inserire diversi tipi di parametri di configurazione: stringhe in textbox (name,paths,regexp), testo in textarea (description), interi in textbox (size) e infine un booleano  in checkbox (completePath). Il risultato potete ammirarlo nello screenshot sottostante.

210 - config parameter plugin

Come potete vedere  i messaggi di help “espansi” (nei paragrafi con sfondo grigio) riportano l’html collegato al file con nome: help-${nomeParametro}.html

Vediamo ora il codice necessario per la UI di esecuzione del job:

<?jelly escape-by-default='true'?>
<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define"
  xmlns:l="/lib/layout" xmlns:t="/lib/hudson" xmlns:f="/lib/form"
  xmlns:i="jelly:fmt" xmlns:p="/lib/hudson/project">
  <f:entry title="${it.name}" description="${it.description}" >
  	<div name="parameter" description="${it.description}" >
      <input type="hidden" name="name" value="${it.name}"/>
      <select multiple="multiple" name="value"  size="${it.selectSize}" >
        <j:forEach var="obj" items="${it.filelist}">
			<f:option value="${obj}">${obj}</f:option>
        </j:forEach>
      </select>
     </div>
  </f:entry>
</j:jelly>

Come avrete intuito il formato Jelly è simile a JSP+JSTL, e oltre ad accedere ai campi con il nome dei parametri di configurazione (name,selectSize) accede ad un metodo getFilelist() che costruisce la lista degli elementi per la selection.

     /*
     * Creates list to display in config.jelly
     */
	public List<String> getFilelist() throws IOException {
		List<String> res = new ArrayList<String>();

		String[] pathTokens = paths.split(",");
		for (String p : pathTokens) {
			File d = new File(p);
			File[] foundFiles = d.listFiles(filter);
			for (File file : foundFiles) {
				if (this.completePath)
					res.add(file.getAbsolutePath());
				else
					res.add(file.getName());
			}
		}

		if (res.size() < 1) {
			res.add(NO_FILE_FOUND);
		}
		return res;
	}

212 - execution

Ecco come si presenta l’interfaccia all’utente che si appresta a lanciare il nostro job.

La form di input, caricata grazie al metodo precedente viene puoi processata (quando il job viene avviato) caricando i vari valori selezionati dall’utente (i path dei file) in un ArrayList. Ho chiamato questo tipo di parametro ArrayParameterValue e quando Jenkins deve processarlo per costruire l’enviroment, procede semplicemente ciclando l’array e aggiungendo i nostri valori all’ oggetto env.

    @Override
    public void buildEnvironment(Run<?,?> build, EnvVars env) {
    	
    	for (int i =0; i<values.size();i++) {
    		env.put(name+SEPARATOR+i+"",values.get(i));
    		LOGGER.finer("Added parameter to env: "+name+SEPARATOR+i+" = "+values.get(i));
		}

    	env.put(name+SEPARATOR+SIZE,""+values.size());
        LOGGER.finer("Added parameter to env: "+name+SEPARATOR+SIZE+" = "+values.size());

    }

Per stampare in console i parametri dell’environment di esecuzione di Jenkins, basta aggiungere uno step di “Esecuzione shell” al nostro job:

#/bin/bash!

NAME="TEST"   << put here the name you have given to the parameter

NN="${NAME}_SIZE"

for((i=0;i<$NN;i+=1))
    do
      VAL="${NAME}_${i}"
      echo " SELECTED FILE ====>>" `eval echo \$\{$VAL\}`
    done
E finalmente vediamo stampati i risultati della nostra esecuzione.

E finalmente vediamo stampati i risultati della nostra esecuzione.

Il codice sorgente del multiple-file-selection plugin è pubblico su GitHub.

 

DevOps per tutte le tasche

Curare le operations è una parte integrante del processo di sviluppo e strumenti come Jenkins possono aiutarci, in modo da non dover temere l’esecuzione di alcuno script! E, se possibile, a ridurre al minimo il numero di operazioni “meccaniche” da dover eseguire manualmente.

Qualora poi le necessità crescessero, i tools più avanzati non mancano:

Netflix, nel 2015, ha dichiarato di deployare in produzione “centinaia di volte al giorno”. Anche se le nostre necessità sono inferiori possiamo, con Jenkins e i suoi plugins,  garantire ai nostri progetti delle operations comode ed intuititve da utilizzare.

Risorse online

Davide Zambon

Mi sono laureato in Scienze Informatiche e da allora la programmazione è, per me, la ricerca del modo più elegante per esprimere un algoritmo. Lavoro come Team Leader e architetto in ambiente Java ma ho avuto esperienze in ambiti molto diversi della programmazione. Le mie strutture dati preferite sono gli alberi: red/black, B, B+, binary, di ricerca, decision tree.... Ho come passione il volo libero in parapendio.