thclpr
thclpr

Reputation: 5938

Executing parallel stages with declarative pipeline only using last item from list

I'm not sure what i'm doing wrong here, since currently when i try to iterate on a list the creation of the stages seems fine, but when executing the shellscript, the value used is allways the last item of the list:

Working pipeline:

pipeline {
    agent any
    stages {
        stage('set servers') {
            steps {
                script {
                    my_list = ['server1','server-2','server-3']
                }
            }
        }
        stage('Execute then') {
            parallel {
                stage('shouter') {
                    steps {            
                        script {
                            shouter = [:]
                            script {
                                for(i in my_list) {
                                    shouter["${i}"] = {
                                        echo "standupandshout.sh ${i}"
                                    }
                                }
                            }                            
                            parallel shouter
                        }
                    }
                }
            }
        }
    }
}

Output:

enter image description here

Console Output:

Replayed #4
Running in Durability level: MAX_SURVIVABILITY
[Pipeline] node
Running on Jenkins in /var/lib/jenkins/workspace/test
[Pipeline] {
[Pipeline] stage
[Pipeline] { (set servers)
[Pipeline] script
[Pipeline] {
[Pipeline] }
[Pipeline] // script
[Pipeline] }
[Pipeline] // stage
[Pipeline] stage
[Pipeline] { (Execute then)
[Pipeline] parallel
[Pipeline] [shouter] { (Branch: shouter)
[Pipeline] [shouter] stage
[Pipeline] [shouter] { (shouter)
[Pipeline] [shouter] script
[Pipeline] [shouter] {
[Pipeline] [shouter] script
[Pipeline] [shouter] {
[Pipeline] [shouter] }
[Pipeline] [shouter] // script
[Pipeline] [shouter] parallel
[Pipeline] [server1] { (Branch: server1)
[Pipeline] [server-2] { (Branch: server-2)
[Pipeline] [server-3] { (Branch: server-3)
[Pipeline] [server1] echo
[server1] standupandshout.sh server-3
[Pipeline] [server1] }
[Pipeline] [server-2] echo
[server-2] standupandshout.sh server-3
[Pipeline] [server-2] }
[Pipeline] [server-3] echo
[server-3] standupandshout.sh server-3
[Pipeline] [server-3] }
[Pipeline] [shouter] // parallel
[Pipeline] [shouter] }
[Pipeline] [shouter] // script
[Pipeline] [shouter] }
[Pipeline] [shouter] // stage
[Pipeline] [shouter] }
[Pipeline] // parallel
[Pipeline] }
[Pipeline] // stage
[Pipeline] }
[Pipeline] // node
[Pipeline] End of Pipeline
Finished: SUCCESS

Desired output:

[Pipeline] [server1] echo
[server1] standupandshout.sh server-1
[Pipeline] [server1] }
[Pipeline] [server-2] echo
[server-2] standupandshout.sh server-2
[Pipeline] [server-2] }
[Pipeline] [server-3] echo
[server-3] standupandshout.sh server-3

Upvotes: 1

Views: 1214

Answers (1)

Sam
Sam

Reputation: 2882

This is due to groovy closures and when the code they contain gets evaluated. http://blog.freeside.co/2013/03/29/groovy-gotcha-for-loops-and-closure-scope/

When the closures are run the value that is bound to the variable i is the value it had on the final iteration of the loop rather than the iteration where the closure was created. The closures' scopes have references to i and by the time any of the closures are executed i is 5.

Variables local to the loop body do not behave like this, obviously because each closure scope contains a reference to a different variable

This is why your stage name is ok but your value is not.

What’s the solution? Should we always use .each rather than a for loop? Well, I kind of like for loops in many cases and there can be memory utilization differences (don’t take that to mean loops are "better" or "more efficient").

If you simply alias the loop variable and refer to that alias in the closure body all will be well

def fns = []
for (i in (1..5)) {
    def myi = i
    def isq = i * i
    fns << {->
        println "$myi squared is $isq"
    }
}
fns.each { it() }

So this should work:

script {
  shouter = [:]
  for(i in my_list) {
    def val = i
    shouter["${i}"] = {
      echo "standupandshout.sh ${val}"
    }
  }
  parallel shouter
}                            

Upvotes: 2

Related Questions