这并不像您想象的那么简单,就像 Jenkins 中的所有内容一样。它似乎没有公开一个简单的 API 来获得当前执行上下文的最终有效环境,至少不会向脚本控制台公开。
最终配方
这是您可以直接使用的打包版本,或者您可以稍作调整以捆绑到管道全局库中的 vars/ 类中。
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))
}
用法:
getCombinedNodeEnvironment('somenode').expand('$JENKINS_HOME/$USER/$SOME_NODE_VARIABLE')
getCombinedNodeEnvironment('').SOME_ENV_VAR_ON_MASTER
相关类:
现有答案的问题
arasio 的回答是一个好的开始,但假设 envvars 属性将位于全局属性的索引 0 处是不正确的。该方法还会忽略在特定节点上本地设置的环境变量。
至少应该是这样的
jenkins.instance.Jenkins.instance.
getGlobalNodeProperties().
get(hudson.slaves.EnvironmentVariablesNodeProperty).
getEnvVars()
即在DescribableList 结果中按类查找属性,而不是假设索引。
但是,只能从全局 jenkins 配置中的“环境变量”列表中获取环境变量 - 它不会显示系统环境变量,也不会显示特定于节点的环境变量。
继续阅读。
尽可能保持简单
如果您使用的是 Groovy 管道,大多数时候您可以使用 env "variable"(请参阅管道帮助中的“全局变量参考”),它将统一环境作为属性公开。如上所述,这不能直接从脚本控制台工作,但在其余时间它是做事的适当方式。
您还可以在 Pipeline 脚本中使用 env.getEnvironment() 来获得统一的 EnvVars 实例,用于替换字符串中的 env-vars 的占位符,例如env.getEnvironment().expand('${FOO} $BAR')。 (为此,您需要脚本安全权限,但最好将其放在全局库的 vars/ 的帮助程序中)。
大多数时候这已经足够了。
我只深入研究了环境结构的细节,因为我需要扩展包含环境变量的字符串因为它们将在不同的节点上扩展。这不是一个常见的用例。
解释其工作原理和设置示例
这是最后的秘诀,但我们是如何做到的,不同的环境变量集从何而来,为什么?
对于以下代码示例,假设这个共同的前奏,主要是为了节省每个示例中的重复。
/* 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 现在包含 jenkins.model.Jenkins 主节点和 hudson.model.Node 工作节点。
eachNode 生成节点名称到环境变量键的缩写列表的映射,只是为了使示例更简洁和更易于阅读。不要在你的代码中使用它。
为了帮助阐明这些示例的结果,我在“管理 Jenkins”->“管理节点和云”-> [节点名]-> 配置-> 环境变量下的 node1 的节点设置中配置了NODE1_SPECIFIC_ENVVAR。
在同一个地方的master节点入口,我已经配置了MASTER_SPECIFIC_ENVVAR
在“管理 Jenkins”->“配置系统”->“全局属性”->“环境变量”中,我添加了“ALL_NODES_ENVVAR”。
我没有费心为节点和主节点在 JVM 级别设置自定义环境变量。
对环境的不同看法
现在,让我们以不同的方式探索环境。
JVM 级环境变量(主)
在 master 上,System.getenv() 仅显示 JVM 启动时设置的环境变量或作为系统属性:
fmtEnv('System.getenv()', ['': System.getenv()])
/*
master:
[HOME, JENKINS_HOME, PATH, SHELL, USER]
*/
所以没有配置每个节点,全局在詹金斯本身,或每个作业。
节点的基础环境
Jenkins 在其 API 中公开了在每个节点上设置的基本环境变量。我认为这与System.getEnv() 在目标节点 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]
*/
请注意 Jenkins 中没有设置全局或特定于节点的 env-vars。
全局配置的环境变量
fmtEnv('master getGlobalNodeProperties', ['':
Jenkins.instance.
getGlobalNodeProperties().
get(EnvironmentVariablesNodeProperty).
getEnvVars()
])
/*
master getGlobalNodeProperties
----
master:
[ALL_NODES_ENVVAR]
*/
所以这里我们只看到全局配置的环境属性,而不是特定于节点的属性、系统属性或主机环境变量。
节点特定的环境变量覆盖
fmtEnv('node getNodeProperty', eachNode() {
node, name -> node.getNodeProperty(EnvironmentVariablesNodeProperty)?.getEnvVars()
})
/*
master:
[MASTER_SPECIFIC_ENVVAR]
ci-node-qa-fsn1-01:
[NODE1_SPECIFIC_ENVVAR]
*/
这里我们看到“管理节点”中每个节点下配置的属性,但没有主机 env-vars、来自系统属性的 vars、标准 jenkins 作业 vars 或 jenkins 全局配置中配置的 vars。
重要提示:如果节点上没有配置自定义环境变量,getNodeProperty(EnvironmentVariablesNodeProperty) 将返回 null,因此您必须处理它。
把它放在一起
上面展示了如何获取EnvVars 实例,用于在脚本控制台上有意义的环境变量的主要来源。
运行作业时还有其他来源,我在这里不考虑,例如作业属性(EnvInject 插件)、添加到所有作业的自动 env-vars、withEnvironment 步骤、由 SCM 插件注入的变量(s ) 等。但它们对脚本控制台任务没有意义。
那么我们如何获得一个统一的环境呢?
首先,为每个相关的环境部分收集EnvVars:
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]
*/
我很确定这会产生正确的结果。扩展处理、优先级覆盖顺序或扩展顺序我没有详细测试过。
(注意:我删除了另一个使用 EnvironmentExpander 的示例)。