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

Rob*_*der 2 jenkins jenkins-pipeline jenkins-declarative-pipeline

我已经尝试了一段时间,开始努力将我们的自由风格项目转移到管道上.为此,我觉得最好建立一个共享库,因为我们的大多数构建都是一样的.我通读了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)
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

在我的管道项目中,我将Pipeline配置为使用Pipeline脚本,脚本如下:

buildGitWebProject {
    repositoryName:'my-git-repo'
    branchName: 'qa'
    solutionName: 'my_csharp_solution.sln'
    emailTo='testuser@domain.com'
}
Run Code Online (Sandbox Code Playgroud)

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

mko*_*bit 7

你的Closure身体没有按照你期望/相信它的方式行事.

在您的方法开始时,您有:

def call(body) {
  def args= [:]
  body.resolveStrategy = Closure.DELEGATE_FIRST
  body.delegate = args
  body()
Run Code Online (Sandbox Code Playgroud)

你的通话机构是:

buildGitWebProject {
    repositoryName:'my-git-repo'
    branchName: 'qa'
    solutionName: 'my_csharp_solution.sln'
    emailTo='testuser@domain.com'
}
Run Code Online (Sandbox Code Playgroud)

让我们来试试这个.

如果你在方法println(args)之后添加一个,你会看到如下内容:body()call(body)

[emailTo:testuser@domain.com]

但是,只有一个值得到了设定.到底是怎么回事?

这里有几点需要了解:

  1. 设置delegate一个Closure做什么?
  2. 为什么repositoryName:'my-git-repo'不做任何事情?
  3. 为什么要emailTo='testuser@domain.com'在地图中设置属性?

设置delegate一个Closure做什么?

这一点大多是直截了当的,但我认为这有助于理解.Closure是强大的,是Groovy的瑞士军刀.在delegate本质上设置什么this是的身体Closure.您也使用了resolveStrategyof Closure.DELEGATE_FIRST,因此委托中的方法和属性可以在委托中进行搜索,然后从封闭范围(所有者)进行检查 - 请参阅Javadoc以获得深入的解释.如果调用类似的方法size(),put(...),entrySet(),等等,它们都是第一次呼吁delegate.财产访问也是如此.

为什么repositoryName:'my-git-repo'不做任何事情?

这似乎是一个Groovy地图文字,但事实并非如此.这些实际上是标签语句.如果你用方括号包围它,[repositoryName:'my-git-repo']那么就像地图文字一样.但是,这就是你在那里所做的一切 - 创建一个地图文字.我们希望确保在这些对象中使​​用这些对象Closure

为什么要emailTo='testuser@domain.com'在地图中设置属性?

这是使用Groovy 的map属性表示法功能.如前所述,你已经设置delegateClosureto def args= [:],这是一个Map.您还可以设置resolveStrategyClosure.DELEGATE_FIRST.这使您emailTo='testuser@domain.com'可以调用args,这就是将emailTo键设置为值的原因.这相当于打电话args.emailTo='testuser@domain.com'.

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

如果要保持Closure语法方法,可以将调用主体更改为在委派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'
  )
}
Run Code Online (Sandbox Code Playgroud)

另一种方法是让你call把它Map作为一个参数并删除你的Closure.

def call(Map args) {
    // no more args and delegates needed right now
}
Run Code Online (Sandbox Code Playgroud)
buildGitWebProject(
    repositoryName: 'my-git-repo',
    branchName: 'qa',
    solutionName: 'my_csharp_solution.sln',
    emailTo: 'testuser@domain.com'
)
Run Code Online (Sandbox Code Playgroud)

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


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

值得记住共享库中声明性管道的局限性.看起来你已经在做了vars,但我只是为了完整性而添加它.在文档的最后说明:

到目前为止,只能pipeline在共享库中定义整个s.这vars/*.groovy只能在call方法中完成,并且只能在方法中完成.在单个构建中只能执行一个Declarative Pipeline,如果您尝试执行第二个,那么构建将因此失败.