"Each" Jenkins 并行脚本问题中的闭包和 For 循环

"Each" Closure and For Loop in Jenkins Parallel script issue

我有一个基本上读取 JSON 文件并从中打印出一些值的 Jenkins 管道。

import groovy.json.JsonSlurper
import groovy.json.JsonSlurperClassic


pipeline {
    agent any
    stages {
        stage('test') {
            steps {
                script {
                    def environment = new JsonSlurperClassic().parseText('''
                        {                              
                          "list": [
                            {
                              "name": "service1"
                            },
                            {
                              "name": "service2"
                            },
                            {
                              "name": "NotAService"
                            },
                            {
                              "name": "AnotherDummyService-mock",
                            }  
                          ]
                        }
                        '''
                        )

                    def forLoopBuilders = [:]
                    for (artifact in environment.list) {
                        if (!artifact.name.contains("-mock")) {
                            println("Before parallel, the value is ${artifact.name}")
                            forLoopBuilders[artifact.name] = { println(artifact.name) }
                        }
                    }
                    parallel forLoopBuilders

                    def closureBuilders = [:]
                    environment.list.each { artifact ->
                        if (!artifact.name.contains("-mock")) {
                            println("Before parallel, the value is ${artifact.name}")
                            closureBuilders[artifact.name] = { println(artifact.name) }
                        }
                    }
                    parallel closureBuilders
                }
            }
        }
    }
}


@NonCPS
def jsonParse(def json) {
    new groovy.json.JsonSlurperClassic().parseText(json)
}

builders 变量是我存储并行 运行 阶段的方式。基本上是 [stageA: What to do in this stageA, anotherStage: What to do in this anotherStage]

输出结果如下

10:20:53  Before parallel, the value is service1
10:20:53  [Pipeline] echo
10:20:53  Before parallel, the value is service2
10:20:53  [Pipeline] echo
10:20:53  Before parallel, the value is NotAService
10:20:53  [Pipeline] parallel
10:20:53  [Pipeline] { (Branch: service1)
10:20:53  [Pipeline] { (Branch: service2)
10:20:53  [Pipeline] { (Branch: NotAService)
10:20:53  [Pipeline] echo
10:20:53  AnotherDummyService-mock
10:20:53  [Pipeline] }
10:20:53  [Pipeline] echo
10:20:53  AnotherDummyService-mock
10:20:53  [Pipeline] }
10:20:53  [Pipeline] echo
10:20:53  AnotherDummyService-mock

10:20:53  [Pipeline] // parallel
10:20:53  [Pipeline] echo
10:20:53  Before parallel, the value is service1
10:20:53  [Pipeline] echo
10:20:54  Before parallel, the value is service2
10:20:54  [Pipeline] echo
10:20:54  Before parallel, the value is NotAService
10:20:55  [Pipeline] parallel
10:20:55  [Pipeline] { (Branch: service1)
10:20:55  [Pipeline] { (Branch: service2)
10:20:55  [Pipeline] { (Branch: NotAService)
10:20:55  [Pipeline] echo
10:20:55  service1
10:20:55  [Pipeline] }
10:20:55  [Pipeline] echo
10:20:55  service2
10:20:55  [Pipeline] }
10:20:55  [Pipeline] echo
10:20:55  NotAService

如您所见,运行 并行阶段的输出是不同的。为什么会这样?

我想要的是并行 forLoopBuilders 的输出应该与 closureBuilders.

的输出相同

这似乎是 for 循环中的闭包如何捕获循环变量的结果,在您的例子中 artifact

只有一个循环变量不断被重新赋值...所以闭包捕获那个单个变量,但是一旦循环结束,该变量将被分配最后一个值,因此所有闭包将只稍后查看该值。

您可以通过 运行 纯 Groovy:

中的这个简单示例来了解它的行为方式
    def map = [
            'Service1': 's1',
            'Service2': 's2'
    ]
    def closures = []
    for (entry in map.entrySet()) {
        closures << { println "ENTRY: $entry" }
    }
    closures*.call()

这将打印:

ENTRY: Service2=s2
ENTRY: Service2=s2

即闭包捕获 entry 变量的最后一个值。

Groovy 闭包在捕获值方面更智能,它们实际上在每个 运行 上创建新变量,因此如果您将 for 循环替换为 each { ... } 构造,它会起作用:

    def map = [
            'Service1': 's1',
            'Service2': 's2'
    ]
    def closures = []
    map.entrySet().each { entry ->
        closures << { println "ENTRY: $entry" }
    }
    closures*.call()

打印:

ENTRY: Service1=s1
ENTRY: Service2=s2

在您的情况下,只需使用 each { },它就可以满足您的需求。

编辑

如果你坚持使用for循环,你应该像在Java中那样做,并将当前值赋给一个新变量。

以下代码与使用 each 的结果相同:

    def map = [
            'Service1': 's1',
            'Service2': 's2'
    ]
    def closures = []
    for (entry in map.entrySet()) {
        def value = entry
        closures << { println "ENTRY: $value" }
    }
    closures*.call()