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.