Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to get environment variable in Jenkins Groovy script console?

Tags:

jenkins

groovy

In Jenkins configuration (http://JenkinsURL/configure) within "Global properties" I defined some "Environment variables".

How can I access them in the Groovy Script console (http://JenkinsURL/script)?

I've tried to find appropriate solution (for example the solutions mentioned in: Access to build environment variables from a groovy script in a Jenkins build step (Windows)) but it seems that none of them work for me.

I've tried for example:

System.getenv("myVar")

and

manager.build.getEnvironment(listener).get('myVar') //no manager error

and

import jenkins.model.Jenkins
Jenkins.instance.getProperty('myVar') //No signature of method: hudson.model.Hudson.getProperty() is applicable for argument types: (java.lang.String)

and

import jenkins.model.Jenkins
Jenkins.instance.ParameterValue("DEV_local")
like image 514
matandked Avatar asked Oct 24 '16 09:10

matandked


2 Answers

You can get global properties like this:

import jenkins.model.Jenkins
def envVars = Jenkins.instance.getGlobalNodeProperties()[0].getEnvVars() 
println envVars['myVar']

I referred to the link below, about how to set global properties programatically. https://groups.google.com/forum/#!topic/jenkinsci-users/KgCGuDmED1Q

like image 62
arasio Avatar answered Sep 19 '22 17:09

arasio


It's not as simple as you'd think, like everything in Jenkins. It doesn't appear to expose a simple API to get the final effective environment for the current execution context, at least not to the script console.

The final recipe

Here's a wrapped up version you can use directly, or you can adapt a little to bundle into a vars/ class in your pipeline global library.

import jenkins.model.Jenkins
import hudson.model.Node
import hudson.slaves.EnvironmentVariablesNodeProperty
import hudson.EnvVars

EnvVars getCombinedNodeEnvironment(Node node) {

  /*
   * Start with env-vars defined by the shell the JVM
   * was started from and env-vars set as system properties.
   */
  def combined = new EnvVars(node.toComputer().getEnvironment())

  /*
   * Apply environment variables from jenkins global settings
   * ("Manage Jenkins" -> "Configure System" -> "Global Properties"
   *   -> "Environment Variables")
   */
  combined.overrideExpandingAll(Jenkins.instance.
       getGlobalNodeProperties().
       get(EnvironmentVariablesNodeProperty).
       getEnvVars() ?: new EnvVars())

  /*
   * Apply environment variables from node specific settings
   * ("Manage Jenkins" -> "Manage Nodes and Clouds"
   *     -> {nodename} -> "Configure" -> "Node Properties"
   *     -> "Environment Variables") 
   */
  combined.overrideExpandingAll((node.
       getNodeProperty(EnvironmentVariablesNodeProperty)?.
       getEnvVars()) ?: new EnvVars())

  /*
   * Important: This environment map will NOT contain job-level,
   * or run-level properties, nor anything set via build steps etc.
   */
  return combined
}

EnvVars getCombinedNodeEnvironment(String nodename) {
  if (nodename == 'master' || !nodename)
    return getCombinedNodeEnvironment(Jenkins.instance)
  else
    return getCombinedNodeEnvironment(Jenkins.instance.getNode(nodename))
}

Usage:

getCombinedNodeEnvironment('somenode').expand('$JENKINS_HOME/$USER/$SOME_NODE_VARIABLE')

getCombinedNodeEnvironment('').SOME_ENV_VAR_ON_MASTER

Relevant classes:

  • hudson.model.Node
  • jenkins.model.Jenkins
  • hudson.EnvVars
  • hudson.slaves.EnvironmentVariablesNodeProperty
  • hudson.util.DescribableList

Problems with existing answer

The answer by arasio is a good start, but it's incorrect to assume the envvars properties will be at index 0 of the global properties. That approach also ignores environment variables set locally on specific nodes.

At minimum it should read

jenkins.instance.Jenkins.instance.
   getGlobalNodeProperties().
   get(hudson.slaves.EnvironmentVariablesNodeProperty).
   getEnvVars() 

i.e find the property by class within the DescribableList result, instead of assuming index.

However, that only gets you env vars from the "Environment Variables" list in the global jenkins configuration - it won't show system environment variables, nor will it show node-specific environment variables.

Read on.

Keep it simple if possible

If you're using the Groovy Pipeline, most of the time you can just use the env "variable" (see "Global Variable Reference" in pipeline help), which exposes the unified environment as properties. As noted above this won't work directly from the script console, but the rest of the time it's the appropriate way to do things.

You can also use env.getEnvironment() in Pipeline scripts to get a unified EnvVars instance, which be used for placeholder substitution of env-vars in strings e.g. env.getEnvironment().expand('${FOO} $BAR'). (You'll need script security permission for this, but it's better to put it in a helper in your global library's vars/ instead).

Most of the time this is sufficient.

I only landed up diving into the details of the environment structure because I needed to expand strings containing environment variables as they would be expanded on a different node. That's not a common use case.

Explanation of how it works and setup for examples

That's the final recipe, but how did we get there, where do the different sets of environment variables come from, and why?

For the following code examples assume this common prelude, mostly to save on repetition in each example.

/* Jenkins uses '' for the master node */
nodenames = ['', 'some-other-node-name']

/* Imports used in various examples */
import jenkins.model.Jenkins
import hudson.slaves.EnvironmentVariablesNodeProperty
import hudson.EnvVars

nodes = nodenames.collect { nodename ->
 (!nodename || nodename == 'master') ?
     Jenkins.instance : Jenkins.instance.getNode(nodename)

import static groovy.json.JsonOutput.toJson
import static groovy.json.JsonOutput.prettyPrint

def eachNode(Closure c) {
  nodes.collectEntries { node -> [node.nodeName, c(node, node.nodeName) ] }


def fmtEnv(desc,m) {
  print "\n\n${desc}\n----\n" + m.collect { k, v -> "${k?:'master'}:\n\t${trimKeys(v)}" }.join('\n')
}

def trimKeys(l) {
  if (l == null)
    return l
  if (l in Map)
    l = l.keySet()
  l = l - ['_', 'OLDPWD', 'PWD', 'SSH_CLIENT', 'JAVA_HOME', 'LANG', 'LOGNAME', 'MAIL', 'MANPATH', 'S_COLORS', 'SHLVL', 'XDG_RUNTIME_DIR', 'XDG_SESSION_ID']
  l.sort()
}

nodes now contains the jenkins.model.Jenkins master and a hudson.model.Node worker node.

eachNode produces a Map of node name to abbreviated list of environment variable keys, just to make the examples briefer and easier to read. Don't use it in your code.

To help clarify the results of these examples, I have configured NODE1_SPECIFIC_ENVVAR in the node setup for node1 under "Manage Jenkins" -> "Manage Nodes and Clouds" -> [nodename] -> Configure -> Environment Variables.

On the master node entry in the same place, I have configured MASTER_SPECIFIC_ENVVAR

In "Manage Jenkins" -> "Configure System" -> "Global Properties" -> "Environment Variables" I added "ALL_NODES_ENVVAR".

I didn't bother setting custom env-vars at the JVM level for the node and master.

Different views of the environment

Now, lets explore the environment different ways.

JVM-level environment variables (master)

On master, System.getenv() shows only env vars set when the JVM started or as system properties:

fmtEnv('System.getenv()', ['': System.getenv()])

/* 
master:
    [HOME, JENKINS_HOME, PATH, SHELL, USER]
*/

so nothing configured per-node, globally in jenkins itself, or per-job.

Base environment of nodes

Jenkins exposes the base environment variables set on each node in its API. I think this is the same as System.getEnv() would return when executed on the target node JVM:

fmtEnv('toComputer.getEnvironment()', eachNode() {
  node, name -> node.toComputer().getEnvironment()
})

/*
master:
    [HOME, JENKINS_HOME, PATH, SHELL, USER]
ci-node-qa-fsn1-01:
    [HOME, PATH, SHELL, SSH_CONNECTION, USER]
*/

Note the absence of global or node-specific env-vars set in Jenkins.

Globally configured env vars

fmtEnv('master getGlobalNodeProperties', ['': 
  Jenkins.instance.
     getGlobalNodeProperties().
     get(EnvironmentVariablesNodeProperty).
     getEnvVars()
])

/*
master getGlobalNodeProperties
----
master:
    [ALL_NODES_ENVVAR]
*/

So here we see only the globally configured environment properties, not node-specific properties, system properties, or host environment vars.

Node specific environment variable overrides

fmtEnv('node getNodeProperty', eachNode() {
  node, name -> node.getNodeProperty(EnvironmentVariablesNodeProperty)?.getEnvVars()
})

/*
master:
    [MASTER_SPECIFIC_ENVVAR]
ci-node-qa-fsn1-01:
    [NODE1_SPECIFIC_ENVVAR]
*/

Here we see the properties configured under each node in "manage nodes", but not host env-vars, vars from system properties, standard jenkins job vars, or vars configured in jenkins global config.

Important: getNodeProperty(EnvironmentVariablesNodeProperty) returns null if there are no custom environment variables configured on a node, so you must handle that.

Putting it together

The above shows how to get EnvVars instances for the main sources of environment variables that are meaningful on the script console.

There are other sources when running a job which I don't consider here, such as job properties (EnvInject plugin), automatic env-vars added to all jobs, withEnvironment steps, vars injected by the SCM plugin(s), etc. But they don't make sense for script console tasks.

So how do we get a unified environment?

First, collect up the EnvVars for each relevant piece of the environment:

def node_base_env = node.toComputer().getEnvironment()

def global_env_properties = Jenkins.instance.
     getGlobalNodeProperties().
     get(EnvironmentVariablesNodeProperty).
     getEnvVars()

def node_env_properties = node.getNodeProperty(EnvironmentVariablesNodeProperty)?.getEnvVars() ?: new EnvVars()

def merged = new EnvVars(node_base_env)
merged.overrideExpandingAll(global_env_properties)
merged.overrideExpandingAll(node_env_properties)
merged

/*
master:
    [ALL_NODES_ENVVAR, HOME, JENKINS_HOME, MASTER_SPECIFIC_ENVVAR, PATH, SHELL, USER]
ci-node-qa-fsn1-01:
    [ALL_NODES_ENVVAR, HOME, NODE1_SPECIFIC_ENVVAR, PATH, SHELL, SSH_CONNECTION, USER]
 */

I'm pretty sure that's going to produce the right results. I have not tested the expansion handling, priority override order, or expansion order in detail.

(Note: I removed another example that used EnvironmentExpander).

like image 9
Craig Ringer Avatar answered Jan 01 '70 00:01

Craig Ringer