Jenkins : Basic guide to Jelly usage in Jenkins

If you are unfamiliar with Jelly and Jenkins' use of it, you may find it difficult to create configurable parameters for your plugin. This page describes some very simple examples to get you started.

UI Samples

The first thing you should do is install the UI Samples Plugin in your test instance so you can see how to do some common things.
It is available on the update center.

Loading Your First *.jelly

A basic plugin structure is 

pom.xml
src/main/java
src/main/resources
src/main/webapp

Suppose you have a java class stored in src/main/java/org/myorganization/MyAction.java that you would like to define Jelly files for.

  1. Create folder under resources with the same name as the class
    • Create src/main/resources/org/myorganization/MyAction/
  2. Add index.jelly to that folder
    • Write your first jelly file in src/main/resources/org/myorganization/MyAction/index.jelly

Now you can start getting more complex!

Understanding the it object

As the above section hints at, Jelly files are tied directly to classes. This means they can call methods on those classes. To reference the file they are tied to, jelly files use the it keyword. To define code, use the dollar sign and curly-braces, like this: "${insert code here}".

Here is a simple example:

  1. Create a java file
    • Let's use src/main/java/org/myorg/MyAction.java
  2. Define a method in the class
    • Let's define

      public String getMyString() {
          return "Hello Jenkins!";
      }
  3. Write a jelly file with the following
    • <j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout"
               xmlns:t="/lib/hudson" xmlns:f="/lib/form">
      
          ${it.myString}
      </j:jelly>
      
  4. Load the class, and note that 'Hello Jenkins!' is displayed

A few things to notice: the 'get' was automatically stripped from the method name, and the first letter of the remaining method name was lower-cased. I'd recommend using the Java convention for naming methods (e.g. starting getters with 'get' and using CamelCase) so that Jelly can always find the methods.

Other predefined objects

Depending on the page being rendered, other objects besides it may be predefined:

  • app - the instance of Jenkins (or Hudson)
  • instance - an object currently being configured, within a section of a configure page such as a BuildStep; null if this is a newly added instance rather than reconfiguring
  • descriptor - the Descriptor object (see below) corresponding to the class of instance
  • h - an instance of hudson.Functions, with various useful functions

Predefined URLs

Pages can use the following variables to create absolute links:

  • rootURL - the Jenkins instance
  • resURL - static webapp resources such as JavaScript or HTML (cf. Jenkins.RESOURCE_PATH)
  • imagesURL - like resURL but with /images appended, for loading icons and the like

(Use resURL rather than rootURL wherever possible as it permits browser caching, improving responsiveness and lessening server load.)

These URLs are defined as soon as you are within an l:layout or an l:ajax block. This means that on any Jenkins page you already have them available except if your page is loaded via ajax. Then you should add the l:ajax tag.

