Introduction

Hudson is a popular Open Source Continuous Integration (CI) tool written purely in Java. Apart from being an Open Source product, the popularity of Hudson is due to its extensible nature using Plugins and the Plugin developer echo system. Plugins allow users and developers to do everything from customizing the way builds are done, results are displayed and notified and integration with Application LifeCycle Management systems such as SCM, Testing and Analysis tools etc. More than 400 Hudson Plugins, supporting various aspects of Continuous Integration, are available for free install. This article is for beginners who are interested in understanding the fundamentals of Hudson Plugin Development.

Hudson provides a series of extension points that allow developers to extend Hudson’s functionality. The Hudson HPI (Hudson Plug-in Interface) tool, a Maven plugin, helps developers to create, build, run and debug plugins that contains one or more extensions to these extension points. This article will cover:

Creating Hudson plug-in using Hudson HPI tool

Extending a Hudson Extension Point to provide custom implementation

Writing configuration for the Extension

How to add Global Configuration for the Extension

Getting Started

Creating and running the Hudson Plugin

This part of the article explains how to create a Hudson Plugin using a Hudson HPI tool, run the Plugin project and view it in action in the Hudson Test Server.

Hudson and MavenHudson uses Maven, another popular Open Source software project management
and comprehension tool, extensivley. Maven can be downloaded from http://maven.apache.org/.

Generating the Plugin skeleton

The very first step is to create the Plugin skeleton. Hudson comes with a tool to generate the minimal sources. The simple command is

mvn hpi:create

This command tells maven to create the Hudson Plugin skeleton source using the Hudson HPI tool. Maven downloads all the required software to execute the command and prompts the user needs

Tip: If maven throws the following error
[ERROR] No plugin found for prefix 'hpi' in the current project ..
then the following entry need to be at ~/.m2/settings.xml file
<pluginGroups>
<pluginGroup>org.jvnet.hudson.tools</pluginGroup>
</pluginGroups>

The name of the generated folder depends on the artifactId provided. It will have the following layout

pom.xml - Maven POM file which is used to build your pluginsrc/main/java - Java source files of the pluginsrc/main/resources - Jelly view files of the pluginsrc/main/webapp - Static resources of the plugin, such as images and HTML files.

Building and running the Plugin Project

The Hudson Plugin project created is minimal but a complete maven project. It can be built and run with out any modification using maven. The Plugin project is built with the command

mvn package

The package command tells maven to build the project and create the HPI package that can be installed directly to a Hudson server.

The skeleton Plugin project has a sample Extension, which is fully functional. It is possible to run the project and see the result of the extension added by this skeleton Hudson Plugin. The Plugin Project is run with the command

mvn hpi:run

Since the Plugin Project is a maven project, the configuration of the project is defined in the POM.xml. The maven goal hpi:run is responsible for several of the tasks including starting the Jetty Server, adding Hudson as a Web Application to that Server and installing the Plugin to Hudson . The “work” sub-folder in the Plugin Project folder is used as Hudson Home. The “work/plugins” folder, contains list of .hpi files corresponding to various bundled plugins. The only notable difference is a .hpl which corresponds to the Plugin Project being run. It is a simple text file which contains metadata describing all the files (classes, jars and resources) associated with the currently built Hudson Plugin. This file is generated by the HPI tool every time the plugin project is run using hpi:run. Hudson knows how to interpret this file and load the entire plugin with out packaging the plugin to a .hpi package. This makes it easy to debug during development time.

Once the Plugin Project is run successfully, and the Jetty Server fully started, Hudson Main page can be viewed using a browser window and typing the URL

http://localhost:8080

Examining the sample extension

As seen above, the hpi:run command installs the currently developed Plugin to Hudson which is added to the Jetty server as a Web Application. This Plugin

Adds an extension to the Hudosn builder interface. This sample custom builder called HelloBuilder does not do anything fancy, but simply prints out the text “Hello <name>” in the build console log.

Provides UI to configure the HelloBuilder Extension.

It is easy to see the HelloBuilder in action by creating a simple Hudson project, say “TestProject” and configuring it. The HelloBuilder is available as a builder in the Build section of the configuration page. It can be added as a builder to the TestProject from the drop down menu. Once the HelloBuilder is set as the builder for the project, the Build section displays the HelloBuilder as one of the Builder. The task of the HelloBuilder is to say “Hello <name>”. Input a name in the provided in the name TextBox.

Since HelloBuilder is set as the only builder of the TestProject project, when a build is started, HelloBuilder will be asked to perform its task. The only task of the HelloBuilder is to print out the message “Hello <name>” to the console log. Once a build of the TestProject is completed, the result of HelloBuilder can be viewed in the build console output.

