How to use Jenkins plugins in Jenkins Pipeline

Pipeline and plugins

Jenkins Pipeline is a suite of plugins which supports implementing and integrating continuous delivery pipelines into Jenkins.

Not all Jenkins plugins are compatible with Jenkins Pipeline (check COMPATIBILITY.md).

With “old” Jenkins plugins installation and usage is straightforward. But it’s not true for Jenkins Pipeline. Even if you find a plugin which is compatible with Jenkins Pipeline, it’s not always obvious how to use it in Jenkinsfile.

How to use Jenkins plugins in Jenkinsfile

There are different ways to identify how to use a Jenkins plugin in Jenkinsfile.

Easy way #1

Easiest way is when plugin maintainer makes the Jenkins plugin compatible with Jenkins Pipeline and includes an example of usage directly on plugin wiki page.

Few examples of plugins which have Jenkins Pipeline support examples on wiki pages:

Easy way #2

We can use snippet generator from Jenkins.

Snippet generator

Hard way

It’s harder when you find that plugin is compatible with Jenkins Pipeline in COMPATIBILITY.md document, but there’re no examples of how to use it in Jenkins Pipeline.

To succeed with this task we need to have a background of what makes Jenkins plugin compatible with Jenkins Pipeline. Such information can be found in workflow-step-api-plugin repository README.md file.

The most important parts for us are:

Extend Step. Define mandatory parameters in a @DataBoundConstructor. Define optional parameters using @DataBoundSetter. (Both need matching getters.)

Extend StepDescriptor. Besides a display name, pick a function name which will be used from Groovy scripts.

Build steps and post-build actions in Jenkins core as of 2.2, and in some plugins according to their individual changelogs, have defined symbols which allow for a more concise syntax. Snippet Generator will offer this when available.

  node {
    sh 'make something'
    archiveArtifacts 'something'
  }

Also, we need to refer to Jenkins Pipeline plugin DEVGUIDE.md#build steps:

See the user documentation for background. The metastep is step. To add support for use of a Builder or Publisher from a pipeline, depend on Jenkins 1.577+, typically 1.580.1 (tips). Then implement SimpleBuildStep, following the guidelines in its Javadoc. Also, prefer @DataBoundSetters to a sprawling @DataBoundConstructor.

From the documentation is clear what we should look for in Jenkins plugin source code to find out how to execute it as a Jenkins Pipeline step.

  • STEP_CLASS_NAME (check example from below)
    • find a class in plugin repository which extends from SimpleBuildStep
  • STEP_PARAMETERS (check example from below)
    • check constructor marked with @DataBoundConstructor annotation for mandatory step parameters
    • check methods marked with @DataBoundSetter annotation
  • STEP_FUNCTION_NAME (check example from below)
    • check for @Symbol annotation on top of descriptor class

This should be enough to call plugin step from Jenkinsfile:

step([$class : 'STEP_CLASS_NAME', STEP_PARAMETERS])

or

STEP_FUNCTION_NAME STEP_PARAMETERS

Let’s take JaCoCo as an example (we made sure first that plugin is compatible with Jenkins Pipeline). We navigate to plugin source code and try to find required information.

  • Class which extends from SimpleBuildStep

    public class JacocoPublisher extends Recorder implements SimpleBuildStep {
    
  • Parameters

    • DataBoundConstructor without parameters which sets defaults

      @DataBoundConstructor
      public JacocoPublisher() {
          this.execPattern = "**/**.exec";
          this.classPattern = "**/classes";
          this.sourcePattern = "**/src/main/java";
          this.inclusionPattern = "";
          this.exclusionPattern = "";
          this.skipCopyOfSrcFiles = false;
          this.minimumInstructionCoverage = "0";
          this.minimumBranchCoverage = "0";
          this.minimumComplexityCoverage = "0";
          this.minimumLineCoverage = "0";
          this.minimumMethodCoverage = "0";
          this.minimumClassCoverage = "0";
          this.maximumInstructionCoverage = "0";
          this.maximumBranchCoverage = "0";
          this.maximumComplexityCoverage = "0";
          this.maximumLineCoverage = "0";
          this.maximumMethodCoverage = "0";
          this.maximumClassCoverage = "0";
          this.changeBuildStatus = false;
          this.deltaInstructionCoverage = "0";
          this.deltaBranchCoverage = "0";
          this.deltaComplexityCoverage = "0";
          this.deltaLineCoverage = "0";
          this.deltaMethodCoverage = "0";
          this.deltaClassCoverage = "0";
          this.buildOverBuild = false;
      }
      
    • List of DataBoundSetter’s

      ...
      @DataBoundSetter
      public void setExecPattern(String execPattern) {
          this.execPattern = execPattern;
      }
      
      @DataBoundSetter
      public void setClassPattern(String classPattern) {
          this.classPattern = classPattern;
      }
      
      @DataBoundSetter
      public void setSourcePattern(String sourcePattern) {
          this.sourcePattern = sourcePattern;
      }
      
      @DataBoundSetter
      public void setInclusionPattern(String inclusionPattern) {
          this.inclusionPattern = inclusionPattern;
      }
      
      @DataBoundSetter
      public void setExclusionPattern(String exclusionPattern) {
          this.exclusionPattern = exclusionPattern;
      }
      ...
      
    • Symbol annotation with function name

      @Extension @Symbol("jacoco")
      public static class DescriptorImpl extends BuildStepDescriptor<Publisher> {
      

Now when we found all the required information, we can start using JaCoCo Publisher step in Jenkins Pipeline:

  • via step class name

    step([$class: 'JacocoPublisher',
          execPattern:'**/**.exec',
          classPattern: '**/classes',
          sourcePattern: '**/src/main/java'])
    
  • via step function name

    jacoco execPattern:'**/**.exec', classPattern: '**/classes', sourcePattern: '**/src/main/java'])
    

