Considerations when using Jenkins Groovy

This section highlights all the considerations you need to when using Jenkins Groovy to improve productivity.

Using steps in classes

Using pipeline steps, that is, every execution of a plugin, within classes that are not the main script, require the script steps to be passed to the class and execute the corresponding methods of the steps class. This starts with a simple prinln. Almost all classes in use in our examples perform one or the other steps method. 

groovy.lang.MissingPropertyException

A common scheme is to declare the class and its constructor as follows:

class IspwHelper implements Serializable
{
    def steps
...
    IspwHelper(steps ...) 
    {

        this.steps              = steps
...
    }

Instantiation of these classes will feature as follows:

 ispwHelper  = new   IspwHelper(steps ...)

or

 ispwHelper  = new   IspwHelper(this.steps ...)

For more and detailed information refer to the Jenkins documentation Open link .

Non-serializable classes

As seen above and as discussed in the Jenkins documentation Open link classes must implement the Serializable interface. This is necessary so that pipeline jobs remain 'restartable', i.e. when the Jenkins node fails during execution of a job, the job is able to resume work from the place were it got interrupted. For this to be possible, Jenkins needs to be able to store the state of all instantiated objects.

This bears some implications when it comes to the use of third party classes that are not serializable. Trying to re-use objects of such classes will likely result in a

java.io.NotSerializableException

Example are the JsonSlurper class or the responseBody class. The latter is being returned by a native httpRequest, the former is used to digest the JSON responseBody from an httpRequest. The simplest way we have found, to use these classes without running into `java.io.NotSerializableException, is to de-reference the objects in the code as soon as possible. That way there is no need to store their state across method boundaries, like this:

def ArrayList getAssigmentList(String cesToken, String level)
    {
        def returnList  = []

        def taskIds     = getSetTaskIdList(cesToken, level)

        def response = steps.httpRequest(
            url:                        "${ispwUrl}/ispw/${ispwRuntime}/releases/${ispwRelease}/tasks",
            consoleLogResponseBody:     false, 
            customHeaders:              [[
                                        maskValue:  true,
                                        name:       'authorization',
                                        value:      "${cesToken}"
                                        ]]
            )

        def jsonSlurper = new JsonSlurper()
        def resp        = jsonSlurper.parseText(response.getContent())
        response        = null
        jsonSlurper     = null
        ...

In the example, response receives the result of the httpRequest, jsonSlurper gets instantiated and resp receives the content of response as a list. Once the two objects are not needed they get de-referenced by:

    	response        = null
        jsonSlurper     = null

Using methods in class constructors

Simply put, class constructors in Groovy cannot use methods, be it own internal methods, or instantiating other classes and using their methods. Anything other than 'simple' variable initialization will result in the following:

hudson.remoting.ProxyException: com.cloudbees.groovy.cps.impl.CpsCallableInvocation

Therefore, many of the classes in use here, have an initialize method that performs any additional work necessary after the constructor executed before any of the other methods can be used.

Plugins setting variables

There are certain plugins that within their execution set variables that are exposed to the rest of the script. Two of these plugins being used throughout the examples are  Config File Provider - configFileProvider Open link and Credentials Binding - withCredentials Open link .

The first one allows accessing a file that has been defined using the Config File Provider plugin like the mailList.config file. You pass the fileID and retrieve a variable that contains the (temporary) path to the file. The following snippet variable mailListFilePath will contain that path.

	configFileProvider(
        [
            configFile(
                fileId: 'MailList',
                variable: 'mailListFilePath'
            )
        ]
    )
    {
        File mailConfigFile = new File(mailListFilePath)

        if(!mailConfigFile.exists())
        {
            steps.error "File - ${mailListFilePath} - not found! \n Aborting Pipeline"
        }

        mailListlines = mailConfigFile.readLines()
    }

The second one allows retrieving the information stored in a Jenkins credentials token in those cases when certain plugins require the content in clear text. This is the case for example when using native httpRequest to interact with REST APIs. Some of the example code makes use of the httpRequest and the Code Pipeline API requires the CES credentials to be passed. Other than the Code Pipeline operations plugin the httpRequest cannot use the Jenkins secret text token that stores the CES token. Using the withCredentials, one can use the Jenkins in credentialsID token to retrieve the 'clear text' CES token during runtime (stored in variable cesToken in the example below), without having to expose the token in the code:

    withCredentials(
        [string(credentialsId: "${CES_Token}", variable: 'cesToken')]
    ) 
    {
        response1 = steps.httpRequest(
            url:                    "${ISPW_URL}/ispw/${ISPW_Runtime}/sets/${ISPW_Container}/tasks",
            httpMode:               'GET',
            consoleLogResponseBody: false,
            customHeaders:          [[maskValue: true, name: 'authorization', value: "${cesToken}"]]
        )
    }

Unfortunately, while this works perfectly fine in the main script, trying to use such plugins in classes 'external' to the script class will likely result in exceptions like the following:

groovy.lang.MissingPropertyException: No such property: mailConfigPath for class: com.compuware.devops.util.PipelineConfig

This seems to be  Groovy specific Open link and the only work around so far seems to be to execute these plugins in the main script. That is why the mailList.config may not get read in the PipelineConfig class as one would expect, but rather in the main script.

Was this page helpful? Yes No Submitting... Thank you

Comments