带有命名参数的自定义 jenkins 声明性管道 dsl
custom jenkins declarative pipeline dsl with named arguments
我已经尝试了一段时间,开始努力将我们的自由风格项目转移到管道中。为此,我觉得最好建立一个共享库,因为我们的大多数构建都是相同的。我通读了这个blog post from Jenkins。我想出了以下
// vars/buildGitWebProject.groovy
def call(body) {
def args= [:]
body.resolveStrategy = Closure.DELEGATE_FIRST
body.delegate = args
body()
pipeline {
agent {
node {
label 'master'
customWorkspace "c:\jenkins_repos\${args.repositoryName}\${args.branchName}"
}
}
environment {
REPOSITORY_NAME = "${args.repositoryName}"
BRANCH_NAME = "${args.branchName}"
SOLUTION_NAME = "${args.solutionName}"
}
options {
buildDiscarder(logRotator(numToKeepStr: '3'))
skipStagesAfterUnstable()
timestamps()
}
stages {
stage("checkout") {
steps {
script{
assert REPOSITORY_NAME != null : "repositoryName is null. Please include it in configuration."
assert BRANCH_NAME != null : "branchName is null. Please include it in configuration."
assert SOLUTION_NAME != null : "solutionName is null. Please include it in configuration."
}
echo "building with ${REPOSITORY_NAME}"
echo "building with ${BRANCH_NAME}"
echo "building with ${SOLUTION_NAME}"
checkoutFromGitWeb(args)
}
}
stage('build and test') {
steps {
executeRake(
"set_assembly_to_current_version",
"build_solution[$args.solutionName, Release, Any CPU]",
"copy_to_deployment_folder",
"execute_dev_dropkick"
)
}
}
}
post {
always {
sendEmail(args)
}
}
}
}
在我的pipeline项目中我配置了Pipeline使用Pipeline脚本,脚本如下:
buildGitWebProject {
repositoryName:'my-git-repo'
branchName: 'qa'
solutionName: 'my_csharp_solution.sln'
emailTo='testuser@domain.com'
}
我尝试过使用和不使用环境块,但结果最终与每个参数的值为 'null' 相同。奇怪的是,代码的脚本部分也没有使构建失败......所以不确定那有什么问题。回声部分也显示为空。我做错了什么?
您的 Closure
body 没有按照您 expect/believe 应有的方式行事。
在你的方法的开头你有:
def call(body) {
def args= [:]
body.resolveStrategy = Closure.DELEGATE_FIRST
body.delegate = args
body()
您的电话 body 是:
buildGitWebProject {
repositoryName:'my-git-repo'
branchName: 'qa'
solutionName: 'my_csharp_solution.sln'
emailTo='testuser@domain.com'
}
让我们尝试调试一下。
如果您在 call(body)
方法中的 body()
之后添加 println(args)
,您将看到如下内容:
[emailTo:testuser@domain.com]
但是,只设置了一个值。怎么回事?
这里有几点需要理解:
- 设置
delegate
或 Closure
有什么作用?
- 为什么
repositoryName:'my-git-repo'
什么都不做?
- 为什么
emailTo='testuser@domain.com'
在地图中设置属性?
What does setting a delegate
of a Closure
do?
这一个大部分是直截了当的,但我认为它有助于理解。 Closure
is powerful and is the Swiss Army knife of Groovy. The delegate
essentially sets what the this
is in the body of the Closure
. You are also using the resolveStrategy
of Closure.DELEGATE_FIRST
, so methods and properties from the delegate are checked first, and then from the enclosing scope (owner) - see the Javadoc 以获得 in-depth 解释。如果调用 size()
、put(...)
、entrySet()
等方法,它们都会首先在 delegate
上调用。 属性访问也是如此。
Why does repositoryName:'my-git-repo'
not do anything?
这似乎是 Groovy map literal, but it is not. These are actually labeled statements。如果您用 [repositoryName:'my-git-repo']
之类的方括号将其括起来,那么这就是地图文字。但是,这就是您在那里所做的一切——创建地图文字。我们要确保这些 objects 在 Closure
中消耗
Why does emailTo='testuser@domain.com'
set the property in the map?
这是使用 Groovy 的 map property notation 功能。如前所述,您将Closure
的delegate
设置为def args= [:]
,这是一个Map
。您还设置了 Closure.DELEGATE_FIRST
的 resolveStrategy
。这使您的 emailTo='testuser@domain.com'
决定在 args
上被调用,这就是 emailTo
键设置为该值的原因。这相当于调用 args.emailTo='testuser@domain.com'
.
那么,你如何解决这个问题?
如果您想保留 Closure
语法方法,您可以将调用的 body 更改为任何实质上在委托 args
映射中存储值的内容:
buildGitWebProject {
repositoryName = 'my-git-repo'
branchName = 'qa'
solutionName = 'my_csharp_solution.sln'
emailTo = 'testuser@domain.com'
}
buildGitWebProject {
put('repositoryName', 'my-git-repo')
put('branchName', 'qa')
put('solutionName', 'my_csharp_solution.sln')
put('emailTo', 'testuser@domain.com')
}
buildGitWebProject {
delegate.repositoryName = 'my-git-repo'
delegate.branchName = 'qa'
delegate.solutionName = 'my_csharp_solution.sln'
delegate.emailTo = 'testuser@domain.com'
}
buildGitWebProject {
// example of Map literal where the square brackets are not needed
putAll(
repositoryName:'my-git-repo',
branchName: 'qa',
solutionName: 'my_csharp_solution.sln',
emailTo: 'testuser@domain.com'
)
}
另一种方法是让您的 call
接受 Map
作为参数并删除您的 Closure
.
def call(Map args) {
// no more args and delegates needed right now
}
buildGitWebProject(
repositoryName: 'my-git-repo',
branchName: 'qa',
solutionName: 'my_csharp_solution.sln',
emailTo: 'testuser@domain.com'
)
还有一些其他方法可以为您的 API 建模,这取决于您要提供的用户体验。
关于共享库代码中声明性管道的旁注:
值得牢记共享库中声明性管道的局限性。看起来您已经在 vars
中这样做了,但为了完整起见,我只是将其添加到此处。在 documentation it is stated:
的最后
Only entire pipeline
s can be defined in shared libraries as of this time. This can only be done in vars/*.groovy
, and only in a call
method. Only one Declarative Pipeline can be executed in a single build, and if you attempt to execute a second one, your build will fail as a result.
我已经尝试了一段时间,开始努力将我们的自由风格项目转移到管道中。为此,我觉得最好建立一个共享库,因为我们的大多数构建都是相同的。我通读了这个blog post from Jenkins。我想出了以下
// vars/buildGitWebProject.groovy
def call(body) {
def args= [:]
body.resolveStrategy = Closure.DELEGATE_FIRST
body.delegate = args
body()
pipeline {
agent {
node {
label 'master'
customWorkspace "c:\jenkins_repos\${args.repositoryName}\${args.branchName}"
}
}
environment {
REPOSITORY_NAME = "${args.repositoryName}"
BRANCH_NAME = "${args.branchName}"
SOLUTION_NAME = "${args.solutionName}"
}
options {
buildDiscarder(logRotator(numToKeepStr: '3'))
skipStagesAfterUnstable()
timestamps()
}
stages {
stage("checkout") {
steps {
script{
assert REPOSITORY_NAME != null : "repositoryName is null. Please include it in configuration."
assert BRANCH_NAME != null : "branchName is null. Please include it in configuration."
assert SOLUTION_NAME != null : "solutionName is null. Please include it in configuration."
}
echo "building with ${REPOSITORY_NAME}"
echo "building with ${BRANCH_NAME}"
echo "building with ${SOLUTION_NAME}"
checkoutFromGitWeb(args)
}
}
stage('build and test') {
steps {
executeRake(
"set_assembly_to_current_version",
"build_solution[$args.solutionName, Release, Any CPU]",
"copy_to_deployment_folder",
"execute_dev_dropkick"
)
}
}
}
post {
always {
sendEmail(args)
}
}
}
}
在我的pipeline项目中我配置了Pipeline使用Pipeline脚本,脚本如下:
buildGitWebProject {
repositoryName:'my-git-repo'
branchName: 'qa'
solutionName: 'my_csharp_solution.sln'
emailTo='testuser@domain.com'
}
我尝试过使用和不使用环境块,但结果最终与每个参数的值为 'null' 相同。奇怪的是,代码的脚本部分也没有使构建失败......所以不确定那有什么问题。回声部分也显示为空。我做错了什么?
您的 Closure
body 没有按照您 expect/believe 应有的方式行事。
在你的方法的开头你有:
def call(body) {
def args= [:]
body.resolveStrategy = Closure.DELEGATE_FIRST
body.delegate = args
body()
您的电话 body 是:
buildGitWebProject {
repositoryName:'my-git-repo'
branchName: 'qa'
solutionName: 'my_csharp_solution.sln'
emailTo='testuser@domain.com'
}
让我们尝试调试一下。
如果您在 call(body)
方法中的 body()
之后添加 println(args)
,您将看到如下内容:
[emailTo:testuser@domain.com]
但是,只设置了一个值。怎么回事?
这里有几点需要理解:
- 设置
delegate
或Closure
有什么作用? - 为什么
repositoryName:'my-git-repo'
什么都不做? - 为什么
emailTo='testuser@domain.com'
在地图中设置属性?
What does setting a
delegate
of aClosure
do?
这一个大部分是直截了当的,但我认为它有助于理解。 Closure
is powerful and is the Swiss Army knife of Groovy. The delegate
essentially sets what the this
is in the body of the Closure
. You are also using the resolveStrategy
of Closure.DELEGATE_FIRST
, so methods and properties from the delegate are checked first, and then from the enclosing scope (owner) - see the Javadoc 以获得 in-depth 解释。如果调用 size()
、put(...)
、entrySet()
等方法,它们都会首先在 delegate
上调用。 属性访问也是如此。
Why does
repositoryName:'my-git-repo'
not do anything?
这似乎是 Groovy map literal, but it is not. These are actually labeled statements。如果您用 [repositoryName:'my-git-repo']
之类的方括号将其括起来,那么这就是地图文字。但是,这就是您在那里所做的一切——创建地图文字。我们要确保这些 objects 在 Closure
Why does
emailTo='testuser@domain.com'
set the property in the map?
这是使用 Groovy 的 map property notation 功能。如前所述,您将Closure
的delegate
设置为def args= [:]
,这是一个Map
。您还设置了 Closure.DELEGATE_FIRST
的 resolveStrategy
。这使您的 emailTo='testuser@domain.com'
决定在 args
上被调用,这就是 emailTo
键设置为该值的原因。这相当于调用 args.emailTo='testuser@domain.com'
.
那么,你如何解决这个问题?
如果您想保留 Closure
语法方法,您可以将调用的 body 更改为任何实质上在委托 args
映射中存储值的内容:
buildGitWebProject {
repositoryName = 'my-git-repo'
branchName = 'qa'
solutionName = 'my_csharp_solution.sln'
emailTo = 'testuser@domain.com'
}
buildGitWebProject {
put('repositoryName', 'my-git-repo')
put('branchName', 'qa')
put('solutionName', 'my_csharp_solution.sln')
put('emailTo', 'testuser@domain.com')
}
buildGitWebProject {
delegate.repositoryName = 'my-git-repo'
delegate.branchName = 'qa'
delegate.solutionName = 'my_csharp_solution.sln'
delegate.emailTo = 'testuser@domain.com'
}
buildGitWebProject {
// example of Map literal where the square brackets are not needed
putAll(
repositoryName:'my-git-repo',
branchName: 'qa',
solutionName: 'my_csharp_solution.sln',
emailTo: 'testuser@domain.com'
)
}
另一种方法是让您的 call
接受 Map
作为参数并删除您的 Closure
.
def call(Map args) {
// no more args and delegates needed right now
}
buildGitWebProject(
repositoryName: 'my-git-repo',
branchName: 'qa',
solutionName: 'my_csharp_solution.sln',
emailTo: 'testuser@domain.com'
)
还有一些其他方法可以为您的 API 建模,这取决于您要提供的用户体验。
关于共享库代码中声明性管道的旁注:
值得牢记共享库中声明性管道的局限性。看起来您已经在 vars
中这样做了,但为了完整起见,我只是将其添加到此处。在 documentation it is stated:
Only entire
pipeline
s can be defined in shared libraries as of this time. This can only be done invars/*.groovy
, and only in acall
method. Only one Declarative Pipeline can be executed in a single build, and if you attempt to execute a second one, your build will fail as a result.