How to write a Jenkins Plugin – Part 1

Maven Settings

You will need Maven 3 for this. Maven 2 will not work. (Also needs JDK 6 or higher.)
You will need to add some junk to your settings.xml:

In pluginGroups add
<pluginGroup>org.jenkins-ci.tools</pluginGroup>

And In profiles, add a profile like this:

<profile>
<id>jenkins</id>
<activation>
<activeByDefault>false</activeByDefault>
</activation>
<repositories>
<repository>
<id>repo.jenkins-ci.org</id>
<url>http://repo.jenkins-ci.org/public/</url>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>repo.jenkins-ci.org</id>
<url>http://repo.jenkins-ci.org/public/</url>
</pluginRepository>
</pluginRepositories>
</profile>

The official tutorial suggests making this profile active by default but of course you don’t want to do that if you’re working with other profiles for other purposes.

Create a plugin project

You can follow the official tutorial as it’s a quick way of getting some of the basic settings established, but we’re going to throw away the example plugin code and make our own.

Open a command prompt in your chosen working directory and run
mvn hpi:create -Pjenkins

You will be asked for the group id and you can accept the default (org.jenkins-ci.plugins).
Then specify an artifact id, which should be the name of your plugin without the “-plugin” on the end, eg “custom-view-tabs”.

You should now have a new “custom-view-tabs” folder containing a pom and a src folder. The pom has some of the important settings you need, namely the parent specification:

<parent>
<groupId>org.jenkins-ci.plugins</groupId>
<artifactId>plugin</artifactId>
<version>1.509.4</version><!-- which version of Jenkins is this plugin built against? -->
</parent>

You could easily get to the same point using mvn archetype:generate but it’s just as fast this way.

The pom will have an unnecessary group id duplicating the parent group id so you can remove that.

Organise the code

Look under src/main/java and you’ll see the package structure is org.jenkinsci.plugins.customviewtabs. You can keep this or not, it doesn’t matter to Jenkins. We’ll keep it.

You can delete the example HelloWorldBuilder.java because we are going to make something different.

Look under resources and you’ll see a package org.jenkinsci.plugins.customviewtabs.helloworldbuilder, which you can also delete, leaving the customviewtabs package.

At this point you probably want to import the Maven project into your IDE.

Write the main plugin class

We’ll create an empty class for the plugin. I’ll try to explain what all this does as we go, but first just copy this so that we can do a test build and deploy into a running Jenkins instance to see how the development process works.

Add the following CustomViewsTabBar.java:

package org.jenkinsci.plugins.customviewtabs;

import hudson.Extension;
import hudson.views.ViewsTabBar;
import hudson.views.ViewsTabBarDescriptor;
import net.sf.json.JSONObject;

import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.StaplerRequest;


public class CustomViewsTabBar extends ViewsTabBar {
   
    @DataBoundConstructor
    public CustomViewsTabBar() {
        super();
    }
    
    @Extension
    public static final class CustomViewsTabBarDescriptor extends ViewsTabBarDescriptor {

        public CustomViewsTabBarDescriptor() {
            load();
        }

        @Override
        public String getDisplayName() {
            return "Custom Views TabBar";
        }

        @Override
        public boolean configure(StaplerRequest req, JSONObject formData) throws FormException {
            save();
            return false;
        }
    }
}

There are a few important things in here. First, you can see that we are extending the built in “ViewsTabBar” component. This is one way of creating plugins, but not the only way. If you look at the ViewsTabBar class you’ll see that it implements hudson.ExtensionPoint, a “Marker interface that designates extensible components in Jenkins that can be implemented by plugins”.

You’ll also see that we have a static nested class marked with @Extension and extending Descriptor, via ViewsTabBarDescriptor. The @Extension annotation marks the component for automatic discovery by the Jenkins framework.

This Descriptor is how Jenkins creates instances of your plguin and passes configuration settings. We’ll come onto this in more detail in part 2 when we add some global configuration options, but first here’s what the JavaDoc says:

hudson.model.Descriptor
Metadata about a configurable instance. 

