首页
使用 Generic Webhook Trigger 触发 Jenkins 多分支流水线自动化构建

在使用 Jenkins 驱动整个 CI/CD 的流程时,代码提交触发任务自动化构建是一个很重要的步骤。这篇文章主要介绍如何在 Jenkins 的多分支流水线(Multibranch Pipeline)中使用 Generic Webhook Trigger 触发任务的自动化构建。

需求

我们项目的源代码是托管在本地的 Gitlab 服务上,Git Flow 也是相对比较简单的。目前分为 FeatureSprintMaster分支。平时的开发是在Feature分支下,当Feature分支代码合并到Sprint分支的时候,需要能做到代码构建并发布到本地测试环境,当Sprint分支合并到Master的时候,需要能做到代码发布到线上测试环境。

对于多分支流水线的问题是,每次触发的可能是所有的分支构建,这显然是不符合要求的。因此,我们需要的是 webhook 可以按分支触发构建,同时根据不同的分支完成不同的构建脚本。这里面涉及到两个需求:

  1. webhook 按分支触发
  2. 不同分支的构建脚本差异化处理

webhook 按分支触发

首先,我们完成每次featuresprint分支有代码合并进去的时候,触发任务构建。这里需要借助到 Generic Webhook Trigger 插件。

Generic Webhook Trigger 插件

我们项目使用的Jenkinsfile是声明式脚本,在Generic Webhook Trigger的文档中,声明式脚本是这样使用的:

pipeline {
  agent any
  triggers {
    GenericTrigger(
     genericVariables: [
      [key: 'ref', value: '$.ref']
     ],
     causeString: 'Triggered on $ref',
     token: 'abc123',
     printContributedVariables: true,
     printPostContent: true,
     silentResponse: false,
     regexpFilterText: '$ref',
     regexpFilterExpression: 'refs/heads/' + BRANCH_NAME
    )
  }
  stages {
    stage('Some step') {
      steps {
        sh "echo $ref"
      }
    }
  }
}

文档中没有对这些配置项做具体的说明,对使用者造成了一定的门槛。在实践中,发现有几个我们需要比较关注的配置项。

token

Generic Webhook Trigger的触发 URL 是固定的:http://localhost:8080/jenkins/generic-webhook-trigger/invoke。通过token我们可以区分不同 job 的 trigger,同时该令牌还允许直接调用,而无需任何其他身份验证凭据。

也就是说,上面的token配置为abc123,我们可以在浏览器中通过访问http://localhost:8080/jenkins/generic-webhook-trigger/invoke?token=abc123触发该任务。

genericVariables

这个配置项的写法基本是固定的,从JSONPath中取出$.ref赋值给ref变量,我们就可以在脚本中使用ref变量获取到Gitref值。当然,我们也可以获取更多的内容复制给不同的变量,这个就看具体的需求了。

regexpFilterExpression

顾名思义,这是一个使用正则表达式的过滤器,也是我们做分支触发构建的关键因素。当满足该正则表达式的时候,trigger才会触发构建。

这里有一个小坑,就是\反斜杠在 Jenkins 中是用来转义字符的。举个例子,我们需要匹配sprint48这个分支,我们的正则表达式是这样的:

regexpFilterExpression: '^refs/heads/(features/sprint\d*)$'

这样写会报错的,因为\直接转义了,我们需要再加一个\,如下:

regexpFilterExpression: '^refs/heads/(features/sprint\\d*)$'

regexpFilterText

这个字段跟上面的regexpFilterExpression是成对使用的,正则表达式过滤的就是该字段的内容。我们前面把Gitref值赋值给了ref变量,这里就可以直接通过$ref来使用ref变量了。

regexpFilterText: '$ref',

Trigger 配置

通过前面对Generic Webhook Trigger插件的了解,我们可以针对自己的项目进行配置了。

token建议以项目名称开头,后面加上一定规则的哈希字符串,保证在Jenkinstoken不会出现重复。

regexpFilterExpression 我们的过滤规则是只接受sprintmaster分支

triggers{
    GenericTrigger(
        genericVariables: [
          [key: 'ref', value: '$.ref']
         ],
        causeString: 'Triggered on $ref',
        token: 'leangoo-b73b246d4bc9c5c9',
        printContributedVariables: true,
        printPostContent: true,
        silentResponse: false,
        regexpFilterText: '$ref',
        regexpFilterExpression: '^refs/heads/(features/sprint\\d*|master)$'
    )
}

到这里为止,Jenkins 这部分的配置已经全部完成了。接下来就是配置Gitlabwebhook了。

Gitlab webhook 配置

1. 进入项目的Settings -> Integrations 界面,新增一个webhook

url就是Jenkinstrigger地址,触发事件这里我们只勾选Push events,不需要SSL验证的话可以取消掉。

image_1ed0omaiv125ova51lq21spvg5o9.png-141.6kB

然后我们可以点击测试按钮进行测试。第一次尝试的时候,发现报url is blocked Requests to the local network are not allowed的错误,我们需要进入Gitlab的管理后台允许本地的请求。

2. 允许本地请求

这一步操作需要有管理员权限。进入Admin Area,依次选择Settings -> Network -> Outbound requests,勾选Allow requests to the local network from hooks and services

image_1ed0p3ed9ndi1vhd1pc918po1tt3m.png-75.1kB

再次点击测试按钮,可以成功地触发Jenkins的自动化构建。我们可以push代码到不同的分支测试一下配置是否正确。

不同分支的构建脚本差异化处理

前面的配置做到了sprintmaster分支有代码合并进来会触发自动化构建。但是对于多分支流水线,如果Jenkins检测到了分支的根目录下有Jenkinsfile文件,就会进行构建。最终导致的问题是,如果项目下有n个分支,每个分支都有Jenkinsfile文件,那么这n个分支都会构建一遍。这显然也不是我们想要的。

我们可以利用Jenkins pipelinewhen语法来做到让不同的分支来做不同的事情。when 的语法如下:

pipeline {
    agent any
    stages {
        stage('Example Build') {
            steps {
                echo 'Hello World'
            }
        }
        stage('Example Deploy') {
            when {
                branch 'production'
            }
            steps {
                echo 'Deploying'
            }
        }
    }
}

when指令允许管道根据给定条件确定是否应执行该阶段。when指令必须至少包含一个条件。如果when指令包含多个条件,则所有子条件必须返回true才能执行该阶段。

when 支持 expression 表达式,配合正则表达式可以构建出我们需要的条件。 比如这里,如果是mastersprint分支我们就会下面的步骤。

stage('build') {
            when {
                expression { BRANCH_NAME ==~ /(master|features\/sprint\d*)/ }
            }
            steps {
                sh '''
                    # 要执行的脚本
                    ...
                '''
            }
        }