Few more examples

Few more examples of Jenkins plugins which are compatible with Jenkins Pipeline, but don’t provide usage steps:

  • ArtifactsArchiver
    • From source code:

      public class ArtifactArchiver extends Recorder implements SimpleBuildStep {
      
      @DataBoundConstructor public ArtifactArchiver(String artifacts) {
         this.artifacts = artifacts.trim();
         allowEmptyArchive = false;
      }
      
      @Extension @Symbol("archiveArtifacts")
      public static class DescriptorImpl extends BuildStepDescriptor<Publisher> {
      
    • In Jenkinsfile:

      step([$class: 'ArtifactArchiver', artifacts:'someFile.txt'])
      

      or

      archiveArtifacts artifacts: 'someFile.txt'
      
  • ArtifactArchiverStep (yes, 2nd archiver :confused: )
    • From source code:

      public class ArtifactArchiverStep extends AbstractStepImpl {
      
      @DataBoundConstructor
      public ArtifactArchiverStep(String includes) {
          this.includes = includes;
      }
      
      @Extension
      public static class DescriptorImpl extends StepDescriptor {
      
          @Override
          public String getFunctionName() {
              return "archive";
          }
      
          @Override
          public String getDisplayName() {
              return "Archive artifacts";
          }
      }
      
    • In Jenkinsfile:

      archive artifacts: 'someFile.txt'
      

      Since class ArtifactArchiverStep extends AbstractStepImpl and not extends SimpleBuildStep we can’t use following syntax:

      step([$class: 'ArtifactArchiverStep', artifacts:'someFile.txt'])
      

      and following exception will be thrown:

      java.lang.UnsupportedOperationException: no known implementation of interface jenkins.tasks.SimpleBuildStep is named ArtifactArchiverStep
      

      Step resolving happens in DescribableModel#resolveClass method.

  • HockeyappRecorder
    • From source code:

      public class HockeyappRecorder extends Recorder implements SimpleBuildStep {
      
      @DataBoundConstructor
      public HockeyappRecorder(List<HockeyappApplication> applications, boolean debugMode,
                               BaseUrlHolder baseUrlHolder, boolean failGracefully) {
          this.applications = applications;
          this.debugMode = debugMode;
          this.baseUrlHolder = baseUrlHolder;
          if (baseUrlHolder != null) {
              this.baseUrl = baseUrlHolder.baseUrl;
          }
          this.failGracefully = failGracefully;
      }
      

      Constructor takes an array of HockeyApplication objects

      @DataBoundConstructor
      public HockeyappApplication(String apiToken, String appId, boolean notifyTeam,
                                  String filePath, String dsymPath, String libsPath,
                                  String tags, String teams, boolean mandatory,
                                  boolean downloadAllowed,
                                  OldVersionHolder oldVersionHolder,
                                  RadioButtonSupport releaseNotesMethod,
                                  RadioButtonSupport uploadMethod) {
          this.schemaVersion = SCHEMA_VERSION_NUMBER;
          this.apiToken = Util.fixEmptyAndTrim(apiToken);
          this.appId = Util.fixEmptyAndTrim(appId);
          this.notifyTeam = notifyTeam;
          this.filePath = Util.fixEmptyAndTrim(filePath);
          this.dsymPath = Util.fixEmptyAndTrim(dsymPath);
          this.libsPath = Util.fixEmptyAndTrim(libsPath);
          this.tags = Util.fixEmptyAndTrim(tags);
          this.downloadAllowed = downloadAllowed;
          this.oldVersionHolder = oldVersionHolder;
          this.releaseNotesMethod = releaseNotesMethod;
          this.uploadMethod = uploadMethod;
          this.teams = Util.fixEmptyAndTrim(teams);
          this.mandatory = mandatory;
      }
      
    • In Jenkinsfile:

      step([$class: 'HockeyappRecorder',
            applications: [[apiToken: 'token123', downloadAllowed: true,
                            filePath: 'file123', mandatory: false, notifyTeam: true,
                            releaseNotesMethod: [$class: 'ChangelogReleaseNotes'],
                            uploadMethod: [$class: 'AppCreation', publicPage: false]]],
            debugMode: false, failGracefully: false])
      

      As there is no Symbol and no function name provided for the descriptor in HockeyappRecorder.java this is the only way to call HockeyappRecorder step.


We learned how Jenkins Pipeline works with Jenkins plugins and how to use them in Jenkinsfile. With the help of the community :pray:, we can increase coverage of plugins compatibility with Jenkins Pipeline.

comments powered by Disqus