env
对象可用于设置大多数执行上下文中的环境变量,并且这些变量在所有代理之间都是可见的,当并行节点执行发生时,可能会产生意外的竞争条件。我想做的是配置仅在当前运行代理的执行上下文中设置的变量,而不更改其他代理中 env
对象返回的属性,如下所示:
pipeline {
agent none
stages {
stage('Stage1AgentA') {
agent {
label "AgentA"
}
steps {
setAgentEnvironmentVariable('MY_VAR', 'MyValue')
sh 'echo "MY_VAR: $MY_VAR"' // Should print "MyValue"
echo "env.MY_VAR: ${env.MY_VAR}" // Should print "MyValue"
}
}
stage('Stage2AgentA') {
agent {
label "AgentA"
}
steps {
sh 'echo "MY_VAR: $MY_VAR"' // Should print "MyValue"
echo "env.MY_VAR: ${env.MY_VAR}" // Should print "MyValue"
}
}
stage('StageAgentB') {
agent {
label "AgentB"
}
steps {
sh 'echo "MY_VAR: $MY_VAR"' // Should print ""
echo "env.MY_VAR: ${env.MY_VAR}" // Should print "null"
}
}
}
}
def setAgentEnvironmentVariable(String name, String value)
{
// Any restricted API is admissible
// ..
}
withEnv
基本步骤,但我想使用受限的 Jenkins API 制定更高级的解决方案。
使用 Jenkins 受限 API,可以创建一个实用方法,仅在代理执行上下文中添加变量。该方法可以作为 groovy 代码存储在 Jenkins 共享库中,如下所示:
import hudson.EnvVars;
import hudson.model.Run;
import hudson.model.EnvironmentContributingAction;
import org.jenkinsci.plugins.workflow.cps.EnvActionImpl;
/** Set the given environment variable in the executing agent,
* without altering the global pipeline environment (That is "env.VARNAME").
* The variables set this way are persistent across stages. Throws when
* executed in the controller
*/
def setAgentEnvironmentVariable(String name, String value)
{
// Determine if I'm running in the controller
def nodeName = env.NODE_NAME;
if (nodeName == null || nodeName == 'master' || nodeName == 'built-in')
throw new Exception("The method is not supported when running in a controller built-in node");
def action = currentBuild.rawBuild.getAction(NodeVariablesAction.class);
if (action == null)
{
action = new NodeVariablesAction();
currentBuild.rawBuild.addAction(action);
}
action.AddNodeVariable(nodeName, name, value);
}
class NodeVariablesAction implements EnvironmentContributingAction
{
private HashMap<String, ArrayList<KeyValuePair>> nodeVariables;
private boolean isSetting;
public NodeVariablesAction()
{
nodeVariables = new HashMap<String, ArrayList<KeyValuePair>>();
}
public void AddNodeVariable(String nodeName, String key, String value)
{
def variables = nodeVariables.get(nodeName);
if (variables == null)
{
variables = new ArrayList<KeyValuePair>();
nodeVariables.put(nodeName, variables);
}
variables.add(new KeyValuePair(key, value));
}
@NonCPS
public void buildEnvironment(Run build, EnvVars envVars)
{
if (isSetting)
return;
// Retrieve the global 'env' object (https://www.jenkins.io/doc/book/pipeline/getting-started/#global-variable-reference)
// from the build and get the 'NODE_NAME' from there. This is the only working
// way found, so far. NOTE: Avoid a stack overflow by setting a sentinel that
// will return from this method in subsequent recursions
isSetting = true;
def env = EnvActionImpl.forRun(build);
def currentNodeName = env.getProperty('NODE_NAME');
isSetting = false;
def variables = nodeVariables.get(currentNodeName);
if (variables == null)
return;
for (def variable in variables)
envVars.put(variable.key, variable.value);
}
public String getDisplayName() { return "VariableInjectionAction"; }
public String getIconFileName() { return null; }
public String getUrlName() { return null; }
}
class KeyValuePair
{
public KeyValuePair(String key, String value)
{
this.key = key;
this.value = value;
}
public final String key;
public final String value;
}