笔者是独立翻译实验,不允许转载
Using a Jenkinsfile
这部分基于前面Getting Started所讲过的信息,介绍更有用的步骤(steps),一般模式,一些非试用的Jenkinsfile例子。
创建一个Jenkinsfile,然后把这个文件保存到源代码版本控制中(例如svn,git等),会带来许多好处:
- 对Pipeline中进行代码审查和迭代(review/iteration)
- Pipeline的审核和跟踪
- Pipeline唯一的真相来源,可以由项目的多个成员查看和编辑。
Pipeline支持两种语法: 声明式 (在Pipeline2.5 中引入) 和脚本Pipeline。两者都支持构建持续集成的Pipeline。都可以被用来定义一个Pipeline,不管是在web UI上还是在Jenkinsfile文件中,一般认为最好的实践是创建一个Jenkinsfile文件,并放到源代码管理库中。
Creating a Jenkinsfile
像前面Getting Started章节讨论的一样,Jenkinsfile文件是一个文本文件,包含一个Jenkins Pipeline定义。考虑如下部分,实现了一个三阶段的持续集成pipeline。
并不是所有的Pipelines都有同样的三个阶段,但是对于大多数项目来说,这是一个很好的开始。下面的部分将说明一个简单的Pipeline的创建和执行。
注意:假设已经为项目创建好了源代码版本库,并且一个Pipeline已经按照前面Getting Started步骤定义好了。
用一个支持Groovy语法高亮的文本编辑器,创建一个新的Jenkinsfile文件,放到项目的根目录下。
上面声明式的Pipeline例子包含实现一个持续集成pipeline最小的必须的结构。Agent指示符是必须的,告诉Jenkins为这个pipeline分配一个执行器和工作空间。没有agent指示符,不仅申明式的Pipeline不是合法的,而且它也不能做任何工作!默认情况下,agent指示符确保源代码库代码被签出,并且是有效的可以被后面阶段的步骤(steps)使用。
对于一个有效的申明式的Pipeline,stage指示符和steps指示符同样是必须的,因为它们告诉Jenkins执行什么,哪个阶段做什么事情。
对于脚本式Pipeline的更高级用法,上面例子中的node是至关重要的第一步,因为它为Pipeline申请了执行器和工作空间。确切的说,没有node,一个Pipeline不能做任何工作!在node里面,业务的第一个命令将是迁出源代码。因为Jenkinsfile文件是直接从源代码库中迁出,所以Pipeline提供了一个快速和容易的方式存取正确的源代码版本。
① checkout步骤将从源代码库中迁出代码;scm是一个特殊的变量,它将告诉checkout克隆特定的版本,然后触发Pipeline运行。
注意,笔者实测如下:
在脚本式Pipeline中
checkout([$class: ‘GitSCM’, branches: [[name: ‘*/master’]], doGenerateSubmoduleConfigurations: false, extensions: [], submoduleCfg: [], userRemoteConfigs: [[url: ‘https://gitee.com/roclli/simple-maven-project-with-tests.git']]])。申明式Pipeline(Jenkinsfile中),采用checkout scm,会导致checkout两次,根据说明Specify where to obtain a source code repository containing your Groovy script. It will be checked out somewhere on the Jenkins master and used to load your Pipeline script. (If you wish to use other files from the same repository during your Pipeline, you will need to check them out separately on some slave; this checkout cannot be reused.)。
Build
许多使用Pipeline的项目开始工作第一步都是”编译(Build)”阶段。通常情况下, Pipeline的此阶段将是源代码的组装、编译或打包。Jenkinsfile文件并不是替代现有的编译工具例如GNU/Make,Maven,Gradle等等,但可以看作是一个粘合层, 以粘合绑定(bind)项目开发的多个阶段生命周期 (构建、测试、部署等) 一起。
Jenkins有许多插件,可以调用几乎所有的构建工具,但是这个例子将简单的使用shell步骤(sh)调用make命令。sh假设系统是Unix/Linux,如果是Windows系统,将会使用bat。
① sh调用make命令,将一直运行,如果命令返回0。任何非0的返回值都将使Pipeline失败。
② archiveArtifacts捕获模式匹配(**/target/*.jar)文件,并且把他们保存到Jenkins的master中。
提示:存档文件不是使用外部文件资料库 (如 Artifactory 或Nexus) 的替代品, 应该只考虑基本的报告和档案存档。
Test
运行自动测试是任何成功持续集成的关键组件。因此Jenkins有许多测试记录,报告,可视化插件。一般来说, 当有测试失败, 它是有用的,Jenkins会记录web UI中的报告和可视化失败。下面的示例使用junit步骤, 由junit插件提供。
下面的例子,如果运行失败,Pipeline被标记不稳定”unstable”,在网页上用一个黄色的球标记。基于记录的测试报告,Jenkins同样提供历史趋势分析和可视化报告。
① 使用内置的shell条件(sh ‘make || true’)确保sh总是有一个0的返回值,这样使得junit机会去捕获和处理测试报告。或者使用下面的处理失败[handing-failures]部分。
② Junit捕获并关联Junit XML文件,模式匹配(**/target/*.xml)
注意:此处笔者测试下来是有出入的,区别在于脚本式Pipeline
- 示例的stage必须放到stages里面,是和stage(‘Build’)并行的兄弟节点。
- stage里的内容(sh和junit必须放到steps里面),否则会报错。
Deploy
部署可能意味着各种步骤, 具体取决于项目或组织的要求,可能是从将生成的文件发布到 Artifactory 服务器的任何东西, 或者将代码推送到生产系统。
在示例Pipeline的这个阶段,”Build”和”Test”阶段都已成功执行。因此发布阶段假设前面的阶段已经成功执行,否则Pipeline将退出。
① 读取currentBuild.result变量允许Pipeline判断是否有失败。这种情况之下,值是UNSTABLE。
假设示例的Jenkins Pipeline都被成功执行,每一个成功的Pipeline部分都将会关联到存档文件,测试结果报告和控制台输出。
脚本式的Pipeline能包含条件测试(像上面所示),循环(loop),try/catch/finally块,甚至函数。接下来的部分将详细讲解脚本式Pipeline语法的高级用法。
注意:笔者测试下来
(1).期间发现过无法读取currentBuild.result变量,此时提示” Cannot get property ‘currentBuild’ on null object”,故忽略此处的Deploy。env变量也是如此提示:Cannot get property ‘env’ on null object。最后实测发现:
脚本式Pipeline(web UI上面输入),可以正确输出变量值,如下方式${env.BUILD_NUMBER}
申明式Pipeline(Jenkinsfile),可以正确输出变量值,如下方式${env.BUILD_NUMBER}
可以正确用echo输出值,也可以用if判断值。代码如下:1
2
3
4
5
6
7
8echo "---${env.BUILD_NUMBER}---"
echo "---${currentBuild.result}---"
if(currentBuild.result == null || currentBuild.result == 'SUCCESS') {
echo "---currentBuild.result is:${currentBuild.result}------"
}
else {
echo "---currentBuild.result is:${currentBuild.result},so, will make publish"
}
得到的结果如下几种情况:
Cases结果 | 全局变量 | 备注 |
---|---|---|
1个skip,其他全对 | 61/null | |
1个error,其他全对 | 60/UNSTABLE | 根据前面说明,只要有失败,就是UNSTABLE |
全部正确 | 59/null |
(2).申明式Jenkins Pipeline(Jenkinsfile)中,使用steps有报错提示:
WorkflowScript: 31: Expected a step @ line 31, column 17.。解决办法:添加script符号1
2
3
4
5
6
7
8
9
10
11
12
13
14stage('Deploy'){
steps {
script{
echo "---${env.BUILD_NUMBER}---"
echo "---${currentBuild.result}---"
if(currentBuild.result == null || currentBuild.result == "SUCCESS") {
echo "---currentBuild.result is:${currentBuild.result}------"
}
else {
echo "---currentBuild.result is:${currentBuild.result},so, will make publish"
}
}
}
}
Pipeline高级语法(Advanced Syntax for Pipeline)
字符串插值(String Interpolation)
Jenkins Pipeline使用与 Groovy 相同的规则来进行字符串插值。Groovy的字符串插值规则可能会让这个语言的初学者感到混乱。因为Groovy支持申明一个字符串使用单引号或者双引号,例如:
只有后一个字符串将支持基于美元符号 ($) 的字符串插值, 例如:
运行结果是:
笔者加:测试运行结果如下:1
2Hello Mr. ${username}
I said, Hello Mr. Jenkins
对于Pipeline的高级特性,理解怎样使用字符串插值是很重要。
环境变量(Working with the Environment)
Jenkins Pipeline通过全局变量env暴露了许多环境变量,env可以在Jenkindfile中任何地方使用。Jenkins中完整的环境变量列表请参见:localhost:8080/pipeline-syntax/globals#env(假设Jenkins运行在本地的8080端口),环境变量包括:
BUILD_ID
当前的build ID,同BUILD_NUMBER一致。
JOB_NAME
被执行的项目的名字,例如foo或者foo/bar。
JENKINS_URL
Jenkins的全URL,例如example.com:port/Jenkins/(注意:只有”System COnfiguration”中设置Jenkins URL以后才有效)
引用或使用这些环境变量可以实现, 如访问Groovy 映射, 例如:
设置环境变量(Setting environment variables)
在申明式Pipeline或者脚本式Pipeline中设置环境变量是不同的。
申明式Pipeline支持环境申明,而脚本式Pipeline必须使用withEnv。
① 一个environment申明使用在pipeline块中,将对pipeline中的所有step有效。
② 在stage中的environment申明将只对stage内的step有效。
参数(Parameters)
声明式Pipeline支持现成的参数, 允许Pipeline接受用户通过参数指令在运行时指定参数。在脚本式Pipeline中配置参数,可以通过properties实现,关于properties我们可以在Snippet Generator中找到。
如果想要配置你的Pipeline接受Build With parameters的参数,那些参数是可以通过params变量读取。
假设一个名叫Greeting的字符串变量被配置在Jenkinsfile中,那么可以通过${params.Greeting}读取。
注意:笔者测试如下:本例如果是script(web UI上直接输入脚本方式)执行,会出现参数输入
如果是Jenkisnfile(声明式执行),会直接使用默认值Hello。
处理失败(Handling Failures)
申明式Pipeline支持健壮的错误处理,默认通过它的post section处理,post section允许申明许多不同的”post conditions”,例如always,unstable,success,failure,和changed。Pipeline
语法(Pipeline Syntax)部分提供更多的细节,怎样使用不同的post情况。
脚本式Pipeline依赖于Groovy内嵌的try/catch/finally语法处理Pipeline执行期间出现的错误。
在上面的测试例子中,sh被修改永远不返回非0值(sh ‘make check || true’)。这个方法意味着接下来的阶段需要检查currentBuild.result去判断是否存在错误发生。
另一种处理此问题的方法是使用一系列try/finally 块, 它可以保留Pipeline中故障的早期退出行为, 同时仍使 junit 有机会捕获测试报告:
注意:笔者实测,申明式添加了post;其次mail的各参数,必须用单引号括起来。
申明式(Jenkinsfile)邮件发送失败,以下两种方式都失败(脚本式Pipeline无邮件部分):
- mail to: ‘a@b.com’, subject: ‘The Pipeline(handing failure) failed :(‘, body: ‘this is body’
- emailext body: ‘this is body’, subject: ‘title’, to: ‘ a@b.com ‘
如果前面执行成功,那么就不会执行post里面的failure部分。
使用多个代理(Using multiple agents)
在所有前面的例子中,只使用一个代理。这意味着Jenkins分配的所有执行器,只有一个是有效的,无论它是如何被标记和配置。Pipeline允许在一个Jenkinsfile中使用多个代理,这对于高级的用户案例是很帮助的,例如在多个平台上执行builds/tests。
在下面的例子中,Build阶段在一个代理上执行,build的结果将被两个子代理重用,在test阶段,标记linux和windows的两个代理是相互独立的。
① stash允许捕获模式匹配的文件(/target/.jar),只在同一个Pipeline其他地方重用。一旦Pipeline完成了执行,stash捕获的文件将被Jenkins master删除。
② 在agent/node中的参数允许任何有效的Jenkins标签表达式。查阅”Pipeline Syntax” 部分可以更详细的信息。
③ unstash 将从Jenkins主机中检索stash到Pipeline的当前工作区中。
④ bat脚本允许在windows平台上执行脚本。
注意:笔者实测
(1).首先需要配置windows slave node,关于windows slave,需要配置master上已有的需要用到的环境变量:
(2).首先分别修改两个node的label为linux(master)和windows(slave)。
可选的step参数(Optional step arguments)
Pipeline遵循Groovy语言约定, 允许在方法参数周围省略括号。
许多Pipeline steps还使用命名参数语法作为在Groovy中创建映射的简写形式, 它使用语法 [key1: value1、key2: value2]。使语句像以下功能等效:
方便起见,当参数只有一个参数的时候,参数名可以省略,例如:
高级脚本Pipeline
脚本Pipeline是一个domain-specific语言,基于Groovy,大多数的Groovy语法都能不用修改被用在脚本Pipeline中。
并行执行(Executing in parallel)
上面部分中的示例在一个线性序列中跨两个不同的平台运行测试。实践中,如果make check需要花30分钟执行完,”Test”阶段将花费60分钟完成。
幸运的是,Pipeline具有内置的功能, 用于并行执行Pipeline脚本部分, 并在恰当命名的并行(parallel)步骤中实现。
重构上面的示例以使用并行(parallel)步骤:
不像原先的在linux和windows上线性执行,他们现在可以并行执行。