Descriptor is an object that has metadata about a Describable object, and also serves as a factory (in a way this relationship is similar to Object/Class relationship. A Descriptor/Describable combination is used throughout in Hudson to implement a configuration/extensibility mechanism. 

Take the list view support as an example, which is implemented in ListView class. Whenever a new view is created, a new ListView instance is created with the configuration information. This instance gets serialized to XML, and this instance will be called to render the view page. This is the job of Describable — each instance represents a specific configuration of a view (what projects are in it, regular expression, etc.) 

For Hudson to create such configured ListView instance, Hudson needs another object that captures the metadata of ListView, and that is what a Descriptor is for. ListView class has a singleton descriptor, and this descriptor helps render the configuration form, remember system-wide configuration, and works as a factory. 

Descriptor also usually have its associated views. 

There are two really important things to notice regarding the lifecycle of your plugin and global configuration data: In the constructor we call load(), which loads the configuration data from an XML file managed by Jenkins, and in the configure method we call save() to persist the config data into the xml file, again managed by Jenkins. The configure method is called by Jenkins when the global config is changed and the user hits the “apply” or “save” button, and we get passed the data from the global config form as JSON. So far, we’re not actually doing anything to read and process that JSON, so the save() call looks a little pointless, but for working with global config this is the lifecycle.

It seems to be conventional to make the descriptor a static nested class in your main plugin class. When I tried to extract it, the plugin broke and was not detected by Jenkins. This relates to the way Jenkins locates extensions, and when I understand exactly why, I’ll update this. For now, save yourself some wasted time and just stick with the nested class.

Now you should be able to build and package the plugin, not forgetting to use the profile: mvn clean install -Pjenkins. Once built, you should see the compiled .hpi artifact in the target directory.

Deploy the plugin

The Jenkins maven plugin makes it very easy to deploy your plugin to a running Jenkins instance. Simply execute

mvn hpi:run -Djetty.port=8090 -Pjenkins

choosing whatever port you like or omitting the port option if you don’t already have anything at 8080. Now visit http://localhost:8090/jenkins/ to view the running Jenkins with your plugin installed.

Click through manage jenkins and manage plugins to the installed plugins tab and you should see your plugin listed, with a snapshot version matching your build time.

Now click through to the “Configure System” page. Near the top, you will see a “Views Tab Bar” drop down where you can select the views tab bar implementation to use. “Custom Views TabBar” should be available as an option. Note that the text here came from the getDisplayName() method we specified in our descriptor.

For this particular extension point (the ViewsTabBar extension point), Jenkins detects any implementations of the extension point and makes them available for selection here (look at the all() method in the ViewsTabBar class for a clue about how Jenkins does this). Other extension points are used in other ways. One of the first tasks in your plugin project will be to identify which extension point you need to use.

This is one specific and limited example, but hopefully by the end of this series things will make a little more sense and you’ll have an easier time figuring out how to get your own off the ground.

In part 2 we will add some configuration options to appear in the global config page, and call our plugin from the Jenkins UI, in this case the view tabs, to use those config settings.

Posted in Jenkins, Jenkins Plugins
12 comments on “How to write a Jenkins Plugin – Part 1
  1. Pooja Shah says:

    Hi Alistair!
    nice post, can you please help me little more on this-
    I’m using & extending a plugin -extreme feedback Panel plugin which is in java, what I want is to get the build parameters used while building the job in plugin code so that I can display it on dashboard using jelly. In jenkinspython api, its available under get_param() function but for java I cant see relevant function, can you please suggest which method will return the build parameters used. [have explained the problem in detail- https://github.com/jenkinsci/xfpanel-plugin/pull/13#issuecomment-56828115 ]

  2. Igor Gabriel says:

    Hi, congratulations your post!
    I was looking for a post simple post like that. I start learning Jenkins recently and your post helped me a lot.

  3. Abhijith says:

    Hi,

    Its a very simple example with a neat explanation.
    I’ve started creating a plugin by reading this post.
    Hope it takes me a long way !

    Thanks man….

  4. Pratham N says:

    I am trying to build my own plugin for jenkins.I tried the above procedure but my pom.xml displays error for the parent tag.

    (Plugin execution not covered by lifecycle configuration: org.codehaus.gmaven:gmaven-plugin:1.5-jenkins-1:generateTestStubs (execution: test-in-groovy, phase: generate-test-sources)

    I am unable to determine the cause of error any information on this error will be helpful

    • Todderz says:

      Sounds like you’re using Eclipse. Eclipse uses plugins to control when it should fire maven goals as things change in the editor. This error just means Eclipse won’t run those goals. Either click through that error and install a suitable plugin from the Eclipse marketplace, or simply build the project with maven at the command line.

      Running maven directly in a command prompt outside eclipse will tell you whether there is a maven configuration problem. The plugin execution error is just Eclipse.

  5. Pratham N says:

    I tried installing plugins from eclipse market place but the error didnt resolve and I tried to understand the cause of error but it too didnt help.
    I am new to maven could you help me in Executing it from command prompt

  6. Priti says:

    Hey there! Thank you so much for this tutorial. Although could you please help me with something? When I deploy the app after the part-1 of the tutorial I get the following error:

    javax.servlet.ServletException: org.apache.commons.jelly.JellyTagException: jar:file:/Users/prit8976/Documents/IntelliJ%20Projects/custom-view-tabs/target/work/webapp/WEB-INF/lib/jenkins-core-1.580.1.jar!/hudson/model/View/index.jelly:42:43: org.apache.commons.jelly.JellyTagException: jar:file:/Users/prit8976/Documents/IntelliJ%20Projects/custom-view-tabs/target/work/webapp/WEB-INF/lib/jenkins-core-1.580.1.jar!/lib/hudson/projectView.jelly:67:24: No such view: viewTabs for class org.jenkinsci.plugins.customviewtabs.CustomViewsTabBar

    Am I doing anything wrong?

    • Todderz says:

      Sorry, I don’t know. I’ll try to have a look when I get home. Check that you have extended hudson.views.ViewsTabBar and called its constructor from your constructor.

      It might be a version problem. It’s been a while since I wrote this guide…

  7. Fijo says:

    good post man…!! was able to start with a sample plugin.

Leave a reply to Todderz Cancel reply