Scrivere un plugin Jenkins

1. Panoramica

Jenkins è un server di integrazione continua open source, che consente di creare una creazione di plug-in personalizzati per attività / ambienti particolari.

In questo articolo, esamineremo l'intero processo di creazione di un'estensione che aggiunge statistiche all'output di compilazione, vale a dire il numero di classi e righe di codice.

2. Configurazione

La prima cosa da fare è impostare il progetto. Fortunatamente, Jenkins fornisce utili archetipi Maven per questo.

Basta eseguire il comando seguente da una shell:

mvn archetype:generate -Dfilter=io.jenkins.archetypes:plugin

Otterremo il seguente output:

[INFO] Generating project in Interactive mode [INFO] No archetype defined. Using maven-archetype-quickstart (org.apache.maven.archetypes:maven-archetype-quickstart:1.0) Choose archetype: 1: remote -> io.jenkins.archetypes:empty-plugin (Skeleton of a Jenkins plugin with a POM and an empty source tree.) 2: remote -> io.jenkins.archetypes:global-configuration-plugin (Skeleton of a Jenkins plugin with a POM and an example piece of global configuration.) 3: remote -> io.jenkins.archetypes:hello-world-plugin (Skeleton of a Jenkins plugin with a POM and an example build step.)

Ora, scegli la prima opzione e definisci gruppo / artefatto / pacchetto in modalità interattiva. Dopodiché, è necessario apportare miglioramenti al pom.xml , poiché contiene voci come TODO Plugin .

3. Jenkins Plugin Design

3.1. Punti di estensione

Jenkins fornisce una serie di punti di estensione. Si tratta di interfacce o classi astratte che definiscono contratti per casi d'uso particolari e consentono ad altri plugin di implementarli.

Ad esempio, ogni build consiste in una serie di passaggi, ad esempio "Checkout da VCS" , "Compile" , "Test", "Assemblaggio", ecc. Jenkins definisce hudson.tasks.BuildStep punto di estensione, quindi possiamo implementarlo per fornire un passaggio personalizzato che può essere configurato.

Un altro esempio è hudson.tasks.BuildWrapper : questo ci consente di definire azioni pre / post.

Abbiamo anche un plug-in Estensione e-mail non core che definisce il punto di estensione hudson.plugins.emailext.plugins.RecipientProvider , che consente di fornire destinatari di posta elettronica. Un'implementazione di esempio è disponibile qui: hudson.plugins.emailext.plugins.recipients.UpstreamComitterRecipientProvider .

Nota: esiste un approccio legacy in cui la classe plug-in deve estendere hudson.Plugin . Tuttavia, ora si consiglia di utilizzare invece i punti di estensione.

3.2. Inizializzazione del plugin

È necessario dire a Jenkins della nostra estensione e di come dovrebbe essere istanziata.

Innanzitutto, definiamo una classe interna statica all'interno del plugin e la contrassegniamo utilizzando l' annotazione hudson.Extension :

class MyPlugin extends BuildWrapper { @Extension public static class DescriptorImpl extends BuildWrapperDescriptor { @Override public boolean isApplicable(AbstractProject item) { return true; } @Override public String getDisplayName() { return "name to show in UI"; } } }

In secondo luogo, dobbiamo definire un costruttore da utilizzare per l'istanza dell'oggetto del plugin e contrassegnarlo con l' annotazione org.kohsuke.stapler.DataBoundConstructor .

È possibile utilizzare parametri per questo. Vengono visualizzati nell'interfaccia utente e vengono forniti automaticamente da Jenkins.

Ad esempio, considera il plugin Maven:

@DataBoundConstructor public Maven( String targets, String name, String pom, String properties, String jvmOptions, boolean usePrivateRepository, SettingsProvider settings, GlobalSettingsProvider globalSettings, boolean injectBuildVariables) { ... }

È mappato alla seguente interfaccia utente:

È anche possibile utilizzare l' annotazione org.kohsuke.stapler.DataBoundSetter con i setter.

