Introduction

Welcome to this workshop briefly describing the Jenkins plugin ecosystem and showing how to contribute a change request to an existing plugin.

Agenda

  1. Jenkins Plugins
  2. Plugin Development
  3. Practice
  4. Resources

Outline

When working with Jenkins in your daily business there comes a time when you have a special request that can not be solved using Jenkins core features or is not fully implemented in one of the numerous existing plugins. Before writing a new plugin from scratch you are encouraged to contribute to existing plugins.

In this workshop you will learn how to change an existing Jenkins plugin and adapt it to your needs. In particular we will change the Yet Another Build Visualizer Plugin to show the display name of a build instead of the build number.

The practice part will guide you through first setting up your development environment and forking the plugin into your GitHub account. After implementing the requested change and extending the unit tests your contribution is pushed back as a GitHub Pull Request.

Hopefully this workshop will encourage you to actively help improve Jenkins plugins by contributing code, documentation, translations, or tests. Let's start!

Jenkins Superhero

(Source: Jenkins Artwork)

Jenkins Plugins

With more than 1700 plugins available Jenkins build system provides integrations for hundreds of tools and services and therefore allows you to customize almost anything to your needs.

Top Plugin installations (installations > 2500) Top Plugin installations (> 2500) (Source: Monthly Jenkins Statistics)

 

Such a powerful extension system also has its drawbacks:

  • inherent complexity for new plugin developers
  • version conflicts due to dependencies with core and other plugins
  • high maintenance installing, configuring and keeping plugins up-to-date
  • out-dated, low-quality plugins written by third parties with losing support

Extensions

Extensibility in Jenkins, for core internals as well as for plugins, is accomplished by definitions of extension points. In general an extension point is an interface or abstract class that defines a contract of what needs to be implemented by contributing classes.

There a mainly two concepts of Descriptors and Describables and Actionable and Actions in order to create new or extend other extension points.

The bulk work in developing plugin consists in implementing those extension points. Whenever a plugin implements such extension points by annotating the class with an @Extension mark, Jenkins automatically detects, creates instances and attaches them to the according extension point instance.

Example Extension

The implementation of the BuildStepDescriptor below registers a new build step called SampleBuilder by marking the Descriptor class with the @Extension annotation.

@Extension
public static class Descriptor extends BuildStepDescriptor<Builder> {

    @Override
    public String getDisplayName() {
        return "SampleBuilder";
    }
}

Further Reading

The full list of all available extensions can be found in the Extensions Index.

For further information regarding extending Jenkins in general see Extensibility.

Plugin Installation

Jenkins provides two common methods for installing or updating plugins on the master:

  • Plugin Manager (Update Center) in the web UI
  • Built-in command line interface (CLI)

After successful download of a plugin and its dependencies a Jenkins master restart is only required if the plugin itself or one of the dependencies are updated from a previous installed version. Otherwise the plugin changes take effect directly.

Jenkins Update Center

Search for plugins under the "Available" tab through the "Manage Jenkins > Manage Plugins" view. Select one or multiple plugins and click "Install without restart" to activate them immediately.

Plugin Search

After building a self-contained .hpi file with all necessary code, resources and other dependencies it can be uploaded manually from the web UI through "Advanced" tab.

Plugin Upload

Jenkins CLI

The Jenkins CLI provides a plugin installation command:

java -jar jenkins-cli.jar -s <JENKINS_URL>:<JENKINS_PORT>/ install-plugin <SOURCE>

The SOURCE part can be given as a local file reference (.hpi file path), download URL or as the short name of an existing plugin.

Further Reading

Collaboration

The Jenkins project and its huge community are always looking for new contributers and encourage you to participate in many ways. Help others get most out of Jenkins, provide your feedback, review changes to code or documentation, or contribute actively to Jenkins core and the numerous plugins.

As a plugin contributor you can adopt a plugin or pick up a feature request or bug report from the issue tracker and send a pull request. Alternatively, start writing a new plugin if none of the existing plugins cover your needs.

Further Reading

The following resources will help you to participate and contribute to Jenkins ecosystem:

Development

This section will cover the main aspects in order to create a new Jenkins plugin from scratch or to contribute to an existing one.

Creating a new plugin

Plugin Archetype

A new plugin can be created from scratch by running following Maven command:

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

This invokes the generation of a new plugin from several project archetypes and requests for all mandatory information like groupId, artifactId, initial version and package structure.

Plugin Structure

The plugin workspace is structured as follows:

  • Project root: Maven build configuration pom.xml and all the project subdirectories
  • src/main/java: Deliverable Java source code files
  • src/main/resources: Deliverable resources, such as Jelly and property files for UI
  • src/main/webapp: Static resources, such as images and HTML files
  • src/test/java: Unit testing classes
  • src/test/resources: Necessary resources for unit tests only

Maven POM

Maven is generally used to build Jenkins plugins and stores the configuration in the pom.xml located at the root directory. New plugin are based on the Plugin Parent POM:

