Thursday, October 28, 2021

Use Groovy to create a Jenkins Parametrized Free-Style Project

Motivation

The Jenkins Java API is a rich and endless source of useful functionality. It is also complex and unless you are a contributor that has built a Jenkins plugin, it is unlikely that you needed to deal with it directly. Nonetheless, even if you are not developing a plugin it is sometimes worth taking a look at how to use it in a simpler way. This simpler way is through Groovy the native scripting language of Jenkins. In my work with life-science Jenkins applications I work primarily with free-style parametrized jobs, and so in this post I will demonstrate how we can use selected parts of the Java API to programmatically create and parametrize a Jenkins job using just the Jenkins Groovy script console.

We can parametrize Jenkins jobs with some standard parameter types (String, Text, Boolean, File etc.) as well as with parameters that are contributed from installed plugins (for example Active Choices).

All of these parameters are defined as Jenkins extension points, and we can use the Jenkins API to discover them.
From the Jenkins API documentation we learn that 'The actual meaning and the purpose of parameters are entirely up to users, so what the concrete parameter implementation is pluggable. Write subclasses in a plugin and put Extension on the descriptor to register them.'

How to find the Jenkins available parameter types

To query for the available types we use the following Groovy code that calls the Jenkins Java API. Note that all of the example Groovy code in these examples can be executed from the Jenkins script console

ep=hudson.ExtensionList.lookup(hudson.model.ParameterDefinition.ParameterDescriptor)
ep.each{
println it.getDisplayName()
}

Executing this script produces the following results on my Jenkins instance:

We essentially lookup in the Jenkins registered extensions, those that are of a specific class. Each parameter type has a corresponding ParameterDescriptor class that describes it. We then use the getDisplayName method to get the human readable names of these parameters (otherwise we get the class name of the plugin contributing the parameter) .


A look at a parametrized job structure

The ParametersDefinitionProperty list


When we examine the configuration file (config.xml) of a parametrized job, we find that all of the job parameters are nested into a ParametersDefinitionProperty that acts as a container for all of the job parameter definitions


From the Jenkins API documentation we learn that '
ParametersDefinitionProperty Keeps a list of the parameters defined for a project.When a job is configured with ParametersDefinitionProperty, which in turns retains ParameterDefinitions, user would have to enter the values for the defined build parameters. 


Programmatic Job parametrization


It is possible to both create and parametrize a job programmatically, without ever opening the job configuration form 
 
create a new job, lets call it 'MyTest01'
myJenkins=jenkins.model.Jenkins.instance
myJenkins.createProject(FreeStyleProject,'MyTest01')
job1=myJenkins.getJob("MyTest01")
Add a ParametersDefinitionProperty to the job
job1.addProperty(new hudson.model.ParametersDefinitionProperty())
Now the job is parametrized!
println 'IsParametized:'+job1.isParameterized()
Result
IsParametized:true

Now we have a parametrized job, but have not defined any parameters yet. 

For simplicity, in this example we have used the null parameter constructor for ParametersDefinitionProperty. However, the constructor allows passing a list of parameter definitions, so if we add a list of ParameterDefinitions we can add the required project parameters!


Programmatic creation and parametrization of a free-style job

A complete example


We will now present a complete step-by-step example where we not only create a parametrized job but also add a couple of parameters. 

To make this example even more interesting, one of the parameters will be an Active Choices parameter, and in the process we'll show how you can add the required Groovy script for the Active Choice parameter configuration.

The following code is a complete example of creating and parametrizing a free-style job programmatically. 


Tasks

  1. Create a new Jenkins job
  2. Construct 2 parameters
    1. A simple String parameter
    2. An Active Choice parameter (with a secure groovy Script)
  3. Construct a ParametersDefinitionProperty with a list of 2 parameters
  4. Add the ParametersDefinitionProperty to the project
  5. Print some diagnostics
The task list labels also match the labels identifying the code fragments in the table below to make it clear what the code fragments do. 

