如何在 `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
就像是每个 steps
、step
或 step
中的局部变量一样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
上述示例作业的输出:
阶段:Get Node Name
:
env.NODE_NAME='EC2 (EC2-Jenkins) - Pipeline Builder (i-feeb1ec0de5caff01d)'
GLOBAL_NODE_NAME='EC2 (EC2-Jenkins) - Pipeline Builder (i-feeb1ec0de5caff01d)'
阶段:Build
:
DEBUG Interpolation GLOBAL_NODE_NAME='EC2 (EC2-Jenkins) - Pipeline Builder (i-feeb1ec0de5caff01d)'
DEBUG Raw Groovy Variable GLOBAL_NODE_NAME='EC2 (EC2-Jenkins) - Pipeline Builder (i-feeb1ec0de5caff01d)'
-
echo Shell string Interpolation GLOBAL_NODE_NAME=EC2 (EC2-Jenkins) -
Pipeline Builder (i-feeb1ec0de5caff01d)
Shell string Interpolation GLOBAL_NODE_NAME=EC2 (EC2-Jenkins) - Pipeline Builder (i-feeb1ec0de5caff01d)
阶段:Run Tests
:
- 注意:在
options
块中,echo
、println
不可用...但我们可以看到输出https://<jenkins-host-here>/lockable-resources/
页面上的资源名称:
GLOBAL_NODE_NAME
设置为“GLOBAL
”,因此 stage
=> step
赋值 没有 工作!
- Lockable Resources显示
resource
的值仍然设置为GLOBAL
!
- 平行阶段:
Unit Tests
Inside Steps block: GLOBAL_NODE_NAME='EC2 (EC2-Jenkins) - Pipeline Builder (i-feeb1ec0de5caff01d)'
DEBUG Interpolation GLOBAL_NODE_NAME='EC2 (EC2-Jenkins) - Pipeline Builder (i-feeb1ec0de5caff01d)'
DEBUG Raw Groovy Variable GLOBAL_NODE_NAME='EC2 (EC2-Jenkins) - Pipeline Builder (i-feeb1ec0de5caff01d)'
-
+ echo Shell string Interpolation GLOBAL_NODE_NAME=EC2 (EC2-Jenkins) - Pipeline Builder (i-feeb1ec0de5caff01d)
Shell string Interpolation GLOBAL_NODE_NAME=EC2 (EC2-Jenkins) - Pipeline Builder (i-feeb1ec0de5caff01d)
编辑:这是最终的工作解决方案
感谢@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
你不能,但由于 lock
在 steps
块中 也 可用,你可以这样处理。由于这些事情的性质,您可能必须在此处使用 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'
问题:如何在 options
块中使用 NODE_NAME
?
我尝试了简单的解决方案来尝试使用 env.NODE_NAME
,但这在 options
块内计算为 null
:
options { lock(resource: "${env.NODE_NAME}") }
Jenkins Pipeline docs 解释了为什么这不起作用:
Inside a
stage
, the steps in theoptions
directive are invoked before entering theagent
or checking anywhen
conditions.
因此在此上下文中设置为 null
。
接下来,我尝试了各种方法来获得 stage { steps {}}
级别的 env.NODE_NAME
,并通过全局 Groovy 变量将其向上传递。但是,这似乎不起作用。
在下面的示例 Jenkinsfile
中,全局 Groovy 变量 GLOBAL_NODE_NAME
就像是每个 steps
、step
或 step
中的局部变量一样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
上述示例作业的输出:
阶段:
Get Node Name
:env.NODE_NAME='EC2 (EC2-Jenkins) - Pipeline Builder (i-feeb1ec0de5caff01d)'
GLOBAL_NODE_NAME='EC2 (EC2-Jenkins) - Pipeline Builder (i-feeb1ec0de5caff01d)'
阶段:
Build
:DEBUG Interpolation GLOBAL_NODE_NAME='EC2 (EC2-Jenkins) - Pipeline Builder (i-feeb1ec0de5caff01d)'
DEBUG Raw Groovy Variable GLOBAL_NODE_NAME='EC2 (EC2-Jenkins) - Pipeline Builder (i-feeb1ec0de5caff01d)'
-
echo Shell string Interpolation GLOBAL_NODE_NAME=EC2 (EC2-Jenkins) - Pipeline Builder (i-feeb1ec0de5caff01d) Shell string Interpolation GLOBAL_NODE_NAME=EC2 (EC2-Jenkins) - Pipeline Builder (i-feeb1ec0de5caff01d)
阶段:
Run Tests
:- 注意:在
options
块中,echo
、println
不可用...但我们可以看到输出https://<jenkins-host-here>/lockable-resources/
页面上的资源名称:GLOBAL_NODE_NAME
设置为“GLOBAL
”,因此stage
=>step
赋值 没有 工作!- Lockable Resources显示
resource
的值仍然设置为GLOBAL
!
- 平行阶段:
Unit Tests
Inside Steps block: GLOBAL_NODE_NAME='EC2 (EC2-Jenkins) - Pipeline Builder (i-feeb1ec0de5caff01d)'
DEBUG Interpolation GLOBAL_NODE_NAME='EC2 (EC2-Jenkins) - Pipeline Builder (i-feeb1ec0de5caff01d)'
DEBUG Raw Groovy Variable GLOBAL_NODE_NAME='EC2 (EC2-Jenkins) - Pipeline Builder (i-feeb1ec0de5caff01d)'
-
+ echo Shell string Interpolation GLOBAL_NODE_NAME=EC2 (EC2-Jenkins) - Pipeline Builder (i-feeb1ec0de5caff01d) Shell string Interpolation GLOBAL_NODE_NAME=EC2 (EC2-Jenkins) - Pipeline Builder (i-feeb1ec0de5caff01d)
- 注意:在
编辑:这是最终的工作解决方案
感谢@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
你不能,但由于 lock
在 steps
块中 也 可用,你可以这样处理。由于这些事情的性质,您可能必须在此处使用 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'