Passing loop variable to sh in Jenkinsfile doesn't scope correctly









up vote
1
down vote

favorite












I'm trying to dynamically create jobs in a Jenkinsfile using the following loop. The jobs are created correctly, and the task names show up in Jenkins with the correct name (e.g. ubuntu:bionic).



However, the within each task the sh commands seem to not have access to images as $images[i] is being evaluated to null (e.g. sh ci/script.sh null). So this doesn't seem to be the same as other single-vs-double quote issues with string interpolation.



def images = ["ubuntu:bionic", "ubuntu:xenial"]
def tasks = [:]

for (int i = 0; i < images.size(); i++)
tasks["$images[i]"] =
node
lock("build")
stage('checkout')
checkout scm

stage('test')
sh "ci/script.sh $images[i]"






stage("matrix")
parallel tasks



How can I build these dynamic commands correctly?










share|improve this question

























    up vote
    1
    down vote

    favorite












    I'm trying to dynamically create jobs in a Jenkinsfile using the following loop. The jobs are created correctly, and the task names show up in Jenkins with the correct name (e.g. ubuntu:bionic).



    However, the within each task the sh commands seem to not have access to images as $images[i] is being evaluated to null (e.g. sh ci/script.sh null). So this doesn't seem to be the same as other single-vs-double quote issues with string interpolation.



    def images = ["ubuntu:bionic", "ubuntu:xenial"]
    def tasks = [:]

    for (int i = 0; i < images.size(); i++)
    tasks["$images[i]"] =
    node
    lock("build")
    stage('checkout')
    checkout scm

    stage('test')
    sh "ci/script.sh $images[i]"






    stage("matrix")
    parallel tasks



    How can I build these dynamic commands correctly?










    share|improve this question























      up vote
      1
      down vote

      favorite









      up vote
      1
      down vote

      favorite











      I'm trying to dynamically create jobs in a Jenkinsfile using the following loop. The jobs are created correctly, and the task names show up in Jenkins with the correct name (e.g. ubuntu:bionic).



      However, the within each task the sh commands seem to not have access to images as $images[i] is being evaluated to null (e.g. sh ci/script.sh null). So this doesn't seem to be the same as other single-vs-double quote issues with string interpolation.



      def images = ["ubuntu:bionic", "ubuntu:xenial"]
      def tasks = [:]

      for (int i = 0; i < images.size(); i++)
      tasks["$images[i]"] =
      node
      lock("build")
      stage('checkout')
      checkout scm

      stage('test')
      sh "ci/script.sh $images[i]"






      stage("matrix")
      parallel tasks



      How can I build these dynamic commands correctly?










      share|improve this question













      I'm trying to dynamically create jobs in a Jenkinsfile using the following loop. The jobs are created correctly, and the task names show up in Jenkins with the correct name (e.g. ubuntu:bionic).



      However, the within each task the sh commands seem to not have access to images as $images[i] is being evaluated to null (e.g. sh ci/script.sh null). So this doesn't seem to be the same as other single-vs-double quote issues with string interpolation.



      def images = ["ubuntu:bionic", "ubuntu:xenial"]
      def tasks = [:]

      for (int i = 0; i < images.size(); i++)
      tasks["$images[i]"] =
      node
      lock("build")
      stage('checkout')
      checkout scm

      stage('test')
      sh "ci/script.sh $images[i]"






      stage("matrix")
      parallel tasks



      How can I build these dynamic commands correctly?







      jenkins groovy jenkins-pipeline






      share|improve this question













      share|improve this question











      share|improve this question




      share|improve this question










      asked Nov 10 at 3:53









      Noah Watkins

      3,29822140




      3,29822140






















          1 Answer
          1






          active

          oldest

          votes

















          up vote
          2
          down vote



          accepted










          A closure you create in the loop and assign to tasks["$images[i]"] is evaluated lazily and it seems like it processes images.getAt(i) with the current i value, which in this case is equal to 2 in both cases. Take a look at following example with some additional printing of current i state (I've skipped scm checkout int this short example):



          def images = ["ubuntu:bionic", "ubuntu:xenial"]
          def tasks = [:]

          for (int i = 0; i < images.size(); i++)
          println "Using i = $i" // <- first print
          tasks["$images[i]"] =
          node
          lock("build")
          stage('checkout')
          echo "ok"

          stage('test')
          println "Print i inside stage = $i" // <- second print
          echo "Echo i inside stage = $i" // <- third print
          sh "ci/script.sh $images[i]".toString()






          stage("matrix")
          parallel tasks



          When we run it we will see something like this in the console:



          [Pipeline] echo
          Using i = 0
          [Pipeline] echo
          Using i = 1
          [Pipeline] stage
          [Pipeline] (matrix)
          [Pipeline] parallel
          [Pipeline] [ubuntu:bionic] (Branch: ubuntu:bionic)
          [Pipeline] [ubuntu:xenial] (Branch: ubuntu:xenial)
          [Pipeline] [ubuntu:bionic] node
          [ubuntu:bionic] Running on Jenkins in /var/jenkins_home/workspace/test-pipeline
          [Pipeline] [ubuntu:xenial] node
          [ubuntu:xenial] Running on Jenkins in /var/jenkins_home/workspace/test-pipeline@2
          [Pipeline] [ubuntu:bionic]
          [Pipeline] [ubuntu:xenial]
          [Pipeline] [ubuntu:bionic] lock
          [ubuntu:bionic] Trying to acquire lock on [build]
          [ubuntu:bionic] Lock acquired on [build]
          [Pipeline] [ubuntu:bionic]
          [Pipeline] [ubuntu:xenial] lock
          [ubuntu:xenial] Trying to acquire lock on [build]
          [ubuntu:xenial] Found 0 available resource(s). Waiting for correct amount: 1.
          [ubuntu:xenial] [build] is locked, waiting...
          [Pipeline] [ubuntu:bionic] stage
          [Pipeline] [ubuntu:bionic] (checkout)
          [Pipeline] [ubuntu:bionic] echo
          [ubuntu:bionic] ok
          [Pipeline] [ubuntu:bionic]
          [Pipeline] [ubuntu:bionic] // stage
          [Pipeline] [ubuntu:bionic] stage
          [Pipeline] [ubuntu:bionic] (test)
          [Pipeline] [ubuntu:bionic] echo
          [ubuntu:bionic] Print i inside stage = 2
          [Pipeline] [ubuntu:bionic] echo
          [ubuntu:bionic] Echo i inside stage = 2
          [Pipeline] [ubuntu:bionic] sh
          [ubuntu:bionic] [test-pipeline] Running shell script
          [ubuntu:bionic] + ci/script.sh null
          [ubuntu:bionic] /var/jenkins_home/workspace/test-pipeline@tmp/durable-998289d1/script.sh: 2: /var/jenkins_home/workspace/test-pipeline@tmp/durable-998289d1/script.sh: ci/script.sh: not found
          [Pipeline] [ubuntu:bionic]
          [Pipeline] [ubuntu:bionic] // stage
          [ubuntu:xenial] Lock acquired on [build]
          [Pipeline] [ubuntu:bionic]
          [ubuntu:bionic] Lock released on resource [build]
          [Pipeline] [ubuntu:xenial]
          [Pipeline] [ubuntu:bionic] // lock
          [Pipeline] [ubuntu:bionic]
          [Pipeline] [ubuntu:xenial] stage
          [Pipeline] [ubuntu:xenial] (checkout)
          [Pipeline] [ubuntu:bionic] // node
          [Pipeline] [ubuntu:bionic]
          [ubuntu:bionic] Failed in branch ubuntu:bionic
          [Pipeline] [ubuntu:xenial] echo
          [ubuntu:xenial] ok
          [Pipeline] [ubuntu:xenial]
          [Pipeline] [ubuntu:xenial] // stage
          [Pipeline] [ubuntu:xenial] stage
          [Pipeline] [ubuntu:xenial] (test)
          [Pipeline] [ubuntu:xenial] echo
          [ubuntu:xenial] Print i inside stage = 2
          [Pipeline] [ubuntu:xenial] echo
          [ubuntu:xenial] Echo i inside stage = 2
          [Pipeline] [ubuntu:xenial] sh
          [ubuntu:xenial] [test-pipeline@2] Running shell script
          [ubuntu:xenial] + ci/script.sh null
          [ubuntu:xenial] /var/jenkins_home/workspace/test-pipeline@2@tmp/durable-b1807fa2/script.sh: 2: /var/jenkins_home/workspace/test-pipeline@2@tmp/durable-b1807fa2/script.sh: ci/script.sh: not found
          [Pipeline] [ubuntu:xenial]
          [Pipeline] [ubuntu:xenial] // stage
          [Pipeline] [ubuntu:xenial]
          [ubuntu:xenial] Lock released on resource [build]
          [Pipeline] [ubuntu:xenial] // lock
          [Pipeline] [ubuntu:xenial]
          [Pipeline] [ubuntu:xenial] // node
          [Pipeline] [ubuntu:xenial]
          [ubuntu:xenial] Failed in branch ubuntu:xenial
          [Pipeline] // parallel
          [Pipeline]
          [Pipeline] // stage
          [Pipeline] End of Pipeline
          ERROR: script returned exit code 127
          Finished: FAILURE


          I have used println inside the stage on purpose, because this is not a Jenkins pipeline step, but simple Groovy method. As you can see it gets evaluated when the parallel execution happens in matrix stage. Each Groovy closure is associated with bindings - its local state of variables. It looks like it contains images and i bindings and it tracks changes of the state of i variable. That is why it tries to access images[2] when evaluating sh step.



          Solution



          There is a simple solution to this problem. You can replace for-loop with for-each. Consider following example:



          def images = ["ubuntu:bionic", "ubuntu:xenial"]
          def tasks = [:]

          images.each image ->
          tasks["$image"] =
          node
          lock("build")
          stage('checkout')
          checkout scm

          stage('test')
          sh "ci/script.sh $image"






          stage("matrix")
          parallel tasks



          Console output:



          [ubuntu:bionic] [test-pipeline] Running shell script
          [ubuntu:bionic] + ci/script.sh ubuntu:bionic
          [ubuntu:xenial] [test-pipeline@2] Running shell script
          [ubuntu:xenial] + ci/script.sh ubuntu:xenial



          You can find explanation of global scope of i variable in Pipeline - Parallel execution of tasks article on CloudBees:




          Note: Variables define outside the for block are not local, but global to the script. Testing the option 2, you will notice that variable i prints always value 4, whereas index increases from 0 to 3 and branch from 1 to 4.







          share|improve this answer






















            Your Answer






            StackExchange.ifUsing("editor", function ()
            StackExchange.using("externalEditor", function ()
            StackExchange.using("snippets", function ()
            StackExchange.snippets.init();
            );
            );
            , "code-snippets");

            StackExchange.ready(function()
            var channelOptions =
            tags: "".split(" "),
            id: "1"
            ;
            initTagRenderer("".split(" "), "".split(" "), channelOptions);

            StackExchange.using("externalEditor", function()
            // Have to fire editor after snippets, if snippets enabled
            if (StackExchange.settings.snippets.snippetsEnabled)
            StackExchange.using("snippets", function()
            createEditor();
            );

            else
            createEditor();

            );

            function createEditor()
            StackExchange.prepareEditor(
            heartbeatType: 'answer',
            convertImagesToLinks: true,
            noModals: true,
            showLowRepImageUploadWarning: true,
            reputationToPostImages: 10,
            bindNavPrevention: true,
            postfix: "",
            imageUploader:
            brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
            contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
            allowUrls: true
            ,
            onDemand: true,
            discardSelector: ".discard-answer"
            ,immediatelyShowMarkdownHelp:true
            );



            );













            draft saved

            draft discarded


















            StackExchange.ready(
            function ()
            StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53235862%2fpassing-loop-variable-to-sh-in-jenkinsfile-doesnt-scope-correctly%23new-answer', 'question_page');

            );

            Post as a guest















            Required, but never shown

























            1 Answer
            1






            active

            oldest

            votes








            1 Answer
            1






            active

            oldest

            votes









            active

            oldest

            votes






            active

            oldest

            votes








            up vote
            2
            down vote



            accepted










            A closure you create in the loop and assign to tasks["$images[i]"] is evaluated lazily and it seems like it processes images.getAt(i) with the current i value, which in this case is equal to 2 in both cases. Take a look at following example with some additional printing of current i state (I've skipped scm checkout int this short example):



            def images = ["ubuntu:bionic", "ubuntu:xenial"]
            def tasks = [:]

            for (int i = 0; i < images.size(); i++)
            println "Using i = $i" // <- first print
            tasks["$images[i]"] =
            node
            lock("build")
            stage('checkout')
            echo "ok"

            stage('test')
            println "Print i inside stage = $i" // <- second print
            echo "Echo i inside stage = $i" // <- third print
            sh "ci/script.sh $images[i]".toString()






            stage("matrix")
            parallel tasks



            When we run it we will see something like this in the console:



            [Pipeline] echo
            Using i = 0
            [Pipeline] echo
            Using i = 1
            [Pipeline] stage
            [Pipeline] (matrix)
            [Pipeline] parallel
            [Pipeline] [ubuntu:bionic] (Branch: ubuntu:bionic)
            [Pipeline] [ubuntu:xenial] (Branch: ubuntu:xenial)
            [Pipeline] [ubuntu:bionic] node
            [ubuntu:bionic] Running on Jenkins in /var/jenkins_home/workspace/test-pipeline
            [Pipeline] [ubuntu:xenial] node
            [ubuntu:xenial] Running on Jenkins in /var/jenkins_home/workspace/test-pipeline@2
            [Pipeline] [ubuntu:bionic]
            [Pipeline] [ubuntu:xenial]
            [Pipeline] [ubuntu:bionic] lock
            [ubuntu:bionic] Trying to acquire lock on [build]
            [ubuntu:bionic] Lock acquired on [build]
            [Pipeline] [ubuntu:bionic]
            [Pipeline] [ubuntu:xenial] lock
            [ubuntu:xenial] Trying to acquire lock on [build]
            [ubuntu:xenial] Found 0 available resource(s). Waiting for correct amount: 1.
            [ubuntu:xenial] [build] is locked, waiting...
            [Pipeline] [ubuntu:bionic] stage
            [Pipeline] [ubuntu:bionic] (checkout)
            [Pipeline] [ubuntu:bionic] echo
            [ubuntu:bionic] ok
            [Pipeline] [ubuntu:bionic]
            [Pipeline] [ubuntu:bionic] // stage
            [Pipeline] [ubuntu:bionic] stage
            [Pipeline] [ubuntu:bionic] (test)
            [Pipeline] [ubuntu:bionic] echo
            [ubuntu:bionic] Print i inside stage = 2
            [Pipeline] [ubuntu:bionic] echo
            [ubuntu:bionic] Echo i inside stage = 2
            [Pipeline] [ubuntu:bionic] sh
            [ubuntu:bionic] [test-pipeline] Running shell script
            [ubuntu:bionic] + ci/script.sh null
            [ubuntu:bionic] /var/jenkins_home/workspace/test-pipeline@tmp/durable-998289d1/script.sh: 2: /var/jenkins_home/workspace/test-pipeline@tmp/durable-998289d1/script.sh: ci/script.sh: not found
            [Pipeline] [ubuntu:bionic]
            [Pipeline] [ubuntu:bionic] // stage
            [ubuntu:xenial] Lock acquired on [build]
            [Pipeline] [ubuntu:bionic]
            [ubuntu:bionic] Lock released on resource [build]
            [Pipeline] [ubuntu:xenial]
            [Pipeline] [ubuntu:bionic] // lock
            [Pipeline] [ubuntu:bionic]
            [Pipeline] [ubuntu:xenial] stage
            [Pipeline] [ubuntu:xenial] (checkout)
            [Pipeline] [ubuntu:bionic] // node
            [Pipeline] [ubuntu:bionic]
            [ubuntu:bionic] Failed in branch ubuntu:bionic
            [Pipeline] [ubuntu:xenial] echo
            [ubuntu:xenial] ok
            [Pipeline] [ubuntu:xenial]
            [Pipeline] [ubuntu:xenial] // stage
            [Pipeline] [ubuntu:xenial] stage
            [Pipeline] [ubuntu:xenial] (test)
            [Pipeline] [ubuntu:xenial] echo
            [ubuntu:xenial] Print i inside stage = 2
            [Pipeline] [ubuntu:xenial] echo
            [ubuntu:xenial] Echo i inside stage = 2
            [Pipeline] [ubuntu:xenial] sh
            [ubuntu:xenial] [test-pipeline@2] Running shell script
            [ubuntu:xenial] + ci/script.sh null
            [ubuntu:xenial] /var/jenkins_home/workspace/test-pipeline@2@tmp/durable-b1807fa2/script.sh: 2: /var/jenkins_home/workspace/test-pipeline@2@tmp/durable-b1807fa2/script.sh: ci/script.sh: not found
            [Pipeline] [ubuntu:xenial]
            [Pipeline] [ubuntu:xenial] // stage
            [Pipeline] [ubuntu:xenial]
            [ubuntu:xenial] Lock released on resource [build]
            [Pipeline] [ubuntu:xenial] // lock
            [Pipeline] [ubuntu:xenial]
            [Pipeline] [ubuntu:xenial] // node
            [Pipeline] [ubuntu:xenial]
            [ubuntu:xenial] Failed in branch ubuntu:xenial
            [Pipeline] // parallel
            [Pipeline]
            [Pipeline] // stage
            [Pipeline] End of Pipeline
            ERROR: script returned exit code 127
            Finished: FAILURE


            I have used println inside the stage on purpose, because this is not a Jenkins pipeline step, but simple Groovy method. As you can see it gets evaluated when the parallel execution happens in matrix stage. Each Groovy closure is associated with bindings - its local state of variables. It looks like it contains images and i bindings and it tracks changes of the state of i variable. That is why it tries to access images[2] when evaluating sh step.



            Solution



            There is a simple solution to this problem. You can replace for-loop with for-each. Consider following example:



            def images = ["ubuntu:bionic", "ubuntu:xenial"]
            def tasks = [:]

            images.each image ->
            tasks["$image"] =
            node
            lock("build")
            stage('checkout')
            checkout scm

            stage('test')
            sh "ci/script.sh $image"






            stage("matrix")
            parallel tasks



            Console output:



            [ubuntu:bionic] [test-pipeline] Running shell script
            [ubuntu:bionic] + ci/script.sh ubuntu:bionic
            [ubuntu:xenial] [test-pipeline@2] Running shell script
            [ubuntu:xenial] + ci/script.sh ubuntu:xenial



            You can find explanation of global scope of i variable in Pipeline - Parallel execution of tasks article on CloudBees:




            Note: Variables define outside the for block are not local, but global to the script. Testing the option 2, you will notice that variable i prints always value 4, whereas index increases from 0 to 3 and branch from 1 to 4.







            share|improve this answer


























              up vote
              2
              down vote



              accepted










              A closure you create in the loop and assign to tasks["$images[i]"] is evaluated lazily and it seems like it processes images.getAt(i) with the current i value, which in this case is equal to 2 in both cases. Take a look at following example with some additional printing of current i state (I've skipped scm checkout int this short example):



              def images = ["ubuntu:bionic", "ubuntu:xenial"]
              def tasks = [:]

              for (int i = 0; i < images.size(); i++)
              println "Using i = $i" // <- first print
              tasks["$images[i]"] =
              node
              lock("build")
              stage('checkout')
              echo "ok"

              stage('test')
              println "Print i inside stage = $i" // <- second print
              echo "Echo i inside stage = $i" // <- third print
              sh "ci/script.sh $images[i]".toString()






              stage("matrix")
              parallel tasks



              When we run it we will see something like this in the console:



              [Pipeline] echo
              Using i = 0
              [Pipeline] echo
              Using i = 1
              [Pipeline] stage
              [Pipeline] (matrix)
              [Pipeline] parallel
              [Pipeline] [ubuntu:bionic] (Branch: ubuntu:bionic)
              [Pipeline] [ubuntu:xenial] (Branch: ubuntu:xenial)
              [Pipeline] [ubuntu:bionic] node
              [ubuntu:bionic] Running on Jenkins in /var/jenkins_home/workspace/test-pipeline
              [Pipeline] [ubuntu:xenial] node
              [ubuntu:xenial] Running on Jenkins in /var/jenkins_home/workspace/test-pipeline@2
              [Pipeline] [ubuntu:bionic]
              [Pipeline] [ubuntu:xenial]
              [Pipeline] [ubuntu:bionic] lock
              [ubuntu:bionic] Trying to acquire lock on [build]
              [ubuntu:bionic] Lock acquired on [build]
              [Pipeline] [ubuntu:bionic]
              [Pipeline] [ubuntu:xenial] lock
              [ubuntu:xenial] Trying to acquire lock on [build]
              [ubuntu:xenial] Found 0 available resource(s). Waiting for correct amount: 1.
              [ubuntu:xenial] [build] is locked, waiting...
              [Pipeline] [ubuntu:bionic] stage
              [Pipeline] [ubuntu:bionic] (checkout)
              [Pipeline] [ubuntu:bionic] echo
              [ubuntu:bionic] ok
              [Pipeline] [ubuntu:bionic]
              [Pipeline] [ubuntu:bionic] // stage
              [Pipeline] [ubuntu:bionic] stage
              [Pipeline] [ubuntu:bionic] (test)
              [Pipeline] [ubuntu:bionic] echo
              [ubuntu:bionic] Print i inside stage = 2
              [Pipeline] [ubuntu:bionic] echo
              [ubuntu:bionic] Echo i inside stage = 2
              [Pipeline] [ubuntu:bionic] sh
              [ubuntu:bionic] [test-pipeline] Running shell script
              [ubuntu:bionic] + ci/script.sh null
              [ubuntu:bionic] /var/jenkins_home/workspace/test-pipeline@tmp/durable-998289d1/script.sh: 2: /var/jenkins_home/workspace/test-pipeline@tmp/durable-998289d1/script.sh: ci/script.sh: not found
              [Pipeline] [ubuntu:bionic]
              [Pipeline] [ubuntu:bionic] // stage
              [ubuntu:xenial] Lock acquired on [build]
              [Pipeline] [ubuntu:bionic]
              [ubuntu:bionic] Lock released on resource [build]
              [Pipeline] [ubuntu:xenial]
              [Pipeline] [ubuntu:bionic] // lock
              [Pipeline] [ubuntu:bionic]
              [Pipeline] [ubuntu:xenial] stage
              [Pipeline] [ubuntu:xenial] (checkout)
              [Pipeline] [ubuntu:bionic] // node
              [Pipeline] [ubuntu:bionic]
              [ubuntu:bionic] Failed in branch ubuntu:bionic
              [Pipeline] [ubuntu:xenial] echo
              [ubuntu:xenial] ok
              [Pipeline] [ubuntu:xenial]
              [Pipeline] [ubuntu:xenial] // stage
              [Pipeline] [ubuntu:xenial] stage
              [Pipeline] [ubuntu:xenial] (test)
              [Pipeline] [ubuntu:xenial] echo
              [ubuntu:xenial] Print i inside stage = 2
              [Pipeline] [ubuntu:xenial] echo
              [ubuntu:xenial] Echo i inside stage = 2
              [Pipeline] [ubuntu:xenial] sh
              [ubuntu:xenial] [test-pipeline@2] Running shell script
              [ubuntu:xenial] + ci/script.sh null
              [ubuntu:xenial] /var/jenkins_home/workspace/test-pipeline@2@tmp/durable-b1807fa2/script.sh: 2: /var/jenkins_home/workspace/test-pipeline@2@tmp/durable-b1807fa2/script.sh: ci/script.sh: not found
              [Pipeline] [ubuntu:xenial]
              [Pipeline] [ubuntu:xenial] // stage
              [Pipeline] [ubuntu:xenial]
              [ubuntu:xenial] Lock released on resource [build]
              [Pipeline] [ubuntu:xenial] // lock
              [Pipeline] [ubuntu:xenial]
              [Pipeline] [ubuntu:xenial] // node
              [Pipeline] [ubuntu:xenial]
              [ubuntu:xenial] Failed in branch ubuntu:xenial
              [Pipeline] // parallel
              [Pipeline]
              [Pipeline] // stage
              [Pipeline] End of Pipeline
              ERROR: script returned exit code 127
              Finished: FAILURE


              I have used println inside the stage on purpose, because this is not a Jenkins pipeline step, but simple Groovy method. As you can see it gets evaluated when the parallel execution happens in matrix stage. Each Groovy closure is associated with bindings - its local state of variables. It looks like it contains images and i bindings and it tracks changes of the state of i variable. That is why it tries to access images[2] when evaluating sh step.



              Solution



              There is a simple solution to this problem. You can replace for-loop with for-each. Consider following example:



              def images = ["ubuntu:bionic", "ubuntu:xenial"]
              def tasks = [:]

              images.each image ->
              tasks["$image"] =
              node
              lock("build")
              stage('checkout')
              checkout scm

              stage('test')
              sh "ci/script.sh $image"






              stage("matrix")
              parallel tasks



              Console output:



              [ubuntu:bionic] [test-pipeline] Running shell script
              [ubuntu:bionic] + ci/script.sh ubuntu:bionic
              [ubuntu:xenial] [test-pipeline@2] Running shell script
              [ubuntu:xenial] + ci/script.sh ubuntu:xenial



              You can find explanation of global scope of i variable in Pipeline - Parallel execution of tasks article on CloudBees:




              Note: Variables define outside the for block are not local, but global to the script. Testing the option 2, you will notice that variable i prints always value 4, whereas index increases from 0 to 3 and branch from 1 to 4.







              share|improve this answer
























                up vote
                2
                down vote



                accepted







                up vote
                2
                down vote



                accepted






                A closure you create in the loop and assign to tasks["$images[i]"] is evaluated lazily and it seems like it processes images.getAt(i) with the current i value, which in this case is equal to 2 in both cases. Take a look at following example with some additional printing of current i state (I've skipped scm checkout int this short example):



                def images = ["ubuntu:bionic", "ubuntu:xenial"]
                def tasks = [:]

                for (int i = 0; i < images.size(); i++)
                println "Using i = $i" // <- first print
                tasks["$images[i]"] =
                node
                lock("build")
                stage('checkout')
                echo "ok"

                stage('test')
                println "Print i inside stage = $i" // <- second print
                echo "Echo i inside stage = $i" // <- third print
                sh "ci/script.sh $images[i]".toString()






                stage("matrix")
                parallel tasks



                When we run it we will see something like this in the console:



                [Pipeline] echo
                Using i = 0
                [Pipeline] echo
                Using i = 1
                [Pipeline] stage
                [Pipeline] (matrix)
                [Pipeline] parallel
                [Pipeline] [ubuntu:bionic] (Branch: ubuntu:bionic)
                [Pipeline] [ubuntu:xenial] (Branch: ubuntu:xenial)
                [Pipeline] [ubuntu:bionic] node
                [ubuntu:bionic] Running on Jenkins in /var/jenkins_home/workspace/test-pipeline
                [Pipeline] [ubuntu:xenial] node
                [ubuntu:xenial] Running on Jenkins in /var/jenkins_home/workspace/test-pipeline@2
                [Pipeline] [ubuntu:bionic]
                [Pipeline] [ubuntu:xenial]
                [Pipeline] [ubuntu:bionic] lock
                [ubuntu:bionic] Trying to acquire lock on [build]
                [ubuntu:bionic] Lock acquired on [build]
                [Pipeline] [ubuntu:bionic]
                [Pipeline] [ubuntu:xenial] lock
                [ubuntu:xenial] Trying to acquire lock on [build]
                [ubuntu:xenial] Found 0 available resource(s). Waiting for correct amount: 1.
                [ubuntu:xenial] [build] is locked, waiting...
                [Pipeline] [ubuntu:bionic] stage
                [Pipeline] [ubuntu:bionic] (checkout)
                [Pipeline] [ubuntu:bionic] echo
                [ubuntu:bionic] ok
                [Pipeline] [ubuntu:bionic]
                [Pipeline] [ubuntu:bionic] // stage
                [Pipeline] [ubuntu:bionic] stage
                [Pipeline] [ubuntu:bionic] (test)
                [Pipeline] [ubuntu:bionic] echo
                [ubuntu:bionic] Print i inside stage = 2
                [Pipeline] [ubuntu:bionic] echo
                [ubuntu:bionic] Echo i inside stage = 2
                [Pipeline] [ubuntu:bionic] sh
                [ubuntu:bionic] [test-pipeline] Running shell script
                [ubuntu:bionic] + ci/script.sh null
                [ubuntu:bionic] /var/jenkins_home/workspace/test-pipeline@tmp/durable-998289d1/script.sh: 2: /var/jenkins_home/workspace/test-pipeline@tmp/durable-998289d1/script.sh: ci/script.sh: not found
                [Pipeline] [ubuntu:bionic]
                [Pipeline] [ubuntu:bionic] // stage
                [ubuntu:xenial] Lock acquired on [build]
                [Pipeline] [ubuntu:bionic]
                [ubuntu:bionic] Lock released on resource [build]
                [Pipeline] [ubuntu:xenial]
                [Pipeline] [ubuntu:bionic] // lock
                [Pipeline] [ubuntu:bionic]
                [Pipeline] [ubuntu:xenial] stage
                [Pipeline] [ubuntu:xenial] (checkout)
                [Pipeline] [ubuntu:bionic] // node
                [Pipeline] [ubuntu:bionic]
                [ubuntu:bionic] Failed in branch ubuntu:bionic
                [Pipeline] [ubuntu:xenial] echo
                [ubuntu:xenial] ok
                [Pipeline] [ubuntu:xenial]
                [Pipeline] [ubuntu:xenial] // stage
                [Pipeline] [ubuntu:xenial] stage
                [Pipeline] [ubuntu:xenial] (test)
                [Pipeline] [ubuntu:xenial] echo
                [ubuntu:xenial] Print i inside stage = 2
                [Pipeline] [ubuntu:xenial] echo
                [ubuntu:xenial] Echo i inside stage = 2
                [Pipeline] [ubuntu:xenial] sh
                [ubuntu:xenial] [test-pipeline@2] Running shell script
                [ubuntu:xenial] + ci/script.sh null
                [ubuntu:xenial] /var/jenkins_home/workspace/test-pipeline@2@tmp/durable-b1807fa2/script.sh: 2: /var/jenkins_home/workspace/test-pipeline@2@tmp/durable-b1807fa2/script.sh: ci/script.sh: not found
                [Pipeline] [ubuntu:xenial]
                [Pipeline] [ubuntu:xenial] // stage
                [Pipeline] [ubuntu:xenial]
                [ubuntu:xenial] Lock released on resource [build]
                [Pipeline] [ubuntu:xenial] // lock
                [Pipeline] [ubuntu:xenial]
                [Pipeline] [ubuntu:xenial] // node
                [Pipeline] [ubuntu:xenial]
                [ubuntu:xenial] Failed in branch ubuntu:xenial
                [Pipeline] // parallel
                [Pipeline]
                [Pipeline] // stage
                [Pipeline] End of Pipeline
                ERROR: script returned exit code 127
                Finished: FAILURE


                I have used println inside the stage on purpose, because this is not a Jenkins pipeline step, but simple Groovy method. As you can see it gets evaluated when the parallel execution happens in matrix stage. Each Groovy closure is associated with bindings - its local state of variables. It looks like it contains images and i bindings and it tracks changes of the state of i variable. That is why it tries to access images[2] when evaluating sh step.



                Solution



                There is a simple solution to this problem. You can replace for-loop with for-each. Consider following example:



                def images = ["ubuntu:bionic", "ubuntu:xenial"]
                def tasks = [:]

                images.each image ->
                tasks["$image"] =
                node
                lock("build")
                stage('checkout')
                checkout scm

                stage('test')
                sh "ci/script.sh $image"






                stage("matrix")
                parallel tasks



                Console output:



                [ubuntu:bionic] [test-pipeline] Running shell script
                [ubuntu:bionic] + ci/script.sh ubuntu:bionic
                [ubuntu:xenial] [test-pipeline@2] Running shell script
                [ubuntu:xenial] + ci/script.sh ubuntu:xenial



                You can find explanation of global scope of i variable in Pipeline - Parallel execution of tasks article on CloudBees:




                Note: Variables define outside the for block are not local, but global to the script. Testing the option 2, you will notice that variable i prints always value 4, whereas index increases from 0 to 3 and branch from 1 to 4.







                share|improve this answer














                A closure you create in the loop and assign to tasks["$images[i]"] is evaluated lazily and it seems like it processes images.getAt(i) with the current i value, which in this case is equal to 2 in both cases. Take a look at following example with some additional printing of current i state (I've skipped scm checkout int this short example):



                def images = ["ubuntu:bionic", "ubuntu:xenial"]
                def tasks = [:]

                for (int i = 0; i < images.size(); i++)
                println "Using i = $i" // <- first print
                tasks["$images[i]"] =
                node
                lock("build")
                stage('checkout')
                echo "ok"

                stage('test')
                println "Print i inside stage = $i" // <- second print
                echo "Echo i inside stage = $i" // <- third print
                sh "ci/script.sh $images[i]".toString()






                stage("matrix")
                parallel tasks



                When we run it we will see something like this in the console:



                [Pipeline] echo
                Using i = 0
                [Pipeline] echo
                Using i = 1
                [Pipeline] stage
                [Pipeline] (matrix)
                [Pipeline] parallel
                [Pipeline] [ubuntu:bionic] (Branch: ubuntu:bionic)
                [Pipeline] [ubuntu:xenial] (Branch: ubuntu:xenial)
                [Pipeline] [ubuntu:bionic] node
                [ubuntu:bionic] Running on Jenkins in /var/jenkins_home/workspace/test-pipeline
                [Pipeline] [ubuntu:xenial] node
                [ubuntu:xenial] Running on Jenkins in /var/jenkins_home/workspace/test-pipeline@2
                [Pipeline] [ubuntu:bionic]
                [Pipeline] [ubuntu:xenial]
                [Pipeline] [ubuntu:bionic] lock
                [ubuntu:bionic] Trying to acquire lock on [build]
                [ubuntu:bionic] Lock acquired on [build]
                [Pipeline] [ubuntu:bionic]
                [Pipeline] [ubuntu:xenial] lock
                [ubuntu:xenial] Trying to acquire lock on [build]
                [ubuntu:xenial] Found 0 available resource(s). Waiting for correct amount: 1.
                [ubuntu:xenial] [build] is locked, waiting...
                [Pipeline] [ubuntu:bionic] stage
                [Pipeline] [ubuntu:bionic] (checkout)
                [Pipeline] [ubuntu:bionic] echo
                [ubuntu:bionic] ok
                [Pipeline] [ubuntu:bionic]
                [Pipeline] [ubuntu:bionic] // stage
                [Pipeline] [ubuntu:bionic] stage
                [Pipeline] [ubuntu:bionic] (test)
                [Pipeline] [ubuntu:bionic] echo
                [ubuntu:bionic] Print i inside stage = 2
                [Pipeline] [ubuntu:bionic] echo
                [ubuntu:bionic] Echo i inside stage = 2
                [Pipeline] [ubuntu:bionic] sh
                [ubuntu:bionic] [test-pipeline] Running shell script
                [ubuntu:bionic] + ci/script.sh null
                [ubuntu:bionic] /var/jenkins_home/workspace/test-pipeline@tmp/durable-998289d1/script.sh: 2: /var/jenkins_home/workspace/test-pipeline@tmp/durable-998289d1/script.sh: ci/script.sh: not found
                [Pipeline] [ubuntu:bionic]
                [Pipeline] [ubuntu:bionic] // stage
                [ubuntu:xenial] Lock acquired on [build]
                [Pipeline] [ubuntu:bionic]
                [ubuntu:bionic] Lock released on resource [build]
                [Pipeline] [ubuntu:xenial]
                [Pipeline] [ubuntu:bionic] // lock
                [Pipeline] [ubuntu:bionic]
                [Pipeline] [ubuntu:xenial] stage
                [Pipeline] [ubuntu:xenial] (checkout)
                [Pipeline] [ubuntu:bionic] // node
                [Pipeline] [ubuntu:bionic]
                [ubuntu:bionic] Failed in branch ubuntu:bionic
                [Pipeline] [ubuntu:xenial] echo
                [ubuntu:xenial] ok
                [Pipeline] [ubuntu:xenial]
                [Pipeline] [ubuntu:xenial] // stage
                [Pipeline] [ubuntu:xenial] stage
                [Pipeline] [ubuntu:xenial] (test)
                [Pipeline] [ubuntu:xenial] echo
                [ubuntu:xenial] Print i inside stage = 2
                [Pipeline] [ubuntu:xenial] echo
                [ubuntu:xenial] Echo i inside stage = 2
                [Pipeline] [ubuntu:xenial] sh
                [ubuntu:xenial] [test-pipeline@2] Running shell script
                [ubuntu:xenial] + ci/script.sh null
                [ubuntu:xenial] /var/jenkins_home/workspace/test-pipeline@2@tmp/durable-b1807fa2/script.sh: 2: /var/jenkins_home/workspace/test-pipeline@2@tmp/durable-b1807fa2/script.sh: ci/script.sh: not found
                [Pipeline] [ubuntu:xenial]
                [Pipeline] [ubuntu:xenial] // stage
                [Pipeline] [ubuntu:xenial]
                [ubuntu:xenial] Lock released on resource [build]
                [Pipeline] [ubuntu:xenial] // lock
                [Pipeline] [ubuntu:xenial]
                [Pipeline] [ubuntu:xenial] // node
                [Pipeline] [ubuntu:xenial]
                [ubuntu:xenial] Failed in branch ubuntu:xenial
                [Pipeline] // parallel
                [Pipeline]
                [Pipeline] // stage
                [Pipeline] End of Pipeline
                ERROR: script returned exit code 127
                Finished: FAILURE


                I have used println inside the stage on purpose, because this is not a Jenkins pipeline step, but simple Groovy method. As you can see it gets evaluated when the parallel execution happens in matrix stage. Each Groovy closure is associated with bindings - its local state of variables. It looks like it contains images and i bindings and it tracks changes of the state of i variable. That is why it tries to access images[2] when evaluating sh step.



                Solution



                There is a simple solution to this problem. You can replace for-loop with for-each. Consider following example:



                def images = ["ubuntu:bionic", "ubuntu:xenial"]
                def tasks = [:]

                images.each image ->
                tasks["$image"] =
                node
                lock("build")
                stage('checkout')
                checkout scm

                stage('test')
                sh "ci/script.sh $image"






                stage("matrix")
                parallel tasks



                Console output:



                [ubuntu:bionic] [test-pipeline] Running shell script
                [ubuntu:bionic] + ci/script.sh ubuntu:bionic
                [ubuntu:xenial] [test-pipeline@2] Running shell script
                [ubuntu:xenial] + ci/script.sh ubuntu:xenial



                You can find explanation of global scope of i variable in Pipeline - Parallel execution of tasks article on CloudBees:




                Note: Variables define outside the for block are not local, but global to the script. Testing the option 2, you will notice that variable i prints always value 4, whereas index increases from 0 to 3 and branch from 1 to 4.








                share|improve this answer














                share|improve this answer



                share|improve this answer








                edited Nov 10 at 8:27

























                answered Nov 10 at 8:17









                Szymon Stepniak

                16.1k83061




                16.1k83061



























                    draft saved

                    draft discarded
















































                    Thanks for contributing an answer to Stack Overflow!


                    • Please be sure to answer the question. Provide details and share your research!

                    But avoid


                    • Asking for help, clarification, or responding to other answers.

                    • Making statements based on opinion; back them up with references or personal experience.

                    To learn more, see our tips on writing great answers.





                    Some of your past answers have not been well-received, and you're in danger of being blocked from answering.


                    Please pay close attention to the following guidance:


                    • Please be sure to answer the question. Provide details and share your research!

                    But avoid


                    • Asking for help, clarification, or responding to other answers.

                    • Making statements based on opinion; back them up with references or personal experience.

                    To learn more, see our tips on writing great answers.




                    draft saved


                    draft discarded














                    StackExchange.ready(
                    function ()
                    StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53235862%2fpassing-loop-variable-to-sh-in-jenkinsfile-doesnt-scope-correctly%23new-answer', 'question_page');

                    );

                    Post as a guest















                    Required, but never shown





















































                    Required, but never shown














                    Required, but never shown












                    Required, but never shown







                    Required, but never shown

































                    Required, but never shown














                    Required, but never shown












                    Required, but never shown







                    Required, but never shown







                    Popular posts from this blog

                    How to how show current date and time by default on contact form 7 in WordPress without taking input from user in datetimepicker

                    Syphilis

                    Darth Vader #20