a
jenkins=jenkins.model.Jenkins.instance
jenkins.createProject(FreeStyleProject,'MyTest01')
job1=jenkins.model.Jenkins.instance.getJob("MyTest01")

b.i
pdef1=new StringParameterDefinition('Myname', 'IoannisIsdefaultValue', 'MyNameDescription')
b.ii
sgs=new org.jenkinsci.plugins.scriptsecurity.sandbox.groovy.SecureGroovyScript("""return['A','B','C']""", false, null)
acScript=new org.biouno.unochoice.model.GroovyScript(sgs,null)
pdef2=new org.biouno.unochoice.ChoiceParameter ('AC_01TEST', 'ACDdescription','boo123', acScript, 'PARAMETER_TYPE_SINGLE_SELECT', true,2)

c,d
job1.addProperty(new hudson.model.ParametersDefinitionProperty([pdef1,pdef2]))
e
println 'IsParametized:'+job1.isParameterized()
println job1.properties
job1.properties.each{
  println it.value.class
  println 'Descriptor:'+it.value.getDescriptor()
  if( it.value.class==hudson.model.ParametersDefinitionProperty){
    println 'Job Parameters'
    println it.value.getParameterDefinitionNames()
    it.value.getParameterDefinitionNames().each{pd->
      println it.value.getParameterDefinition(pd).dump()
    }
  }
}
Result
IsParametized:true
[com.sonyericsson.hudson.plugins.metadata.model.MetadataJobProperty$MetaDataJobPropertyDescriptor@5ba29d96:com.sonyericsson.hudson.plugins.metadata.model.MetadataJobProperty@23d5994d, com.sonyericsson.rebuild.RebuildSettings$DescriptorImpl@52e43430:com.sonyericsson.rebuild.RebuildSettings@56c0e80c, hudson.model.ParametersDefinitionProperty$DescriptorImpl@1b2facb8:hudson.model.ParametersDefinitionProperty@6054a6c5]
class com.sonyericsson.hudson.plugins.metadata.model.MetadataJobProperty
Descriptor:com.sonyericsson.hudson.plugins.metadata.model.MetadataJobProperty$MetaDataJobPropertyDescriptor@5ba29d96
class com.sonyericsson.rebuild.RebuildSettings
Descriptor:com.sonyericsson.rebuild.RebuildSettings$DescriptorImpl@52e43430
class hudson.model.ParametersDefinitionProperty
Descriptor:hudson.model.ParametersDefinitionProperty$DescriptorImpl@1b2facb8
Job Parameters
[Myname, AC_01TEST]
<hudson.model.StringParameterDefinition@125d74 defaultValue=IoannisIsdefaultValue trim=false name=Myname description=MyNameDescription>
<org.biouno.unochoice.ChoiceParameter@35c83a1d choiceType=PARAMETER_TYPE_SINGLE_SELECT filterable=true filterLength=2 visibleItemCount=1 script=GroovyScript [script=return['A','B','C'], fallbackScript=] projectName=null randomName=boo123 name=AC_01TEST description=ACDdescription>

Examine the configuration of the newly created project


We will finally review our programmatic creation with the aid of a standard job configuration page. Find the job and click the configure action on the left panel.


Note that the 'Build with Parameters' Form UI is not updated until the project definition is saved to disk. 

Once we 'Apply' or 'Save' the job configuration we can then review the build form UI with the programmatically created parameters.

Summary

We can use Groovy scripts to interact with the Jenkins Java API in many different ways. This is a simple example, but clearly demonstrates the popular concept of  'configuration-as-code' and also helped me to understand some of the Jenkins Java API used with parametrized jobs.

Free-style parametrized jobs are key components to life-science Jenkins applications, and using the Jenkins API allows us to reuse subsets of the job parameters in an ad-hoc way to build reusable interactive components (more details here: imoutsatsos/JENKINS-JOB_PARAM_ASSEMBLER: Utility job for replicating parameters across freestyle jobs (github.com)).