在Jenkinsfile中将循环变量传递给sh无法正确作用域

时间:2018-11-10 03:53:10

标签: jenkins groovy jenkins-pipeline

我正在尝试使用以下循环在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 :(得分:2)

您在循环中创建并分配给tasks["${images[i]}"]的闭包被懒惰地求值,似乎它使用当前的images.getAt(i)值处理i,在这种情况下,该值等于{在两种情况下均为{1}}。看看下面的示例,其中包含当前2状态的一些附加打印信息(在此简短示例中,我已跳过i int):

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 ,因为这不是Jenkins管道步骤,而是简单的Groovy方法。如您所见,当并行执行发生在println阶段时,它将得到评估。每个Groovy闭包都与绑定关联-变量的局部状态。看起来它包含matriximages绑定,并且它跟踪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
}

您可以在CloudBees上的Pipeline - Parallel execution of 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块之外定义的变量不是本地的,而是脚本的全局变量。测试选项2时,您会注意到变量for始终输出值4,而i从0增大到3,而index从1增大到4。