Month: October 2014

Programmatically Set Jenkins Build Variables

The EnvInject Plugin is extremely useful when it comes to injecting build variables into your Jenkins jobs, however it’s not really geared up for using dynamic values. The plugin can take a list of specified properties and values, or it can take a path to a properties file and read in the values. But what if you want to use a number of variables to dynamically construct another? For example, take the version of the code from a properties file in git (1.2), add the Jenkins build number (345), and then add the branch name (develop), resulting in:

FULL_VERSION=1.2.345-develop

How to inject build variables isn’t immediately obvious, Jenkins exposes them all over the place like a flasher and his junk, but just like a flasher, Jenkins protects it’s variables from tampering. I did however find a way to do it:

It basically revolves around the EnvironmentContributingAction interface. Whenever Jenkins processes the “environment” it rattles through all the “Action” classes associated with that Job, and if the class implements the EnvironmentContributingAction interface, Jenkins calls the buildEnvVars method on that class and helpfully passes in the map of build variables as an argument. This map is special however in that it is the actual map and not just a copy which Jenkins normally exposes. Adding to this map object will actually add real build variables to you job to be used later on whenever you need them.

And so to the implementation! All I’ve done is create my own class that implements EnvironmentContributingAction which takes a key and a value in its constructor. When the buildEnvVars method is called it simply adds the key-value pair to the variable map.

The Job class just provides a wrapper to create the instance, add it to the job actions, and then call build.getEnvironment() so that the new variable is actually injected.

import hudson.EnvVars
import hudson.model.*

def build_number = Job.getVariable("BUILD_NUMBER")
def branch_name  = Job.getVariable("GIT_BRANCH")

def build_branch = "${build_number}-${branch_name}"

Job.setVariable("BUILD_BRANCH", build_branch)

class Job {

    static def getVariable(String key) {
        def config = new HashMap()
        def thr = Thread.currentThread()
        def build = thr?.executable
        def envVarsMap = build.parent.builds[0].properties.get("envVars")
        config.putAll(envVarsMap)
        return config.get(key)
    }

    static def setVariable(String key, String value) {
        def build = Thread.currentThread().executable
        def action = new VariableInjectionAction(key, value)
        build.addAction(action)
        build.getEnvironment()
    }
}

class VariableInjectionAction implements EnvironmentContributingAction {

    private String key
    private String value

    public VariableInjectionAction(String key, String value) {
        this.key = key
        this.value = value
    }

    public void buildEnvVars(AbstractBuild build, EnvVars envVars) {

        if (envVars != null && key != null && value != null) {
            envVars.put(key, value);
        }
    }

    public String getDisplayName() {
        return "VariableInjectionAction";
    }

    public String getIconFileName() {
        return null;
    }

    public String getUrlName() {
        return null;
    }
}

This is a simple example that gets the build number and branch name of the current job, concatenates them and sets the result as the value of a new variable called “BUILD_BRANCH”

The ‘Job’ and ‘VariableInjectionAction’ classes provide all the functionality, just copy them and use as you see fit. I hope this helps someone

Advertisements