Jay Grossman

Creating a Custom Jenkins Plugin with JRuby

Creating a Custom Jenkins Plugin with JRuby

I have seen scenarios where I would like to not have Jenkins not execute builds under certain circumstances. One common example is with the Maven Project Plugin, it will often update the .pom file and check it back into source control. When you have Jenkins jobs that poll source code repos for new commits, your builds will enter an endless cycle of triggering more builds.

It would be nice to have the ability to stop builds from executing when the commit contained a specified phrase. I searched for such a plugin with no luck, so I have created my own plugin (that I use in production):

https://github.com/jaygrossman/jenkins-ignore-commit-plugin

Since there wasn't much detail for creating custom plugins in Ruby that I could find, this blog post will walk through the process.

Options for making Jenkins Plugins

1) Maven (default)
2) JRuby

Since setting up .pom files always seems painful to me, I wanted to try the Ruby option.

I found this very light post from 2013 that showed a few examples and the jpi gem with not much explanation:
https://wiki.jenkins-ci.org/display/JENKINS/Jenkins+plugin+development+in+Ruby

Setting up a JRuby Plugin Development Environment

I like to do all my development in reproducible environments when possible, so I set up a vagrant environment for building and testing Jenkins Plugin development & testing.

My Vagrantfile installs the following dependencies on Centos 6.5:

  • Java 1.6
  • Maven 
  • Jenkins
  • rbenv & jruby 1.7.9
  • jpi gem

Setting up a JRuby Plugin Development Project

1) Create a directory with the name of your project (jenkins-ignore-commit-plugin)

2) Create a pluginspec file (jenkins-ignore-commit-plugin.pluginspec):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Jenkins::Plugin::Specification.newdo |plugin|
    plugin.name = "jenkins-ignore-commit-plugin"
    plugin.display_name = "Ignore Commit Plugin"
    plugin.version = '0.0.1'
    plugin.description = 'Commits that contain a supplied phrase within the 
    commit messages will be skipped.'
    
    # You should create a wiki-page for your plugin when you publish it, see
    # https://wiki.jenkins-ci.org/display/JENKINS/Hosting+Plugins#HostingPlugins-AddingaWikipage
    # This line makes sure it's listed in your POM.
    plugin.url = 'https://wiki.jenkins-ci.org/display/JENKINS/Ignore+Commit+Plugin'
    
    # The first argument is your user name for jenkins-ci.org.
    plugin.developed_by "jaygrossman", "Jay Grossman <jay.grossman@org>"
    
    plugin.uses_repository :github => "jaygrossman/jenkins-ignore-commit-plugin"
    # This is a required dependency for every ruby plugin.
    plugin.depends_on 'ruby-runtime', '0.12'
end 

3) You'll need these gems: jenkins-plugin-runtime, jpi, jruby-openssl. Here is my Gemfile:

1
2
3
4
5
6
7
8
9
10
gem "jenkins-plugin-runtime", "~> 0.2.3"

group :development do
    gem "jpi", "~> 0.3.8"
    gem "jruby-openssl", "~> 0.8.8"
    gem "rake", "~> 10.0.4"
    gem "pry"
    gem 'coveralls', require: false
    gem 'rubyzip', "~> 0.9.9"
end

4) If your plugin requires user interface elements (such as checkbox, textbox, textarea, password) such as the items shown below, you'll want to create a View.

configure_jenkins_plugin

To set up the View:

  • Create views sub-directory.
  • Create a plugin sub-directory with views (I called my directory views/ignore_commit). 
  • Create a file named config.erb in that directory.
  • The entry() function can define the form element, title, field id, and description as shown below:
  • 1
    2
    3
    4
    5
    6
    7
    
    <% 
    f = taglib("/lib/form")
    f.entry(:title => 'phrase', :field => 'ignore_commit_phrase', 
    :description => "Commits containing this phrase will be considered NOT_BUILT") do
    f.textbox
    end
    %>
    

    5) Next you'll want to create a model to do the actual work for the plugin.

  • a) create a models sub-directory
  • b) create a file plugin.rb file (mine is called ignore_commit.rb):
  • 1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    
    class IgnoreCommit < Jenkins::Tasks::BuildWrapper
    display_name "Ignore Commits with Phrase"
    
    attr_accessor :ignore_commit_phrase
    
    def initialize(attrs)
        @ignore_commit_phrase = attrs['ignore_commit_phrase']
    end
    
    # Here we test if any of the changes warrant a build
    def setup(build, launcher, listener)
        begin
        changeset = build.native.getChangeSet()
        # XXX: Can there be files in the changeset if it's manually triggered?
        # If so, how do we check for manual trigger?
        if changeset.isEmptySet()
            listener.info "Empty changeset, running build."
            return
        end
    
        logs = changeset.getLogs()
        latest_commit = logs.get(logs.size - 1)
        comment = latest_commit.getComment()
    
        if comment.include? ignore_commit_phrase
            listener.info "Build is skipped through commit message."
            listener.info "Commit: #{latest_commit.getCommitId()}"
            listener.info "Message: #{comment}"
            
            build.native.setResult(Java.hudson.model.Result::NOT_BUILT)
            build.halt("Build is skipped by Ignore Commit Plugin.")
        end
    
        rescue
            listener.error "Encountered exception when scanning for filtered paths: #{$!}"
            listener.error "Allowing build by default."
            return
        end
    
        listener.error "Encountered exception when looking commit message: #{$!}"
        listener.error "Allowing build by default."
        end
    end
    

    Building the code and generating the Jenkins Plugin

    From within the project directory, run:

    1
    
    jpi build
    

    The JPI gem executes a build via maven and will compile a .hpi file (plugin binary file that can be uploaded into Jenkins) in a pkg sub-directory:

    configure_jenkins_plugin

    You can then manually install the plugin from the Plugin Manager upload page in Jenkins.

    If you have a local  local Jenkins environment (like in our vagrant set up), you can run the following commands to upload the plugin to it:

    1
    2
    
    bundle update rake
    jpi server
    

    Running the Build and Testing Vagrant image

    1) Download this Vagrantfile and put it in the root of your plugin project directory:

    https://github.com/jaygrossman/jenkins-ignore-commit-plugin/blob/master/Vagrantfile

    2) Run this command to build the plugin and load it into a local Jenkins instance:

    1
    
    vagrant up
    

    3) Once the vagrant up execution is complete, paste the following link into a web browser on the host machine to view the Jenkins instance running in the vagrant:

    1
    
    http://localhost:58080