You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Jenkins并行流水线各阶段使用不同用户的问题求助

问题原因分析

你碰到的是Groovy闭包的变量引用捕获特性导致的问题:在each循环中定义的闭包并没有保存循环时WINUSER的当前值,而是持有了这个变量的引用。当整个循环执行完毕,WINUSER已经被更新为最后一次循环的test3,所有并行阶段运行时都会读取这个最终值,所以出现了所有agent都用同一个用户名的情况。

解决方案

要让每个并行阶段获取到对应循环步骤的用户名,核心是让每个闭包绑定独立的变量值,避免共享同一个变量引用。以下是几种实用的修改方案:

方案一:合并循环逻辑(最直观)

把阶段名生成和并行闭包定义合并到同一个计数循环中,每次循环创建独立的局部变量存储当前用户名,确保每个闭包捕获的是独立的变量:

def parallelStagesSoak = [:]

pipeline {
    agent none 

    parameters {
        string(name: "AGENT_COUNT", defaultValue: "1", trim: true, description: "How many Windows agents to run the test")
        string(name: "HNAS_PATH_WIN", defaultValue: "\\\\server1\\dir", trim: true, description: "path to share")
        string(name: "MAP_DRIVE_WIN", defaultValue: "P", trim: true, description: "drive letter to use in Windows")
    }

    stages {      
        stage("soak_test") { 
            steps {
                script {
                    def agentCount = params.AGENT_COUNT.toInteger()
                    // 直接在计数循环中生成每个并行任务
                    for (int i=1; i<=agentCount; i++) {
                        def currentUser = "test${i}" // 每次循环创建独立变量
                        def stageName = "win_agent_${i}"
                        parallelStagesSoak[stageName] = {
                            echo "WINUSER in test=${currentUser}"
                            node(label: "soak-client && windows") {
                                stage(stageName) {
                                    // 修正原代码参数名错误:PATH_WIN -> HNAS_PATH_WIN
                                    bat "net use ${params.MAP_DRIVE_WIN}: ${params.HNAS_PATH_WIN} /user:domain1\\${currentUser} test"
                                }
                            }
                        }
                    }
                    parallel parallelStagesSoak
                }
            }
        }
    }
}

方案二:用索引关联阶段与用户名

如果需要保留先收集阶段名的逻辑,可以通过eachWithIndex获取阶段对应的索引,重新计算对应用户名:

def parallelStagesSoak = [:]
def stagesToBuild = []

pipeline {
    agent none 

    parameters {
        string(name: "AGENT_COUNT", defaultValue: "1", trim: true, description: "How many Windows agents to run the test")
        string(name: "HNAS_PATH_WIN", defaultValue: "\\\\server1\\dir", trim: true, description: "path to share")
        string(name: "MAP_DRIVE_WIN", defaultValue: "P", trim: true, description: "drive letter to use in Windows")
    }

    stages {      
        stage("soak_test") { 
            steps {
                script {
                    def agentCount = params.AGENT_COUNT.toInteger()
                    // 先收集所有阶段名
                    for (int i=1; i<=agentCount; i++) {
                        stagesToBuild.add("win_agent_${i}")
                    }
                
                    // 通过索引获取对应用户名
                    stagesToBuild.eachWithIndex { stageName, index ->
                        def currentUser = "test${index + 1}" // 索引从0开始,加1匹配编号
                        parallelStagesSoak[stageName] = {
                            echo "WINUSER in test=${currentUser}"
                            node(label: "soak-client && windows") {
                                stage(stageName) {
                                    bat "net use ${params.MAP_DRIVE_WIN}: ${params.HNAS_PATH_WIN} /user:domain1\\${currentUser} test"
                                }
                            }
                        } 
                    }
                    parallel parallelStagesSoak
                }
            }
        }
    }
}

方案三:闭包参数绑定(进阶用法)

利用Groovy闭包的curry方法,显式为每个闭包绑定当前用户名参数:

def parallelStagesSoak = [:]

pipeline {
    agent none 

    parameters {
        string(name: "AGENT_COUNT", defaultValue: "1", trim: true, description: "How many Windows agents to run the test")
        string(name: "HNAS_PATH_WIN", defaultValue: "\\\\server1\\dir", trim: true, description: "path to share")
        string(name: "MAP_DRIVE_WIN", defaultValue: "P", trim: true, description: "drive letter to use in Windows")
    }

    stages {      
        stage("soak_test") { 
            steps {
                script {
                    def agentCount = params.AGENT_COUNT.toInteger()
                    for (int i=1; i<=agentCount; i++) {
                        def currentUser = "test${i}"
                        def stageName = "win_agent_${i}"
                        // 用curry绑定当前用户值到闭包参数
                        parallelStagesSoak[stageName] = { user ->
                            echo "WINUSER in test=${user}"
                            node(label: "soak-client && windows") {
                                stage(stageName) {
                                    bat "net use ${params.MAP_DRIVE_WIN}: ${params.HNAS_PATH_WIN} /user:domain1\\${user} test"
                                }
                            }
                        }.curry(currentUser)
                    }
                    parallel parallelStagesSoak
                }
            }
        }
    }
}
核心原理

Groovy闭包默认捕获变量的引用而非当前值。原代码中所有闭包都指向同一个WINUSER变量,循环结束后该变量的值是最后一次循环的结果。通过在每次循环中创建独立的局部变量(如currentUser),让每个闭包持有不同的变量引用,就能确保每个阶段获取到正确的用户名。

内容的提问来源于stack exchange,提问作者Ian Mayoh

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.20 08:14:52