Закрытие, которое вы создаете в цикле и присваиваете tasks["${images[i]}"]
, оценивается лениво, и похоже, что оно обрабатывает images.getAt(i)
с текущим значением i
, которое в этом случае равно 2
в обоих случаях. Посмотрите на следующий пример с дополнительной печатью текущего состояния i
(я пропустил scm checkout
в этом коротком примере):
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
}
Когда мы запустим его, мы увидим что-то вроде этого в консоли:
[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
Я специально использовал println
внутри сцены, потому что это не шаг конвейера Дженкинса, а простой метод Groovy. Как вы можете видеть, он оценивается, когда параллельное выполнение происходит на этапе matrix
. Каждое Groovy-замыкание связано с привязками - его локальным состоянием переменных. Похоже, что он содержит привязки images
и i
и отслеживает изменения состояния переменной i
. Вот почему он пытается получить доступ к images[2]
при оценке шага sh
.
Решение
Существует простое решение этой проблемы. Вы можете заменить for-loop
на for-each
. Рассмотрим следующий пример:
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
}
Консольный вывод:
[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
Описание глобальной области действия переменной i
можно найти в Конвейер - Параллельное выполнение задач Статья на CloudBees:
Примечание: Переменные, определенные вне блока for
, являются не локальными, а глобальными для сценария. Тестируя опцию 2, вы заметите, что переменная i
печатает всегда значение 4, тогда как index
увеличивается с 0 до 3 и branch
с 1 до 4.