我正在尝试使用以下循环在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
}
如何正确构建这些动态命令?
答案 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闭包都与绑定关联-变量的局部状态。看起来它包含matrix
和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
}
您可以在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。