Extending an Extension Point

The HelloBuilder Extension

This part of the article will look in to some of the code that extends the Builder Extension Point to understand

How to extend and extension Point.

How to implement the methods to extend the functionality encapsulated by the extension point

Hudson provides the concept of Extension Points and Extensions to facilitate contribution of functionalites to the core platform by plugins. Extension points are interfaces that encapsulate entry points to extend certain services or functionality of a service provided by the core platform.

Amongst various services provided by Hudson, the foremost is building a job. A job, which is a build-able project consists of several configurable area and build steps. Some of the build steps are

SCM checkout - Based on SCM type, source code is checked out

Pre-build - Invoked to indicate that the build is starting

Build wrapper - Prepare an environment for the build.

Builder runs - Actual building like calling Ant, Make, etc. happen.

Recording - Record the output from the build, such as test results.

Notification - Send out notifications, based on the results determined so far.

Builders are responsible for building jobs. The Extension Point provided by Hudson to contribute to this builder run step is aptly called Builder. Hudson comes bundled with two of the most popular builders - Ant and Maven. They are in fact Extensions to the Builder Extension Point. So it is possible for a plugin to provide its own Builder Extension as one of the Builders of the Job. Several external plugins exist for other popular builders such as make, gradle, rake etc. HelloBuilder, our example Builder Extension is a contrived example to understand how extensions are built. Far more sophisticated Builder Extensions are possible uisng the Builder Extension Point. Let us examine the source to understand how the extension mechanism works.

Class NamingEven though in this article the sample Extension is referred as HelloBuilder,
the Java Class corresponding to it is called HelloWorldBuilder.java.

Examining HelloBuilder Extension

In order for Hudson to understand a class as an Extension, it must

Extend a class that advertise itself as an Extension Point

Implement the required abstract methods to extend the functionality

Tell Hudson that the particular class is an Extension

Looking at the source HelloWorldBuilder.java one would notice, the class HelloWorldBuilder extends the class Builder which is the Extension Point for the Builder interface.

