Ecrire un plugin Jenkins

1. Vue d'ensemble

Jenkins est un serveur d'intégration continue open source, qui permet de créer une création de plugin personnalisée pour une tâche / un environnement particulier.

Dans cet article, nous allons passer en revue tout le processus de création d'une extension qui ajoute des statistiques à la sortie de construction, à savoir le nombre de classes et de lignes de code.

2. Configuration

La première chose à faire est de mettre en place le projet. Heureusement, Jenkins fournit des archétypes Maven pratiques pour cela.

Exécutez simplement la commande ci-dessous à partir d'un shell:

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

Nous obtiendrons la sortie suivante:

[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.)

Maintenant, choisissez la première option et définissez groupe / artefact / package en mode interactif. Après cela, il est nécessaire d'apporter des améliorations au pom.xml - car il contient des entrées telles que TODO Plugin .

3. Conception du plugin Jenkins

3.1. Points d'extension

Jenkins fournit un certain nombre de points d'extension. Ce sont des interfaces ou des classes abstraites qui définissent des contrats pour des cas d'utilisation particuliers et permettent à d'autres plugins de les implémenter.

Par exemple, chaque build se compose d'un certain nombre d'étapes, par exemple «Checkout from VCS» , «Compile» , «Test», «Assemble», etc. Jenkins définit le point d'extension hudson.tasks.BuildStep , afin que nous puissions l'implémenter pour fournir une étape personnalisée qui peut être configurée.

Un autre exemple est hudson.tasks.BuildWrapper - cela nous permet de définir des actions pré / post.

Nous avons également un plugin d'extension de courrier électronique non essentiel qui définit le point d'extension hudson.plugins.emailext.plugins.RecipientProvider , qui permet de fournir des destinataires de courrier électronique. Un exemple d'implémentation est disponible ici: hudson.plugins.emailext.plugins.recipients.UpstreamComitterRecipientProvider .

Remarque: il existe une approche héritée dans laquelle la classe du plugin doit étendre hudson.Plugin . Cependant, il est maintenant recommandé d'utiliser des points d'extension à la place.

3.2. Initialisation du plugin

Il est nécessaire d'informer Jenkins de notre extension et de la manière dont elle doit être instanciée.

Tout d'abord, nous définissons une classe interne statique dans le plugin et la marquons à l'aide de l' annotation 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"; } } }

Deuxièmement, nous devons définir un constructeur à utiliser pour l'instanciation d'objet du plugin et le marquer par l' annotation org.kohsuke.stapler.DataBoundConstructor .

Il est possible d'utiliser des paramètres pour cela. Ils sont affichés dans l'interface utilisateur et sont automatiquement livrés par Jenkins.

Par exemple, considérez le plugin Maven:

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

Il est mappé à l'interface utilisateur suivante:

Il est également possible d'utiliser l' annotation org.kohsuke.stapler.DataBoundSetter avec les setters.

4. Implémentation du plugin

Nous avons l'intention de collecter des statistiques de projet de base pendant une construction, donc, hudson.tasks.BuildWrapper est la bonne façon d'aller ici.

Implémentons-le:

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, maintenant nous devons implémenter la fonctionnalité réelle.

Définissons une classe de domaine pour les statistiques du projet:

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

Et écrivez le code qui construit les données:

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); }

Enfin, nous devons montrer les statistiques aux utilisateurs finaux. Créons un modèle HTML pour cela:

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

Et remplissez-le pendant la construction:

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. Utilisation

Il est temps de combiner tout ce que nous avons créé jusqu'à présent - et de le voir en action.

Il est supposé que Jenkins est opérationnel dans l'environnement local. Veuillez vous référer aux détails d'installation sinon.

5.1. Ajouter le plugin à Jenkins

Maintenant, construisons notre plugin:

mvn install

Cela créera un fichier * .hpi dans le répertoire cible . Nous devons le copier dans le répertoire des plugins Jenkins ( ~ / .jenkins / plugin par défaut):

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

Enfin, redémarrons le serveur et assurons-nous que le plugin est appliqué:

  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.

Naturellement, nous n'avons pas couvert tous les aspects du développement des extensions CI, nous avons juste fourni un aperçu de base, des idées de conception et une configuration initiale.

Et, comme toujours, le code source peut être trouvé sur GitHub.