<parent>
    <groupId>org.jenkins-ci.plugins</groupId>
    <artifactId>plugin</artifactId>
    <version>3.42</version>
    <relativePath />
</parent>

The Jenkins baseline version and Java level are mandatory and can be set via Maven property:

<properties>
    <jenkins.version>2.164.1</jenkins.version>
    <java.level>8</java.level>
</properties>

Further Reading

Building a plugin

To build a plugin run following Maven command:

mvn install

When rebuilding a plugin it is sometimes helpful to clean the target directory previously:

mvn clean install

This will create a plugin artifact at target/<plugin>.hpi that can be deployed manually to Jenkins.

CI Integration

Hosted Jenkins plugins are automatically built with each commit by placing Jenkinsfile at project root.

#!groovy
buildPlugin()

Further Reading

Debugging a plugin

With help of the Maven HPI Plugin Jenkins plugins can be started in a local Jenkins sandbox.

For debugging purposes run following Maven command from plugin project directory:

mvn hpi:run

By default, the debugging instance is then available at http://localhost:8080/jenkins in your browser.

In order to launch Jenkins on a different port than 8080 use this system property:

mvn hpi:run -Djetty.port=8090

Changing the default context path can be achieved by setting this system property:

mvn hpi:run -Dhpi.prefix=/debug

Debugging from IDE

Starting a debug session from your IDE requires to set special Maven options:

MAVEN_OPTS="-Xdebug -Xrunjdwp:transport=dt_socket,server=y,address=8000,suspend=n"

This allows to hook into the instance and set break points while being in IDE debugging mode.

Changing code while debugging is supported for Jelly/Groovy views, properties and help files with simple page reload in the browser. Utilizing from JVM's hot swap feature does not support changing method signatures. Changes in pom.xml also requires to restart the debugging process.

Further Reading

Testing a plugin

Next to well-known unit testing frameworks like JUnit, TestNG, Spock etc. which can be easily integrated in Java projects, Jenkins provides an own test harness framework built around JUnit to make testing plugins much easier. There are several test techniques included like stubbing, HTML scraping or round-trip testing and provides multiple test harness annotations.

Unit tests can be executed with following Maven command:

mvn test

While building the plugin unit tests can be skipped by appending the Maven command as follows:

mvn install -DskipTests

Further Reading

Publishing a plugin

Packaging

To create a local distribution image of your plugin run the following Maven command:

mvn package