4. Implementazione del plugin

Intendiamo raccogliere le statistiche di base del progetto durante una build, quindi hudson.tasks.BuildWrapper è il modo giusto per andare qui.

Implementiamolo:

class ProjectStatsBuildWrapper extends BuildWrapper { @DataBoundConstructor public ProjectStatsBuildWrapper() {} @Override public Environment setUp( AbstractBuild build, Launcher launcher, BuildListener listener) {} @Extension public static class DescriptorImpl extends BuildWrapperDescriptor { @Override public boolean isApplicable(AbstractProject item) { return true; } @Nonnull @Override public String getDisplayName() { return "Construct project stats during build"; } } }

Ok, ora dobbiamo implementare la funzionalità effettiva.

Definiamo una classe di dominio per le statistiche del progetto:

class ProjectStats { private int classesNumber; private int linesNumber; // standard constructors/getters }

E scrivi il codice che costruisce i dati:

private ProjectStats buildStats(FilePath root) throws IOException, InterruptedException { int classesNumber = 0; int linesNumber = 0; Stack toProcess = new Stack(); toProcess.push(root); while (!toProcess.isEmpty()) { FilePath path = toProcess.pop(); if (path.isDirectory()) { toProcess.addAll(path.list()); } else if (path.getName().endsWith(".java")) { classesNumber++; linesNumber += countLines(path); } } return new ProjectStats(classesNumber, linesNumber); }

Infine, dobbiamo mostrare le statistiche agli utenti finali. Creiamo un modello HTML per questo:

    $PROJECT_NAME$   Project $PROJECT_NAME$: 
    
Classes number Lines number
$CLASSES_NUMBER$ $LINES_NUMBER$

E popolarlo durante la compilazione:

public class ProjectStatsBuildWrapper extends BuildWrapper { @Override public Environment setUp( AbstractBuild build, Launcher launcher, BuildListener listener) { return new Environment() { @Override public boolean tearDown( AbstractBuild build, BuildListener listener) throws IOException, InterruptedException { ProjectStats stats = buildStats(build.getWorkspace()); String report = generateReport( build.getProject().getDisplayName(), stats); File artifactsDir = build.getArtifactsDir(); String path = artifactsDir.getCanonicalPath() + REPORT_TEMPLATE_PATH; File reportFile = new File("path"); // write report's text to the report's file } }; } }

5. Utilizzo

È ora di combinare tutto ciò che abbiamo creato finora e di vederlo in azione.

Si presume che Jenkins sia attivo e funzionante nell'ambiente locale. Si prega di fare riferimento ai dettagli di installazione in caso contrario.

5.1. Aggiungi il plugin a Jenkins

Ora costruiamo il nostro plugin:

mvn install

Questo creerà un file * .hpi nella directory di destinazione . Dobbiamo copiarlo nella directory dei plugin di Jenkins ( ~ / .jenkins / plugin per impostazione predefinita):

cp ./target/jenkins-hello-world.hpi ~/.jenkins/plugins/

Infine, riavviamo il server e assicuriamoci che il plugin sia applicato:

  1. Open CI dashboard at //localhost:8080
  2. Navigate to Manage Jenkins | Manage Plugins | Installed
  3. Find our plugin

5.2. Configure Jenkins Job

Let's create a new job for an open-source Apache commons-lang project and configure the path to its Git repo there:

We also need to enable our plugin for that:

5.3. Check the Results

We're all set now, let's check how it works.

We can build the project and navigate to the results. We can see that a stats.html file is available here:

Let's open it:

That's what we expected – a single class which has three lines of code.

6. Conclusion

In this tutorial, we created a Jenkins plugin from scratch and ensured that it works.

Naturalmente, non abbiamo coperto tutti gli aspetti dello sviluppo delle estensioni CI, abbiamo solo fornito una panoramica di base, idee di design e una configurazione iniziale.

E, come sempre, il codice sorgente può essere trovato su GitHub.