publicclass HelloWorldBuilder extends Builder {

The Builder class itself is a subclass of BuildStep, which defines the abstract method that needs to be implemented by the Extensions to contribute to the builder interface. The abstract method needed to be implemented by any Builder Extension is

BuildStep.perform(..) overridden by HelloBuilder will be called by Hudson to include the BuildStep functionality extended by HelloBuilder extension.

Finally to tell Hudson, the class is an Extension to some Extension Point, it must be annotated with the annotation @Extension. The annotation @Extension at the inner class DescriptorImpl tells Hudson the class is an Extension.

Launcher correctly launch the application in the Master or Slave node the job is running. Always use the return status of the Launcher to find out if the execution was successful. The standard output of the Launcher is hooked to the listener. This sends console output of the execution to Hudson. This is how the output of the command to list the User Directory is displayed in the build console.

Writing configuration for the Extension

There are two ways to configure your Extension. One is local to the area of the functionality the plugin extends and the other via the Hudson wide Global Configuration. In this exercise you will learn how to configure your Extension in the project configuration page. In this exercise you will learn:

How to add a UI to get input from user

How to give feedback to the user on their input

How to configure the Extension with the user input

Understanding the configuration file convention

Hudson uses a UI technology called Jelly. The Jelly UI technology is a Server Side rendering technology which uses a Rendering engine to convert XML based Jelly definitions (tags) to client-side code: HTML, Javascript and Ajax. Hudson provides a number of Jelly tags for your convenience.

The model objects are bound to these tag attributes via an Expression Language called Jexl. When the tags are rendered into HTML and Javascript, the rendered code includes information from the model objects to which their attributes are bound to. This makes it very powerful to express your view with simple jelly tags, rather than writing lots of HTML , Javascript and Ajax.

The jelly files you use to render the UI has the extension .jelly. They reside in the resources directory of the plugin. Hudson uses a heuristic convention to find these jelly files. The folder under which these jelly files must reside should have a path hierarchy similar to the package name of the model class, plus the name of model class itself.

Hudson use the same namespace of the Class package as the folder hierarchy plus model name. In your example the HelloWordBuilder model class has the package name org.sample.hudson. So the configuration file must reside under the folder

org/sample/hudson/HelloWordBuilder

Hudson uses another convention to tell if the configuration file is meant for local configuration or global configuration. If the configuration is named as config.jelly it is used as a local configuration file and its content is included in the configuration of the functionality that this Extension extends. Since HelloWordBuilder extends the Builder build step of a Hudson Job, any Jelly content put in the configuration file

org/sample/hudson/HelloWordBuilder/config.jelly

is included in the Job configuration page to configure the HelloWordBuilder extension in the Builder section of the Job Configuration.

As explained in in the earlier part of the article, HelloBuilder Extension provides a UI for the user to configure. The UI provided by the HelloBuilder Extension is a simple TextBox for the user to input their name. The content of the file is very simple. It is a pure XML file with jelly syntax

<j:jellyxmlns:j="jelly:core"xmlns:st="jelly:stapler"xmlns:d="jelly:define"xmlns:l="/lib/layout"xmlns:t="/lib/hudson"xmlns:f="/lib/form"><!-- Creates a text field that shows the value of the "name" property. When submitted, it will be passed to the corresponding constructor parameter. --><f:entrytitle="Name"field="name"><f:textbox/></f:entry></j:jelly>

There are two main tags playing the role of user interaction

entry - tells hudson the enclosing tags are considered as user interaction elements and submitted via HTML form

textbox - renders simple HTML text field whose value will be send back to the server

Understanding the UI rendering

Let us take a closer look at UI rendering from the jelly file. If you open the TestProject Job configuration Page and scroll down to the Build section and and view the Say Hello World Builder and its configuration, you would see a “Question” icon on the right hand side of the TextField. It displays Help Text . Where does this help text come from? If you look at the content of config.jelly, you’d notice there is no such Help text. However, Hudson still displays some Help. Once again convention comes in to play. In the same folder where your configuration exists, a file named “help-name.html” will be present. Examining the content of this file, you will see in the Help Text above. How does Hudson know to get the content from this file and display it as Help content for the field? The trick is in the name of the file. By convention Hudson look for a file name in the folder path as the config file. The name of the file should be

help-{fieldName}.html

In the config.xml we have

<f:entrytitle="Name"field="name">;
<f:textbox/>;
</f:entry<;

field=”name”, indicates the TextBox should be used as an entry field with the name “name”. So based on convention, the help text for that field should exist in a file with name “help-name.html”.

The content of the file help-name.html is pure HTML . You can include image, text and hyperlinks in the content to emphasise and enhance your Help Text. As mentioned in the help text, if you want to use information from Hudson model objects, then you should have jelly content in the field Help file and the extension of the file name should be .jelly instead of .html. To see this in action. Delete help-name.html file and create the file help-name.jelly. Add the following content to the file

<j:jellyxmlns:j="jelly:core"xmlns:st="jelly:stapler"xmlns:d="jelly:define"><div>
Welcome to ${app.displayName}.
Enter your name in the Field.
</div></j:jelly>

Stop and run the project again. Go to the Build section of the Page configuration page and click on the Help button on the right side of the TextBox. You would see in the help text, ${app.displayName} is replaced as “Hudson” which is the name of the application.

Understanding the interaction between UI and model

This part of the article explains how the UI interacts with Hudson model objects. HelloBuilder is a Hudson model object. It encapsulate data. UI can interact with this model to get and display its data or get information from user via fields in the UI and update the model data. Now examine how this happens.

You created help file help-name.jelly and included a Jexl expression ${app.displayName} in the content. When the Server side of the Hudson application received the request for Job configuration page, it included the HelloBuilder configuration snippet in to the Job configuration page. Since the Help itself is a jelly file, it was given to the Jelly renderer to render it to client side code. The Jelly renderer is responsible for substituting the corresponding value for the Jexl expression after evaluating it. The first part of the expression evaluates to the model object, then to the method name of the model object.

By default Hudson registers three identifiers for the model objects to the Jexl expression evaluator, they are

Since the expression ${app.displayName} evaluates to “Hudson”, the name of the Hudson application, that is what you see in the Field Help text.

While the UI displays the data of a model, the input of the user in the UI must update the model data when the configuration page is submited. In this case, the value of the name the user enters in the UI must be updated in the model.

When the UI is submitted, Hudson re-creates the model by passing the corresponding value via the constructor. Hence the constructor of the model Object must have a parameter whose name matches the name of the field. In the configuration you have

<f:entrytitle="Name"field="name">

So the constructor of your HelloBuilder must have a parameter with name “name”. If you look at the constructor of the class HelloWorldBuilder, it does indeed have a parameter “name”

Examining the UI validation methodology

In the Job configuration page go to Build Section -> HelloBuilder UI and remove the name in the text field. Then click else where on the page. You will see the error message as in Figure 15

Do not press enter or return keyThis will submit the configuration page.

Now enter a two letter word (say “xy”) for name and enter elsewhere. You will see an information message. Where does this error message or info come from? If you examine your config.xml or any of the corresponding Field Help files, no such message exists. The magic is in the Jelly file rendering. Some Ajax code is rendered, which behind the scenes contacts the Hudson server and asking what message it should display. You can easily observe these Ajax requests using Firefox and Firebug. When config.jelly was rendered by Hudson, the jelly tag <f:textbox /> the Ajax code required to do the checking is also rendered. Firebug displays the Ajax request info. An Ajax request is sent to Hudson Server as

Hudson evaluates this request, finds the extension HelloWorldBuilder then executes the method checkName() and returns the result. Again Hudson uses the convention doCheck + {nameOfTheField} as part of the Ajax URL.

By default for every f:textbox tag in the Jelly config file, Hudson will render the Ajax check. However, if your Extension Class does not include the corresponding method (in this case doCheckName()), then Hudson will silently ignore the check request.

The doCheckName() method is straight forward. The Ajax request specifies the checkName method with the parameter “value” as {..}/checkName?value=”xy”. Hence the parameter value of doCheckName must be annotated with the annotation @QueryParameter. Also the method must return a FormValidation Object which determines the outcome of the check.

As you see in this method, FormValidation.error(..) is returned if an error occurs. The HTML which is sent back to the client displays the text in red and with an error icon. If FormValidation.warning() is returned then the HTML sent back displays the message in brownish Yellow with a warning icon.

Adding your Extension’s Global configuration

You learned how to configure the Extension per Job basis. You may be able to configure the Extension (in this case HelloBuilder) of each Job to do different things. However, some of the configuration Extension could be global. For example, if you use Git as your SCM in a project, you might want to configure two different projects to force Git to check out from a different repository. So in both the projects, Git SCM Extension must be configured to use two different repository URLs.

If the Git SCM Extension needs to know the Git native binaries, then placing the UI to configure the Git binary location in the project makes little sense, because it doesn’t vary project to project. It makes sense to put such configuration in Hudson’s Global Configuration Page.

For Hudson to include the Global configuration of an Extension in its Gloabl Configuration, it must be placed in a file called global.jelly. The namespace convention for this file is similar to local config. For the HelloBuilder the global config file is

HelloBuilder can be configured to say hello either in French or English. The configuration of the language to use is done globally. Once set, all the builds of various jobs which use HelloBuilder would either say hello in French or English based on this global configuration.

To configure this global configuration open the Hudson’s Global Configuration Page and scroll down to the Hello World Builder configuration section. Here you can set the global configuration to use French as the hello language. In the global.jelly the checkbox is defined as

<f:sectiontitle="Hello World Builder"><f:entrytitle="French"description="Check if we should say hello in French"help="/plugin/javaone-sample/help-globalConfig.html"><f:checkboxname="hello_world.useFrench"checked="${descriptor.useFrench()}"/></f:entry></f:section>

The decision whether this checkbox should be checked or not comes from the Extension itself. The Jexl expression ${descriptor.useFrench()} would resolve to HelloBuilder.DescriptorImpl.useFrench() which is defined as

publicboolean useFrench(){return useFrench;}

useFrench is a field in HelloBuilder. This field should be set to true if the user checks the CheckBox. Once the global configuration is submitted, by convention Hudson would call the HelloBuilder.Descriptor.config passing a JSONObject. It is up to the Extension to find its submitted value and then use it. HelloBuilder defines this method as

In this method the boolean value of the field useFrench is obtained as set to HelloBuilder.useFrench. The next time HelloBuilder.perform() is called during the Build of the Job, HelloBuilder.useFrench is consulted and based on its value either the hello message is either in French or English

Loading and Saving the global configurations

Your Extension must do the work of loading and saving the global configurations. The saving is done by calling the method

save();

Typically the saving is done in the configure method. Once the form data received as JSONObject is processed, save() method is called, which in turn tells hudson to persist the current Descriptor object.

To load the global configuration, the Descriptor must include a call to the method

load();

The best place to have the load() method is in the constructor of the Descriptor. The load() method instructs Hudson to load the saved global configuration and populate the current Descriptor.

In order for the saving and loading of global properties to work correctly, each property should have proper getter and setter methods

Summary

Hudson Continuous Integration Server is a popular Open Source project and recently became a Technology project at Eclipse Foundation. It has an ecosystem of Plugin developers developing plugins for various aspects of this Continuous Integration System. The Hudson Plugin Development Environment provides a rich set of Extension Points for Plugin Developers to develop their custom plugins. This article explored the fundamentals of the Plugin Development Environment explaining various steps involved in developing a simple plugin using Hudson HPI tool.