Ecrire un plugin Jenkins

Écrire un plugin Jenkins

1. Vue d'ensemble

Jenkins est un serveur à intégration continue à source ouverte, qui permet de créer un plug-in personnalisé 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. Installer

La première chose à faire est de mettre en place le projet. Luckily, Jenkins provides convenient Maven archetypes pour cela.

Il suffit de lancer la commande ci-dessous à partir d'un shell:

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

Nous obtiendrons le résultat suivant:

[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’affiner lespom.xml - car il contient des entrées telles que<name>TODO Plugin</name>.

3. Conception du plugin Jenkins

3.1. Points d'extension

Jenkins provides a number of extension points. 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 génération consiste en plusieurs étapes, par exemple: “Checkout from VCS”,“Compile”,“Test”,“Assemble”, etc. Jenkins définit le point d'extension dehudson.tasks.BuildStep, afin que nous puissions l'implémenter pour fournir une étape personnalisée qui peut être configurée.

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

Nous avons également un plugin non-coreEmail Extension qui définit le point d'extensionhudson.plugins.emailext.plugins.RecipientProvider, qui permet de fournir des destinataires d'e-mails. Un exemple d'implémentation est disponible ici:hudson.plugins.emailext.plugins.recipients.UpstreamComitterRecipientProvider.

Remarque: il existe une approche héritée où la classe du plugin doit étendrehudson.Plugin. Cependant,it’s now recommended to use extension points instead.

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'annotationhudson.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'annotationorg.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.

E.g. considérez lesMaven plugin:

@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:

image

Il est également possible d’utiliser l’annotationorg.kohsuke.stapler.DataBoundSetter avec les setters.

4. Implémentation du plugin

Nous avons l'intention de collecter les statistiques de base du projet pendant une construction, donc,hudson.tasks.BuildWrapper est la bonne voie à suivre ici.

Mettons-le en œuvre:

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 le peupler lors de 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. Usage

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

On suppose que Jenkins est opérationnel dans l’environnement local. Veuillez vous référer auxinstallation details sinon.

5.1. Ajouter le plugin à Jenkins

Maintenant, construisons notre plugin:

mvn install

Cela créera un fichier*.hpi dans le répertoiretarget. 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 assurez-vous que le plug-in est appliqué:

  1. Ouvrir le tableau de bord CI àhttp://localhost:8080

  2. Accédez àManage Jenkins | Manage Plugins | Installed

  3. Trouvez notre plugin

image

5.2. Configurer le travail Jenkins

Créons un nouveau travail pour un projet Apache commons-lang open-source et configurons le chemin vers son dépôt Git ici:

image

Nous devons également activer notre plugin pour cela:

image

5.3. Vérifiez les résultats

Nous sommes tous prêts, voyons comment cela fonctionne.

Nous pouvons construire le projet et naviguer vers les résultats. Nous pouvons voir qu'un fichierstats.html est disponible ici:

image

Ouvrons-le:

image

C’est ce à quoi nous nous attendions - une seule classe qui a trois lignes de code.

6. Conclusion

Dans ce tutoriel, nous avons créé un pluginJenkins à partir de zéro et nous nous sommes assurés qu'il fonctionne.

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

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