如何在 `options` 块中使用 `NODE_NAME`? - Jenkinsfile 声明式管道

How can I use `NODE_NAME` within an `options` block? - Jenkinsfile Declarative Pipeline

问题:如何在 options 块中使用 NODE_NAME

我尝试了简单的解决方案来尝试使用 env.NODE_NAME,但这在 options 块内计算为 null

options { lock(resource: "${env.NODE_NAME}") }

Jenkins Pipeline docs 解释了为什么这不起作用:

Inside a stage, the steps in the options directive are invoked before entering the agent or checking any when conditions.

因此在此上下文中设置为 null

接下来,我尝试了各种方法来获得 stage { steps {}} 级别的 env.NODE_NAME,并通过全局 Groovy 变量将其向上传递。但是,这似乎不起作用。

在下面的示例 Jenkinsfile 中,全局 Groovy 变量 GLOBAL_NODE_NAME 就像是每个 stepsstepstep 中的局部变量一样script 块。同时,它的行为就好像它是 pipeline { stage { options {} }} 块中的全局变量一样。在节点的“重量级执行程序”上下文中设置的值不会冒泡到全局 Groovy 脚本级别。

我想做什么:

我想在 options 块中使用 lockable-resources 插件的 lock 语法来限制并发作业。如果多个作业运行 parallel "Run Tests" 阶段,则所有 RAM 都在 Jenkins 代理节点上消耗。因此,我试图锁定每个节点的资源以限制该特定节点上的并发作业。

我使用的最小 Jenkinsfile 看起来像这样:

String GLOBAL_NODE_NAME = 'GLOBAL'
pipeline {
    agent { label 'ec2-node' }

    stages {
        stage('Get Node Name') {
            steps {
                script {
                    println("env.NODE_NAME='${env.NODE_NAME}'")
                    GLOBAL_NODE_NAME = NODE_NAME
                    println("GLOBAL_NODE_NAME='${GLOBAL_NODE_NAME}'")
                }
            }
        }
        stage('Build') {
            steps {
                script {
                    println("DEBUG Interpolation GLOBAL_NODE_NAME='${GLOBAL_NODE_NAME}'")
                    println("DEBUG Raw Groovy Variable GLOBAL_NODE_NAME='" + GLOBAL_NODE_NAME + "'")
                }
                sh "echo Shell string Interpolation GLOBAL_NODE_NAME='${GLOBAL_NODE_NAME}'"
                // Do build steps here
            }
        }

        stage('Run Tests') {
            // Inverse order LIFO
            options {
                // Get NODE_NAME from the currentBuild b/c lightweight executor returns 'null'
                lock(inversePrecedence: true, resource: "${GLOBAL_NODE_NAME}")
            }
            parallel {
                stage('Unit Tests') {
                    steps {
                        echo "Inside Steps block: GLOBAL_NODE_NAME='${GLOBAL_NODE_NAME}'"
                        script {
                            println("DEBUG Interpolation GLOBAL_NODE_NAME='${GLOBAL_NODE_NAME}'")
                            println("DEBUG Raw Groovy Variable GLOBAL_NODE_NAME='" + GLOBAL_NODE_NAME + "'")
                        }
                        sh "echo Shell string Interpolation GLOBAL_NODE_NAME='${GLOBAL_NODE_NAME}'"

                        // Do Unit Tests
                    }
                }

                stage('Integration Tests') {
                    steps {
                        sh 'echo this so example Jenkinsfile is valid'
                        // Do Integration Tests
                    }
                }
            } // end parallel
        }  // end Run Tests
    } // end stages
} // end pipeline

上述示例作业的输出:

编辑:这是最终的工作解决方案

感谢@AdamSmith 的 ,它让我开始了正确的方向。这是最终 Jenkinsfile 有效结构的框架:

/* groovylint-disable DuplicateStringLiteral */
/* groovylint-disable-next-line CompileStatic */
String debugBegin = '============================== DEBUG ENV =============================='
String debugEnd = '============================== END DEBUG =============================='
/* groovylint-disable NestedBlockDepth */
/* groovylint-disable-next-line CompileStatic */
pipeline {
    agent { label 'ec2-node' }

    stages {
        stage('Build') {
            steps {
                script {
                   // Evaluates in 'heavyweight' executor context
                    println("Running on env.NODE_NAME='${env.NODE_NAME}'") // env.NODE_NAME works here
                }
                // Do build steps here
            }
        }

        stage('Run Tests') {
            options {
                /*
                Note: Cannot get NODE_NAME from this context!
                options is evaluated inside 'lightweight' executor, so currentBuild returns 'null'
                and any other method trying to pass env.NODE_NAME, or
                NODE_NAME up from the node / 'heavyweight' executor context did not work!

                lock(inversePrecedence: true, resource: "cannot-get-node-name") // Anything I tried here did not work!
                This context executes on jenkins master, and I could not find a way to pass the value back from a node
                */
                timeout(time: 15, unit: 'MINUTES')
            }
            steps {
                // Inverse order LIFO
                // lock NODE_NAME b/c parallel tests are RAM intensive
                lock(inversePrecedence: true, resource: "${NODE_NAME}") {
                    script { // hack so parallel syntax is made available here
                        parallel Test: { // map: {} syntax to pass to scripted-pipeline 'parallel'
                            stage('Test') {
                                try { // Switched from post { always {}} to => try {} finally {} here
                                    // because syntax did not work otherwise
                                    echo "Inside Steps block: NODE_NAME='${NODE_NAME}'"
                                    println("DEBUG Interpolation NODE_NAME='${NODE_NAME}'")
                                    println("DEBUG Raw Groovy Variable NODE_NAME='" + NODE_NAME + "'")
                                    sh "echo Shell string Interpolation GLOBAL_NODE_NAME='${NODE_NAME}'"
                                    echo "${debugBegin}"
                                    sh 'env' // STAGE_NAME is now 'Test'
                                    echo "${debugEnd}"
                                    sh 'make test'
                                    echo 'Tests Succeeded!'
                                }
                                finally {
                                    junit '**/path/to/test-reports/*.xml'
                                    sh 'make cleanup'
                                }
                            }
                        },
                        IntegrationTests: {
                            stage('Integration Tests') {
                                try {
                                    echo "${debugBegin}"
                                    sh 'env' // STAGE_NAME is now 'Integration Tests'
                                    echo "${debugEnd}"
                                    sh 'make integration'
                                    echo 'Integration Tests Succeeded!'
                                }
                                finally {
                                    sh 'make cleanup'
                                }
                            }
                        } // end parallel map
                    } // end script
                } // end lock NODE_NAME
            } // end steps
        } // end stage Run Tests
    } // end stages
} // end pipeline

你不能,但由于 locksteps 块中 可用,你可以这样处理。由于这些事情的性质,您可能必须在此处使用 script 逃生舱口才能继续平行(抱歉)

stage('Run Tests') {
    steps {
        // Inverse order LIFO
        lock(inversePrecedence: true, resource: "${NODE_NAME}") {
            script {
                parallel [
                    'Unit Tests': {
                        echo "Inside Steps block: NODE_NAME='${NODE_NAME}'"
                        println("DEBUG Interpolation NODE_NAME='${NODE_NAME}'")
                        println("DEBUG Raw Groovy Variable NODE_NAME='" + NODE_NAME+ "'")
                        sh "echo Shell string Interpolation GLOBAL_NODE_NAME='${GLOBAL_NODE_NAME}'"
                    },
                    'Integration Tests': {
                        sh 'echo this so example Jenkinsfile is valid'
                    }
                ]
            } // end script
        } // end lock
    }  // end steps
} // end stage 'Run Tests'