Note that until 1.505 rootURL will be empty in the typical case of Jenkins running in development mode with no context path (http://localhost:8080/); it is used for creating server-absolute paths such as /some/path for direct links. As of 1.505 it will be /jenkins in test mode, to help remind you to use it! In the rare case that you need to pass a complete URL to an external service for some reason, use app.rootUrl instead (which takes into account “Jenkins URL” from the system /configure screen). Hyperlinks in HTML has a fuller treatment of this topic.

Iteratively Modifying *.jelly files

It's worth mentioning that in most cases you don't need to re-start the Jenkins server, simply modify the *.jelly file in your editor of choice and re-request the page that will load that jelly. 

Objects with Descriptors

For objects with Descriptor (such as Publisher), the general steps are as follows:

  1. Define a immutable class that takes all the configuration parameters as constructor parameters. Put @DataBoundConstructor on this constructor, which tells Jenkins how to instantiate it.
  2. Define getters for the configuration fields, or make the fields "public final". This allows Jelly script to read the values to populate the configuration page.
  3. Write a Jelly fragment (normally named config.jelly but see javadoc of your base class) and list all the configuration options. The most basic form of this is something like the following. The value of @field is used as a property name (so you need to either have the getPort method or a public port field) as well as the constructor parameter name.
<j:jelly xmlns:j="jelly:core" xmlns:f="/lib/form">
  <f:entry title="${%Port}" field="port">
    <f:textbox />
  </f:entry>
  <f:entry title="${%Host}" field="host">
    <f:textbox />
  </f:entry>
</j:jelly>

That odd "${%...}" thing is a marker for internationalization.

Help files

If your plugin has a help-FIELD.html or help-FIELD.jelly in the src/main/resources/path/to/plugin/PluginName directory, Jenkins will put (question) icon and render your help inline. You can add top-level plugin help, e.g. on the job configuration page, by providing help.html or help.jelly in the src/main/resources/path/to/plugin/PluginName directory. If you add help for a field for a job configuration, e.g. a post-build action, add the help-FIELD.html file next to  your config.jelly file, e.g. to the src/main/resources/path/to/plugin/PluginName/YourPostBuildAction directory instead.

Form validation

You can write doCheckFIELD method on your descriptor to add the form validation logic. Your check method would look something like this:

public FormValidation doCheckPort(@QueryParameter String value) {
  if(looksOk(value))  return FormValidation.ok();
  else                return FormValidation.error("There's a problem here");
}

You can also define additional parameters with @QueryParameter to obtain values from other nearby form fields of the specified names. This is useful if your validation depends on values on the other form fields.

Refer to stapler documentation for more details.

Default value

If you want the configuration page to have the initial default value, use @default. The first example shows the literal default value, while the second example shows the programmatically computed default value:

<j:jelly xmlns:j="jelly:core" xmlns:f="/lib/form">
  <f:entry title="${%Port}" field="port">
    <f:textbox default="80" />
  </f:entry>
  <f:entry title="${%Host}" field="host">
    <f:textbox default="${descriptor.defaultHost()}/>
  </f:entry>
</j:jelly>
Other jelly variables

When using the field= mechanism shown above you see where the data really comes from. Jenkins defines these variables which you may use as needed: descriptor and instance, which are the instance of your Descriptor class and instance of the class it describes, respectively (both are available in config.jelly, just descriptor in global.jelly).

Objects without Descriptors

If your plugin uses an object without a Descriptor, such as ComputerListener, follow these steps to include a single text box in the configuration page that will be readable within your plugin class (the one that extends Plugin).

  1. Override the configure(StaplerRequest req, JSONObject formData) method in your plugin class. This method will be called when the user clicks the Save button in the configuration page.
  2. In your configure method, use a method like optInt to extract the value you would like the user to configure. For instance, formData.optInt("port", 3141) will get a port number if the user enters it, or the value 3141 if the user leaves it blank. Store the extracted value in a member of the plugin class.
  3. In your configure method, call save(). This will save the serializable fields of your plugin class.
  4. In the start method of your plugin class, call load(), which will reload these fields from persistent storage on startup.
  5. Create a file called config.jelly with content like this:

    <j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout"
             xmlns:t="/lib/hudson" xmlns:f="/lib/form">
      <f:section title="My Plugin">
        <f:entry title="${%Port}" help="/plugin/ARTIFACT_ID_GOES_HERE/help-projectConfig.html">
          <f:textbox name="port" value="${it.port}"/>
        </f:entry>
      </f:section>
    </j:jelly>
    

    We use it.port to refer to the current value of the parameter port (note that config.jelly for a plugin class uses it instead of instance). In the help attribute, we use the artifactId you specified when you created your plugin (see pom.xml in the root directory of your plugin if you forgot what your artifactId is).

  6. Store the file config.jelly in src/main/resources/path/to/plugin/class/config.jelly. For instance, if your plugin class is hudson.plugins.exampleplugin.MyPlugin, put the file in src/main/resources/hudson/plugins/exampleplugin/MyPlugin/config.jelly.
  7. Create a file called help-projectConfig.html containing an HTML fragment like this:

    <div>
     <p>
      Your help text goes here.
     </p>
    </div>
    

    Store it in src/main/webapp/help-projectConfig.html.

  8. Start Jenkins normally (using mvn hpi:run). Navigate to the configure page and observe your new section. Click the help link and observe your help text.

Useful links