39

My project takes in a version number (separated by '.' or '_'). I tried writing a Groovy script that creates a Jenkins environment variable using only the first two of these numbers:

//Get the version parameter
def env = System.getenv()
def version = env['currentversion']
def m = version =~/\d{1,2}/
env = ['miniVersion':m[0].m[1]]

Am I doing this correctly? Can I even create a new environment variable? Is there a better solution to this?

alex
  • 6,818
  • 9
  • 52
  • 103
themaniac27
  • 1,109
  • 3
  • 15
  • 27
  • 1
    I think the main problem is that by the usual means you can get only a copy of the environment, i.e. modifying it does not propagate 'up'. I've looked at EnvInject plugin code (https://github.com/jenkinsci/envinject-plugin) and it seems to be using some 'backdoor' BuildWrappers mechanism provided by Jenkins in order to do what it does. So your best bet (as @jwernerny proposes) is to use the plugin. – malenkiy_scot May 02 '12 at 16:12
  • I get this error: `Scripts not permitted to use staticMethod java.lang.Thread currentThread. ` – Chris Wolf Aug 01 '23 at 20:09

11 Answers11

54

Jenkins 1.x

The following groovy snippet should pass the version (as you've already supplied), and store it in the job's variables as 'miniVersion'.

import hudson.model.*
  
def env = System.getenv()
def version = env['currentversion']
def m = version =~/\d{1,2}/
def minVerVal = m[0]+"."+m[1]
  
def pa = new ParametersAction([
  new StringParameterValue("miniVersion", minVerVal)
])
  
// add variable to current job
Thread.currentThread().executable.addAction(pa)

The variable will then be accessible from other build steps. e.g.

echo miniVersion=%miniVersion%

Outputs:

miniVersion=12.34

I believe you'll need to use the "System Groovy Script" (on the Master node only) as opposed to the "Groovy Plugin" - https://wiki.jenkins-ci.org/display/JENKINS/Groovy+plugin#Groovyplugin-GroovyScriptvsSystemGroovyScript

Jenkins 2.x

I believe the previous (Jenkins 1.x) behaviour stopped working because of this Security Advisory...

Solution (paraphrased from the Security Advisory)

It's possible to restore the previous behaviour by setting the system property hudson.model.ParametersAction.keepUndefinedParameters to true. This is potentially very unsafe and intended as a short-term workaround only.

java -Dhudson.model.ParametersAction.keepUndefinedParameters=true -jar jenkins.war

To allow specific, known safe parameter names to be passed to builds, set the system property hudson.model.ParametersAction.safeParameters to a comma-separated list of safe parameter names.

e.g.

java -Dhudson.model.ParametersAction.safeParameters=miniVersion,FOO,BAR -jar jenkins.war

And in groovy these two lines should be written this way:

System.setProperty("hudson.model.ParametersAction.keepUndefinedParameters","true");
System.setProperty("hudson.model.ParametersAction.safeParameters","miniVersion,FOO,BAR");  
Eyal Gerber
  • 1,026
  • 10
  • 27
Nick Grealy
  • 24,216
  • 9
  • 104
  • 119
  • Nice I didn't know you could do that. But I found it easier to just write the variable into a file and using the plugin. – themaniac27 Oct 04 '12 at 14:14
  • 1
    If the next step is a "shell" step, you will want to access the variable like: `echo miniVersion=$miniVersion` – ferrants Jun 05 '13 at 15:54
  • 3
    This only work when you run the groovy script in Master node, but not on slave. – samxiao Jun 11 '13 at 01:07
  • 1
    This was a life-saver. I am using this to export an environment variable in the PostBuild Groovy Step. This environment variable is then picked up by another post-build step (email-ext plugin). Thanks! – nonbeing Jan 06 '14 at 16:18
  • This is just want I need, but I am getting `unable to resolve class StringParameterValue` – Slav Aug 15 '14 at 19:01
  • 1
    I believe you'll need to use the "System Groovy Script" instead of the "Groovy Script" - https://wiki.jenkins-ci.org/display/JENKINS/Groovy+plugin#Groovyplugin-GroovyScriptvsSystemGroovyScript – Nick Grealy Aug 18 '14 at 23:21
  • Does this still work in Jenkins 2.x? We used this solution in Jenkins 1.x but it does not set the environment variable after we updated to 2.x – schoenk Aug 12 '16 at 07:36
  • 1
    @schoenk, yep it does still work. You'll need to tell Jenkins to allow the behaviour though (see updated solution). – Nick Grealy Aug 14 '16 at 00:28
  • And in groovy the two property line changes should be written this way: `System.setProperty("hudson.model.ParametersAction.keepUndefinedParameters","true");` `System.setProperty("hudson.model.ParametersAction.safeParameters","miniVersion,FOO,BAR"); ` – Eyal Gerber Jan 25 '22 at 12:30
24

You can also define a variable without the EnvInject Plugin within your Groovy System Script:

import hudson.model.*
def build = Thread.currentThread().executable
def pa = new ParametersAction([
  new StringParameterValue("FOO", "BAR")
])
build.addAction(pa)

Then you can access this variable in the next build step which (for example) is an windows batch command:

@echo off
Setlocal EnableDelayedExpansion
echo FOO=!FOO!

This echo will show you "FOO=BAR".

Regards

stede
  • 355
  • 2
  • 4
  • 9
  • 2
    This is just what I need, but I am getting `unable to resolve class StringParameterValue`, any ideas? – Slav Aug 15 '14 at 19:02
  • @Slav, make sure you've got `import hudson.model.*` -> http://javadoc.jenkins-ci.org/hudson/model/StringParameterValue.html – Nick Grealy Aug 17 '14 at 01:28
  • @NickG I do. Copy-pasted the code from the answer as-is. I am using Jenkins, but the Jenkins javadoc still has `hudson.model` and I even looked inside the jar to see that it's there too. – Slav Aug 18 '14 at 20:58
  • 1
    @Slav you're probably not running the Groovy **system** script, which has the proper libs in the classpath – Rajish Sep 11 '14 at 16:25
  • on running Groovy system script unable to resolve class StringParameterValue – jayant singh Sep 19 '17 at 20:58
14

For me, the following also worked in Jenkins 2 (2.73.3)

Replace

def pa = new ParametersAction([new StringParameterValue("FOO", foo)])
build.addAction(pa)

with

def pa = new ParametersAction([new StringParameterValue("FOO", foo)], ["FOO"])
build.addAction(pa)

ParametersAction seems to have a second constructor which allows to pass in "additionalSafeParameters" https://github.com/jenkinsci/jenkins/blob/master/core/src/main/java/hudson/model/ParametersAction.java

jammann
  • 690
  • 1
  • 6
  • 22
10

As other answers state setting new ParametersAction is the way to inject one or more environment variables, but when a job is already parameterised adding new action won't take effect. Instead you'll see two links to a build parameters pointing to the same set of parameters and the one you wanted to add will be null.

Here is a snippet updating the parameters list in both cases (a parametrised and non-parametrised job):

import hudson.model.*

def build = Thread.currentThread().executable

def env = System.getenv()
def version = env['currentversion']
def m = version =~/\d{1,2}/
def minVerVal = m[0]+"."+m[1]

def newParams = null

def pl = new ArrayList<StringParameterValue>()
pl.add(new StringParameterValue('miniVersion', miniVerVal))

def oldParams = build.getAction(ParametersAction.class)

if(oldParams != null) {
  newParams = oldParams.createUpdated(pl)
  build.actions.remove(oldParams)
} else {
  newParams = new ParametersAction(pl)
}

build.addAction(newParams)
Rajish
  • 6,755
  • 4
  • 34
  • 51
  • This worked for me, with one modification: if the variable you're reading is modified by a plugin, then you need to get that modification done to your env map, like so: `def env = new HashMap<>(System.getenv()) build.environments.each { e -> e.buildEnvVars(env) }` – Magnus Reftel Apr 05 '17 at 10:00
  • I don't think your statement is correct. Even if a job is parameterized, it appears possible to inject new parameters with the `ParametersAction` – Heinz Jun 01 '17 at 14:25
  • 1
    With Jenkins 2.73.3+ you'll want to look at this additional tweak (see ParametersAction on this link): https://stackoverflow.com/questions/10413936/creating-a-jenkins-environment-variable-using-groovy/12721531 – Tony Falabella Aug 23 '18 at 20:31
  • @Heinz It is possible to inject to parameter actions, but this will mess up your build. You will get two sets of parameters. You will also not be able to read the param you set if you have another Groovy step. In other words `build.buildVariableResolver.resolve("miniVersion");` will *not* work. – Nux Jul 24 '19 at 13:58
9

The Jenkins EnvInject Plugin might be able to help you. It allows injecting environment variables into the build environment.

I know it has some ability to do scripting, so it might be able to do what you want. I have only used it to set simple properties (e.g. "LOG_PATH=${WORKSPACE}\logs").

jwernerny
  • 6,978
  • 2
  • 31
  • 32
  • 1
    I think you can use a script build step that would write the new variable you create to a properties file recognized by EnvInject plugin and as the next build step run 'Inject Environment Variables' and read that file. I'd try to key the file name by the build id in order to avoid confusion. – malenkiy_scot May 02 '12 at 16:18
8

After searching around a bit, the best solution in my opinion makes use of hudson.model.EnvironmentContributingAction.

import hudson.model.EnvironmentContributingAction
import hudson.model.AbstractBuild 
import hudson.EnvVars

class BuildVariableInjector {

    def build
    def out

    def BuildVariableInjector(build, out) {
        this.build = build
        this.out = out
    }

    def addBuildEnvironmentVariable(key, value) {
        def action = new VariableInjectionAction(key, value)
        build.addAction(action)
        //Must call this for action to be added
        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;
        }
    }    
}

