带有命名参数的自定义jenkins声明性管道dsl

时间:2018-03-23 13:10:39

标签: jenkins jenkins-pipeline jenkins-declarative-pipeline

我现在已经尝试了一段时间,开始努力将我们的自由风格项目转移到管道上。为此,我觉得最好建立一个共享库,因为我们的大多数构建都是一样的。我读完了这个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脚本,脚本如下:

buildGitWebProject {
    repositoryName:'my-git-repo'
    branchName: 'qa'
    solutionName: 'my_csharp_solution.sln'
    emailTo='testuser@domain.com'
}

我尝试使用和不使用环境块,但结果最终与每个参数的值为“null”相同。奇怪的是,代码的脚本部分也没有使构建失败......所以不确定这有什么问题。回声部分也显示为空。我做错了什么?

1 个答案:

答案 0 :(得分:5)

你的Closure身体的行为不符合您的预期/相信它。

在方法的开头,你有:

def call(body) {
  def args= [:]
  body.resolveStrategy = Closure.DELEGATE_FIRST
  body.delegate = args
  body()

您的通话机构是:

buildGitWebProject {
    repositoryName:'my-git-repo'
    branchName: 'qa'
    solutionName: 'my_csharp_solution.sln'
    emailTo='testuser@domain.com'
}

让我们尝试调试一下。

如果您在println(args)方法的body()之后添加call(body),您会看到以下内容:

  

[emailTo:testuser@domain.com]

但是,只有一个值得到了设定。发生了什么事?

这里有几点需要了解:

  1. 设置delegate的{​​{1}}做什么?
  2. Closure为什么不做任何事情?
  3. 为什么repositoryName:'my-git-repo'在地图中设置了属性?
  4.   

    设置emailTo='testuser@domain.com'的{​​{1}}做什么?

    这个很简单,但我认为这有助于理解。 Closure是强大的,是Groovy的瑞士军刀。 delegate基本上设置了Closure正文中delegate的内容。您还使用了this Closure,因此委托中的方法和属性可以在委托中进行搜索,然后从封闭范围(所有者)进行检查 - 请参阅Javadoc深入解释。如果您调用resolveStrategyClosure.DELEGATE_FIRSTsize()等方法,则会首先在put(...)上调用它们。对于财产访问也是如此。

      

    entrySet()为什么不做任何事情?

    这似乎是Groovy map literal,但事实并非如此。这些实际上是labeled statements。如果用delegate之类的方括号代替它,那么这将是一个地图文字。但是,这就是你在那里所做的一切 - 创建一个地图文字。我们希望确保在repositoryName:'my-git-repo'

    中使用这些对象
      

    为什么[repositoryName:'my-git-repo']在地图中设置了属性?

    这是使用Groovy的map property notation功能。如前所述,您已将Closure的{​​{1}}设置为emailTo='testuser@domain.com',即delegate。您还设置了Closure的{​​{1}}。这样就可以def args= [:]Map上调用resolveStrategy,这就是将Closure.DELEGATE_FIRST键设置为值的原因。这相当于调用emailTo='testuser@domain.com'

    那么,你如何解决这个问题?

    如果您想保留args语法方法,可以将通话主体更改为在委派的emailTo地图中实际存储值的任何内容:

    args.emailTo='testuser@domain.com'

    另一种方法是让Closureargs作为参数并删除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

    您还可以使用其他一些方法对API进行建模,这取决于您要提供的用户体验。

    关于共享库代码中声明性管道的附注:

    值得记住共享库中声明性管道的局限性。看起来您已经在Closure中进行了此操作,但我只是为了完整性而在此处添加它。在documentation it is stated

    的最后
      

    到目前为止,只能在共享库中定义整个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' ) 中完成,并且只能在vars方法中完成。在一个构建中只能执行一个Declarative Pipeline,如果您尝试执行第二个,那么构建将因此失败。