Advanced Template Techniques

In this section, we discuss various techniques to create and maintain sophisticated templates.

Navigating around objects in context

Sophisticated templates require accessing information available in the rest of the Jenkins object model. For example, let’s say your “test” job template has an attribute that refers to the corresponding “build” job, and you’d want the test job to change its behaviours based on the configuration of the build job.

This kind of template can be best written as “Groovy-based transformer”, since it provides concise syntax to navigate around the object model and access information. In order to work with the Jenkins object model you need some basic familiarity with that API; Jenkins Javadoc is the starting point.

Instance to Item, Item to Instance

The Instance type refers to a Map-like object that retains the values of attributes for a model, and during the transformation, the current instance is always accessible as the instance variable.

When those instances are from templates that map to jobs in Jenkins (such as job template and folder template), these instances are of the com.cloudbees.hudson.plugins.modeling.impl.entity.EntityInstance class, which defines the item property that lets you access the corresponding hudson.model.Item instance (such as FreeStyleProject object and a Folder object.) So for example, instance.item.isDisabled() would check if the job is disabled or not.

Conversely, those items that are created from their templates have the instance property that allows you to navigate back to the instance. In other words, instance.item.instance==instance. For items that are not created from any templates, this property returns null. Also note that this property is defined in Groovy, and not in Java, and therefore it is only accessible when using Groovy transformers.

Accessing containing folder

One of the useful technique to eliminate redundancy in job templates is to access information of the folder that contains jobs. For example, you might define a folder property that specifies the name of the branch, then templatize jobs inside this folder so that it uses this information to determine where to check out.

To facilitate such an use case, there’s the parent property accessible during a transformation of templates that map to jobs in Jenkins (such as job template and folder template.) This property refers to the instance of ItemGroup, which is typically com.cloudbees.hudson.plugins.folder.Folder instance (that represents the folder that contains the job) or Jenkins (if the job isn’t contained in any folder but a top-level.)

Accessing a folder template’s attributes from a child job template

While the parent property can be used to access the parent Folder itself, if you have a Job template as a child of a Folder template it can be very useful to access the attributes of the Folder template directly from the Job template. For this use case the parentInstance variable is analogous to the instance variable only it allows accessing the attributes of the parent Folder template.

Unlike other techniques mentioned above, to use parentInstance you need know nothing about the Jenkins object model, since you have direct access to the template attributes which are exposed as simple objects.

There are a number of issues to be aware of when using the parentInstance variable:

  • parentInstance will be null when the Job template is instantiated outside of a Folder template
  • While parentInstance will be non-null when the Job template is instantiated as one of the jobs in the "Initial Activities" only the parentInstance.name attribute will have a non-null value until the user saves the Folder template configuration

Therefore, when using the parentInstance variable it is necessary to ensure that it is used defensively, as any NullPointerException thrown when attempting to instantiate the Job template can cause the folder template to fail to instantiate also.

When using the parentInstance variable, it is recommended to use the Groovy transformer for the Job template as that provides both the "Elvis" operator (?:) and the safe navigation operator (?.) both of which combine to allow safer template instantiation, e.g. \${parentInstance?.someAttribute?:"someDefault"} is a safe Groovy expression to return the value of the someAttribute attribute of the parent template instance, falling back to someDefault if the job is either instantiated outside of a Folder template or while the value of someAttribute has not been defined by the user.

If you will be repeating multiple references to the same attribute within the same Job template, it would make sense to create a Computed attribute in the Job template and use a JEXL expression to evaluate the value to use as that makes it easier to refactor the expression, e.g. if you need to change the default value. In the context of JEXL expressions, JEXL also supports the "Elvis" operator and performes safe navigation by default, e.g. \${parentInstance.someAttribute?:"someDefault"} is a safe JEXL expression to return the value of the someAttribute attribute of the parent template instance, falling back to someDefault if the job is either instantiated outside of a Folder template or while the value of someAttribute has not been defined by the user.

Note

The recommendation is to test that your Job template can be instantiated with sensible defaults outside of a Folder template before attempting to add the Job template creation as an initial action for a folder template, as this provides a fast way to verify that your template is coded defensively.

There are a couple of additional points you may want to consider:

  • If you have triggers in the jobs, you may want to structure the template such that the triggers are disabled while the attributes from the parent instance are invalid so as to prevent the job from being triggered prior to the parent folder template being configured correctly
  • The "Elvis" operator only supplies the default value for null values. It does not supply the default for empty strings. If you need the default to apply for both null and an empty string, you will need to write your expression accordingly
  • It is easy for users to accidentally add leading/trailing whitespace to values that they input in the template attributes. If such whitespace could affect your template you will need to structure the template accordingly.