Home > Back-end >  Variables get overwritten in parallel stages
Variables get overwritten in parallel stages

Time:07-22

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.

CodePudding user response:

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"
  • Related