I use this class in a system groovy script (using the groovy plugin) within a job.

import hudson.model.*
import java.io.File;
import jenkins.model.Jenkins;    

def jenkinsRootDir = build.getEnvVars()["JENKINS_HOME"];
def parent = getClass().getClassLoader()
def loader = new GroovyClassLoader(parent)

def buildVariableInjector = loader.parseClass(new File(jenkinsRootDir + "/userContent/GroovyScripts/BuildVariableInjector.groovy")).newInstance(build, getBinding().out)

def projectBranchDependencies = [] 
//Some logic to set projectBranchDependencies variable

buildVariableInjector.addBuildEnvironmentVariable("projectBranchDependencies", projectBranchDependencies.join(","));

You can then access the projectBranchDependencies variable at any other point in your build, in my case, from an ANT script.

Note: I borrowed / modified the ideas for parts of this implementation from a blog post, but at the time of this posting I was unable to locate the original source in order to give due credit.

TheEllis
  • 1,666
  • 11
  • 18
4

Just had the same issue. Wanted to dynamically trigger parametrized downstream jobs based on the outcome of some groovy scripting.

Unfortunately on our Jenkins it's not possible to run System Groovy scripts. Therefore I had to do a small workaround:

  1. Run groovy script which creates a properties file where the environment variable to be set is specified

    def props = new File("properties.text")
    if (props.text == 'foo=bar') {
        props.text = 'foo=baz'
    } else {
        props.text = 'foo=bar'
    }
    
  2. Use env inject plugin to inject the variable written into this script

    Inject environment variable
    Property file path: properties.text
    

