Issue
In the CI of a project I maintain, it is required to test the code against multiple architecures. Our Jenkins CI then uses a parallel stage to be run on multiple agents.
Emulating architectures is prohibited.
Everything has been working fine until I decided to refactor the pipeline in order to make it clearer. I have introduced definitions of variables inside the script that is looped on each agent.
I have observed strange behaviors, where those variables get overwritten in some task by a parallel one, as if those variables had a global scope ? Is there a way to make those variable completely local to the execution on a single agent ?
Here is a code to reproduce:
def testAgents = ["agent_1", "agent_2"]
def generateTestStage(nodeLabel) {
return {
stage("Tests on ${nodeLabel}") {
node(nodeLabel) {
withCredentials(
[usernamePassword(
credentialsId: "${NODE_NAME}",
usernameVariable: "NODE_USERNAME",
passwordVariable: "NODE_PASSWORD"
)]
){
script {
sh "echo ${nodeLabel}"
ANSIBLE_CREDENTIALS = "ansible_user=$NODE_USERNAME ansible_ssh_pass=$NODE_PASSWORD ansible_sudo_pass=$NODE_PASSWORD"
if (nodeLabel == "agent_1") {
echo "Where am I ? agent_1"
SOME_VARIABLE = "on agent_1"
HOSTS_FILE = "ansible/hosts.template.agent_1.yml"
sh "sleep 3"
} else {
echo "Where am I ? agent_2"
sh "sleep 2"
SOME_VARIABLE = "on agent_2"
HOSTS_FILE = "ansible/hosts.template.agent_2.yml"
}
sh "echo 'BEFORE SLEEP on ${nodeLabel}/${NODE_NAME}: SOME_VARIABLE ${SOME_VARIABLE}'"
sh "echo 'BEFORE SLEEP on ${nodeLabel}/${NODE_NAME}: ANSIBLE_CREDENTIALS ${ANSIBLE_CREDENTIALS}'"
sh "echo 'BEFORE SLEEP on ${nodeLabel}/${NODE_NAME}: HOSTS_FILE ${HOSTS_FILE}'"
sh "echo 'BEFORE SLEEP on ${nodeLabel}/${NODE_NAME}: NODE_USERNAME ${NODE_USERNAME}'"
sh "sleep 10"
sh "echo 'AFTER SLEEP on ${nodeLabel}/${NODE_NAME}: SOME_VARIABLE ${SOME_VARIABLE}'"
sh "echo 'AFTER SLEEP on ${nodeLabel}/${NODE_NAME}: ANSIBLE_CREDENTIALS ${ANSIBLE_CREDENTIALS}'"
sh "echo 'AFTER SLEEP on ${nodeLabel}/${NODE_NAME}: HOSTS_FILE ${HOSTS_FILE}'"
sh "echo 'AFTER SLEEP on ${nodeLabel}/${NODE_NAME}: NODE_USERNAME ${NODE_USERNAME}'"
}
}
}
}
}
}
def parallelTestStages = testAgents.collectEntries {
["${it}" : generateTestStage(it)]
}
pipeline {
environment {
PROJECT = "test"
}
agent none
stages {
stage("Testing on both agents") {
steps {
script {
parallel parallelTestStages
}
}
}
}
}
Here, NODE_USERNAME
gives the correct result, but ANSIBLE_CREDENTIALS
will always give the credentials of the slowest agent, and HOSTS_FILE
and SOME_VARIABLE
will always be displayed with the value set in the agent_2
stage.
Solution
Your issue seems to be with how you are declaring the variables. In order to explicitly tell groovy to create a new variable you need to use def
. So if you do not add def
when creating the variable it may endup in the bindings for the current script and groovy may treat them as global variables. So try changing your variable declaration like below.
def ANSIBLE_CREDENTIALS = "ansible_user=$NODE_USERNAME ansible_ssh_pass=$NODE_PASSWORD ansible_sudo_pass=$NODE_PASSWORD"
Answered By - ycr
Answer Checked By - Senaida (JavaFixing Volunteer)