How to get environment variable in Jenkins Groovy script console? - jenkins

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")

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

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).

You can use System to get environment variables.
def env = System.getenv()
println(env['JENKINS_HOME'])
(See also http://#myJenkHostname#/env-vars.html (where #myJenkHostname# is your Jenkins hostname) for a list of built-in environment variables.)

None of these answers worked for me, due to 'security' issues.
Instead just use the name of the environment variable e.g. the environment variable PATH you can use:
final path = PATH
you can also use
final path = env.PATH
Surprised it was so easy but it is...

Related

Jenkins pipeline agent to use environment variable

Can an agent label make use of environment variable? Something like this:
pipeline {
environment {
SLAVE_NODE = 'MY_COMPUTER_NAME'
}
agent { label $SLAVE_NODE}
...
Since the editor for pipelines is so small, I would like to have the available space (visible by default) to be the "environment" block, so when I copy a jenkins job I just need to adjust a few environment variables used further in the script... I think I tried all the obvious syntax possibilities by now.
Stumbled upon it by try and error... (and found a duplicate here): Add a string parameter to your jenkins job (e.g. jenkinsNode) and use this in your script:
agent { label "${jenkinsNode}" }

How to run pipeline with constant variables between production and non production

I have my jenkins pipelines working and all in source code management, within my pipelines I have some constants which are variables that do not change/rarely change, so these would rarely change and the pipelines requires these values, 90% of it doesnt change but I have some that does change based on the environment type (production/pre-production/test etc)
The problem I have right now is that I would like to take thesame code from non-production to production without having to change things like the file server details, as production/non-production use different file servers, as it stands one has to remember to change the file server when promoting code to production, is it possible to have like a configuration file and the pipeline can read the values from the configuration file, I do not want to change the pipelines or make as little changes as possible when my code moves from non-production to production.
Thanks in advance.
Install the Pipeline Utility Steps plugin and create a properties file each in the repositories/branches for production and non-production respectively. Then read and populate the properties from the file in your pipeline during the build.
Sample server.properties
fileServerUrl=ftp://prod.company.net
fileServerPort=21
// more properties here
Sample Jenkinsfile
pipeline {
agent {
label 'production'
}
stages {
stage('prepare-env') {
script {
def props = readProperties file: 'server.properties'
env.fileServerUrl = props.fileServerUrl
env.fileServerPort = props.fileServerPort
// more properties here
}
}
stage('deploy') {
println("INFO: The file server is ${env.fileServerUrl}:${env.fileServerPort}")
// do stuff here
}
}
}

Jenkinsfile pipeline source an outside file for dynamic parameters?

I have a unique issue. I have one build system that needs one set of params to start a job and another jenkins server that needs to use the same Jenkinsfile but have a different set of options to select from. Think of it as a jenkins server in region 1 and another in region 2 and they need a different set of options
I have this
pipeline {
agent any
environment {
ENV_NAMES = readFile "/etc/env_list"
}
parameters {
// choices are newline separated
choice(choices: "${env.ENV_NAMES}", description: 'What Environment?', name: 'env')
}
}
Right now the choice is coming up as null but i'd love a way to source an outside file to import those.
I have config management that can place a file with data in it.
So server one would get like
env1
env2
env3
And server2 would get
prod1
prod2
prod3

“Inject environment variables” Jenkins 2.0

I recently upgraded to Jenkins 2.0.
I’m trying to add a build step to a jenkins job of "Inject environment variables" along the lines of this SO post, but it’s not showing up as an option.
Is this not feature in Jenkins 2.0 (or has it always been a separate plugin)? Do I have to install another plugin, such as Envinject?
If you are using Jenkins 2.0
you can load the property file (which consists of all required Environment variables along with their corresponding values) and read all the environment variables listed there automatically and inject it into the Jenkins provided env entity.
Here is a method which performs the above stated action.
def loadProperties(path) {
properties = new Properties()
File propertiesFile = new File(path)
properties.load(propertiesFile.newDataInputStream())
Set<Object> keys = properties.keySet();
for(Object k:keys){
String key = (String)k;
String value =(String) properties.getProperty(key)
env."${key}" = "${value}"
}
}
To call this method we need to pass the path of property file as a string variable
For example, in our Jenkins file using groovy script we can call like
path = "${workspace}/pic_env_vars.properties"
loadProperties(path)
Please ask me if you have any doubt

Change number of executors on existing Jenkins node by script

I wan't to able to script and change number of executors on a node(not master) that already exists. Preferably by using groovy but if there is a plugin or CLI command that could do the trick that is also interesting.
Snippet of what I am trying to do:
jenkins.model.Jenkins.instance.nodes.each { node ->
println node.getNumExecutors()
//How do I set the number of executors for a node?
}
I managed it using Slave, which is a subclass of Node.
Below a part of a method I use for that, with target_label and target_executors as parameters
def nodes = nodesByLabel(target_label) // requires plugin "Pipeline Utility Steps"
def j = Jenkins.getInstanceOrNull();
for (int i = 0; i < nodes.size(); ++i) {
def aSlave = (Slave) j.getNode(nodes[i]) // here cast is needed
aSlave.setNumExecutors(target_executors.toInteger())
aSlave.save()
println aSlave.getDisplayName() + "-" + aSlave.getNumExecutors()
}
j.reload()
This isn't currently possible — the numExecutors property of a Jenkins node is read-only.
From JENKINS-23534:
[setNumExecutors] is intentionally private since Jenkins does not offer a way to change the number of executors of a SlaveComputer or Slave once created. Instead, you change the configuration, meaning replacing the existing Slave.
You could run the script below to modify the Jenkins config.xml file… then just "Reload Configuration from Disk" in Manage Jenkins.
This works from the http://jenkins:8080/script console.
import groovy.xml.XmlUtil
// the path to your jenkins config.xml
filePath = '/opt/sites/.jenkins/config.xml'
fileContents = new File(filePath).text
def config = new XmlSlurper().parseText(fileContents)
config.slaves[0].slave.each {
it.numExecutors = 5
}
def writer = new FileWriter(filePath)
XmlUtil.serialize(config, writer)
If you want to reload config automatically you could add:
Jenkins.instance.reload()
It's pretty painful that you can't change the number of executors on a slave through the Jenkins API. You'll have to delete the slave and recreate it to change the number of executors, so you'll need to make sure that there are no builds running on the slave first.
Another option to do this in an automated way is to use the create-slave and delete-slave commands in the Jenkins CLI (go to the /cli link in your Jenkins instance for CLI documentation).

Resources