After that I was able to use the variable 'foo' as parameter for the parametrized trigger plugin. Some kind of workaround. But works!

Joerg S
  • 4,730
  • 3
  • 24
  • 43
1

My environment was prior tooling such as Jenkins and was running with batch files (I know, I'm old). So those batch files (and their sub-batch files) are using environment variables. This was my piece of groovy script which injects environment variables. The names and parameters used are dummy ones.

// The process/batch which uses environment variables
def buildLabel = "SomeVersionNr"
def script = "startBuild.bat"
def processBuilder = new ProcessBuilder(script, buildLabel)

//Inject our environment variables
Map<String, String> env = processBuilder.environment()
env.put("ProjectRoot", "someLocation")
env.put("SomeVar", "Some")

Process p = processBuilder.start()
p.waitFor()

Of course if you set Jenkins up from scratch you would probably do it differently and share the variables in another way, or pass parameters around but this might come handy.

Erwin
  • 11
  • 2
0

On my side it only worked this way by replacing an existing parameter.

def artifactNameParam = new StringParameterValue('CopyProjectArtifactName', 'bla bla bla')
build.replaceAction(new ParametersAction(artifactNameParam))

Additionally this script must be run with system groovy.

A groovy must be manually installed on that system and the bin dir of groovy must be added to path. Additionally in the lib folder I had to add jenkins-core.jar.

Then it was possible to modify a parameter in a groovy script and get the modified value in a batch script after to continue work.

Markus
  • 1,360
  • 1
  • 16
  • 22
0

For me the following worked on Jenkins 2.190.1 and was much simpler than some of the other workarounds:

matcher = manager.getLogMatcher('^.*Text we want comes next: (.*)$');

if (matcher.matches()) {
    def myVar = matcher.group(1);
    def envVar = new EnvVars([MY_ENV_VAR: myVar]);
    def newEnv = Environment.create(envVar);
    manager.build.environments.add(0, newEnv);
    // now the matched text from the LogMatcher is passed to an
    // env var we can access at $MY_ENV_VAR in post build steps
}

This was using the Groovy Script plugin with no additional changes to Jenkins.

Kyle Pittman
  • 2,858
  • 1
  • 30
  • 38
0

You can also create a global environment variable for jenkins if want to use wider. Written here longer.

import hudson.EnvVars;
import hudson.slaves.EnvironmentVariablesNodeProperty;
import hudson.slaves.NodeProperty;
import hudson.slaves.NodePropertyDescriptor;
import hudson.util.DescribableList;
import jenkins.model.Jenkins;
public createGlobalEnvironmentVariables(String key, String value){

       Jenkins instance = Jenkins.getInstance();

       DescribableList<NodeProperty<?>, NodePropertyDescriptor> globalNodeProperties = instance.getGlobalNodeProperties();
       List<EnvironmentVariablesNodeProperty> envVarsNodePropertyList = globalNodeProperties.getAll(EnvironmentVariablesNodeProperty.class);

       EnvironmentVariablesNodeProperty newEnvVarsNodeProperty = null;
       EnvVars envVars = null;

       if ( envVarsNodePropertyList == null || envVarsNodePropertyList.size() == 0 ) {
           newEnvVarsNodeProperty = new hudson.slaves.EnvironmentVariablesNodeProperty();
           globalNodeProperties.add(newEnvVarsNodeProperty);
           envVars = newEnvVarsNodeProperty.getEnvVars();
       } else {
           envVars = envVarsNodePropertyList.get(0).getEnvVars();
       }
       envVars.put(key, value)
       instance.save()
}
createGlobalEnvironmentVariables('Var1','Dummy')
matebende
  • 543
  • 1
  • 7
  • 21