Передача переменной цикла в sh в Jenkinsfile некорректна - PullRequest
0 голосов
/ 10 ноября 2018

Я пытаюсь динамически создавать задания в Jenkinsfile, используя следующий цикл. Задания создаются правильно, а имена задач отображаются в Jenkins с правильным именем (например, ubuntu:bionic).

Однако внутри каждой задачи команды sh, по-видимому, не имеют доступа к images, поскольку ${images[i]} оценивается как null (например, sh ci/script.sh null). Так что, похоже, это не то же самое, что другие проблемы с одинарными и двойными кавычками с интерполяцией строк.

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
}

Как правильно построить эти динамические команды?

1 Ответ

0 голосов
/ 10 ноября 2018

Закрытие, которое вы создаете в цикле и присваиваете 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.

...