After successful packaging an plugin artifact should be created at target/*.hpi. This .hpi file can then be manually uploaded to the Jenkins Update Center or directly placed in the Jenkins plugin directory (located at $JENKINS_HOME/plugins). The Jenkins master needs to be restarted afterwards.

Hosting

In order to officially host a new plugin in the Jenkins Update Center, meaning it will be available to all Jenkins users worldwide, you first have to meet some prerequisites regarding the plugin structure and open source licensing. Fulfilling those you can request hosting your plugin repository under the Jenkins GitHub organization. It is recommended to add your maintainer information and to create a well-documented and up-to-date wiki page for your plugin.

Further Reading

IDE Support

Because most Jenkins plugins are based on Java and are built with Maven any IDE software can be chosen that provides JVM language and Maven framework support. The Maven HPI Plugin provides an easy way to integrate the plugin lifecycles into the Maven build process.

With help of the Gradle JPI Plugin Groovy and Gradle can be used as an alternative way to build and release such configured plugins.

Jelly Views

XML based tag libraries for writing Jelly views are supported by Stapler plugins available for most modern IDEs (see links to below).

Internationalization

Support for different languages can be achieved by internationalization features in Stapler plugins.

IntelliJ IDEA

NetBeans

Eclipse

Practice

Task

Show the build name instead of the build number in Yet Another Build Visualizer plugin.

You will change the Build Flow view from

Build Flow Before

to named builds

Build Flow After


Note: The original change request in JENKINS-55893 has been already implemented with this commit but is used to be reimplemented just for learning purposes of this workshop.

Requirements

For the practice part meet following prerequisites:

Set up the development environment

Task

Follow the instructions below to prepare your local development environment, alternatively run with Docker or choose the browser-based online IDE powered by GitPod.

Local Toolchain

For building and developing the Jenkins plugin Java SE Development Kit and Maven are required.

IDE

The preferred IDE for this workshop is IntelliJ IDEA Community Edition because it is free and Maven is shipped with the installation. Alternatively take the IDE of your choice (Eclipse, NetBeans, etc.) you are most familiar with.

JDK

Jenkins core is based on Java and therefore most of the Jenkins plugins also relies on Java or JVM compatible languages like Groovy. For this workshop we will use JDK 8 you can download for free from the Oracle website and install it afterwards. Ensure this JDK is properly configured in your IDE.

Maven

Most of the Jenkins plugins utilize Maven as the preferred build management system. The plugin to be changed is already configured as a Maven project providing a pom.xml.

Download Maven from related Apache project and extract it to an arbitrary path. Afterwards ensure the bin/ subdirectory is added to your PATH in order to invoke mvn from command line.

IDEA: Use the bundled Maven version or change it via Settings > Build Tools > Maven

In order to make Jenkins repositories available during Maven builds edit your ~/.m2/settings.xml (Windows: %USERPROFILE%\.m2\settings.xml) as follows:

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

  <profiles>
    <!-- Give access to Jenkins plugins -->
    <profile>
      <id>jenkins</id>
      <activation>
        <activeByDefault>true</activeByDefault>
      </activation>
      <repositories>
        <repository>
          <id>repo.jenkins-ci.org</id>
          <url>https://repo.jenkins-ci.org/public/</url>
        </repository>
      </repositories>
      <pluginRepositories>
        <pluginRepository>
          <id>repo.jenkins-ci.org</id>
          <url>https://repo.jenkins-ci.org/public/</url>
        </pluginRepository>
      </pluginRepositories>
    </profile>
  </profiles>
  <mirrors>
    <mirror>
      <id>repo.jenkins-ci.org</id>
      <url>https://repo.jenkins-ci.org/public/</url>
      <mirrorOf>m.g.o-public</mirrorOf>
    </mirror>
  </mirrors>
</settings>

Docker

For building and debugging the plugin inside a Docker container you can use following commands:

Build Plugin

docker run -it --rm -v "$PWD":/usr/src/app -v "$HOME"/.m2:/root/.m2 \
-w /usr/src/app maven:3-jdk-8-alpine mvn install

Run Debugging Instance

docker run -it -d --rm -p 8080:8080 -v "$PWD":/usr/src/app -v "$HOME"/.m2:/root/.m2 \
-w /usr/src/app maven:3-jdk-8-slim mvn hpi:run

GitPod

As an handy alternative without the need to install any tools locally you can use an one-click online IDE. GitPod provides a ready-to-code development environment in your browser.

Usage

Fork the plugin

Task

Log in to your GitHub account and fork the repository cpoenisch/yabv-workshop-plugin into your personal account. Then copy the repository URL and clone the repository to your local machine, alternatively create a new project from Git Version Control in your IDE.

Hints

Show / Hide

Build the plugin

Task

First import the cloned plugin repository into your IDE as a Maven project.

Run a full build by invoking Maven from command line or IDE integrated terminal:

mvn install

Watch the build log for any build failures and ensure a .hpi file is created in the target directory.

Optionally, install the built plugin to an existing Jenkins master instance.

Hints

Show / Hide

Maven Repositories

Add Jenkins plugin repositories to pom.xml or your .m2/settings.xml

<pluginRepositories>
  <pluginRepository>
    <id>repo.jenkins-ci.org</id>
      <url>https://repo.jenkins-ci.org/public/</url>
  </pluginRepository>
</pluginRepositories>

IntelliJ IDEA

  • Add Maven framework support
  • Configure JDK 8 as project SDK and set Java language level to 8

Ensure that following directories are marked as:

  • Source Root: src/main/java
  • Resources Root: src/main/resources
  • Test Source Root: src/test/java
  • Test Resources Root: src/test/resources
  • Excluded: target

Implement the changes

Task

Change the Groovy view rendering the Build Flow from displaying the build display name instead of the build number. Rebuild the project to check for any syntax or compiling errors.

Hints

Show / Hide

  • Take a look at the JavaDocs for available AbstractBuild methods
  • Source file for the Groovy view is located in resources
  • Change the current implementation at line 46 from
span("${build.parent.name} #${build.number}")

to

span("${build.parent.name} ${build.displayName}")

Debug the plugin

Task

Instantiate a local Jenkins instance for debugging purposes by executing following Maven command:

mvn hpi:run

Open the local Jenkins instance at http://localhost:8080/jenkins in your browser.

In order to be able to change the display name of a build install the Build Name Setter beforehand. Then create at least two dependent Freestyle jobs and configure them to set the build name to anything other than the build number. Start a new upstream build and check if the change you did is working properly.

Hints

Show / Hide

  • Create an upstream job that triggers a downstream job
  • Set the build name in the Build Environment section
  • The default build trigger can be selected from post-build actions

Write unit tests

Task (optional)

Write a new unit test for the implemented change that is divided into following parts:

ARRANGE: Create an upstream job and a downstream job with arbitrary build names
ACT: Schedule a new upstream build
ASSERT: Assert that the job and build pages contain a Build Flow graph with the set build names

Trigger the new test directly from your IDE or run all tests by executing following Maven command:

mvn test

Hints

Show / Hide

Create a Pull Request

Task

Commit all your changes to your local development branch and push them to your remote branch.

Navigate to the "Pull requests" tab of your forked GitHub repository and click the "New pull request" button. Ensure that the correct origin repository (cpoenisch/yabv-workshop-plugin) with master branch is set as the base and you head to your own forked branch repository.

Choose a meaningful title for your pull request and give a brief description what you have changed. After your pull request has been reviewed, it can be merged into the repository.

Hints

Show / Hide

Resources