
一、脚本化Pipeline
1.1 脚本化Pipeline
脚本化流水线, 与声明式一样的是, 是建立在底层流水线的子系统上的。与声明式不同的是, 脚本化流水线实际上是由 Groovy构建的通用 DSL 。 Groovy 语言提供的大部分功能都可以用于脚本化流水线的用户。这意味着它是一个非常有表现力和灵活的工具,可以通过它编写持续交付流水线。
1.2 流程控制
脚本化流水线从Jenkinsfile的顶部开始向下串行执行, 就像 Groovy 或其他语言中的大多数传统脚本一样。 因此,提供流控制取决于 Groovy 表达式, 比如 if/else 条件, 例如:
node {
stage('Example') {
if (env.BRANCH_NAME == 'master') {
echo 'I only execute on the master branch'
} else {
echo 'I execute elsewhere'
}
}
}
另一种方法是使用Groovy的异常处理支持来管理脚本化流水线流控制。当 步骤 失败 ,无论什么原因,它们都会抛出一个异常。处理错误的行为必须使用Groovy中的 try/catch/finally 块 , 例如:
node {
stage('Example') {
try {
sh 'exit 1'
}
catch (exc) {
echo 'Something failed, I should sound the klaxons!'
throw
}
}
}
二、Pipeline扩展——共享库
2.1 在pipene中定义函数
pipeline本质就是一个Groovy脚本。所以,可以在pipeline中定义函数,并使用Groovy语言自带的特性。如下代码示例,我们定义了createVersion函数,并使用了Date类。
pipeline {
agent any
stages {
stage('Build') {
steps {
echo "${createVersion(BUILD_NUMBER)}"
}
}
}
}
def createVersion(String BUILD_NUMBER) {
return new Date().format( 'yyMM' ) + "-${BUILD_NUMBER}"
}
日志打印
2006-4
[Pipeline] }
[Pipeline] // stage
[Pipeline] }
[Pipeline] // node
[Pipeline] End of Pipeline
Finished: SUCCESS
其实还有一种更加优雅的写法,将变量定义在evnvironment部分
pipeline{
agent any
environment {
_version = createVersion(BUILD_NUMBER)
}
stages {
stage('Build'){
steps {
echo "${_version}"
}
}
}
}
def createVersion(String BUILD_NUMBER) {
return new Date().format( 'yyMM' ) + "-${BUILD_NUMBER}"
}
如果在一个JenklnfiIe中定义一个函数倒是无伤大雅, 但是如果在20个JenklnfiIe中重复定义这个函数20遍,就有问题了——存在大量重复的代码,所以,不推荐大量使用自定义的函数
- 2.2 Jenkins共享库
2.2 Pipeline扩展——共享库
由于流水线被组织中越来越多的项目所采用,常见的模式很可能会出现。 在多个项目之间共享流水线有助于减少冗余并保持代码。流水线支持创建 “共享库” ,可以在外部源代码控制仓库中定义并加载到现有的流水线中。
src目录类似于标准Java源目录结构。执行流水线时,此目录将添加到类路径中。vars目录托管脚本文件,这些脚本文件在“管道”中作为变量公开。resources目录允许libraryResource从外部库中使用步骤来加载相关联的非Groovy文件。

$ tree JenkinslibTest
JenkinslibTest
├── README.md
├── src
│ └── org
│ └── devops
│ ├── build.groovy
│ └── tools.groovy
└── vars
└── hello.groovy
4 directories, 4 files
src: 类vars: 变量
tools.groovy
package org.devops
//Print content
def PrintMes(content){
println(content)
}
build.groovy
package org.devops
def PrintMes(){
println("Hello World!")
}
hello.groovy
def call(){
print("hello")
}
Global Pipeline Libraries

下面介绍一下配置项。
Name:共享库的唯一标识,在Jenkinsfile中会使用到。Default version:默认版本。可以是分支名、tag标签等。Load implicitly:隐式加载。如果勾选此项,将自动加载全局共享库,在Jenkinsfile中不需要显式引用,就可以直接使用。Allow default version to be overridden:如果勾选此项,则表示允许“Default version”被Jenkinsfile中的配置覆盖。Include @Library changes in job recent changes:如果勾选此项,那么共享库的最后变更信息会跟项目的变更信息一起被打印在构建日志中。Retrieval method:获取共享库代码的方法。我们选择“Modern SCM”选项,进而选择使用Git仓库。
提示: 除了可以使用Git仓库托管共享库代码,还可以使用SVN仓库托管。使用不同的代码仓库托管, “Default version” 的值的写法不一样。
2.3 简单使用库
标记Load的共享库隐含的允许管道立即使用任何此类库定义的类死全局变量。 要访问其他共享库, Jenkinsfile需要使用@Library注释,并指定库的名称
@Library('my-shared-library') _
/* Using a version specifierk such as branch, tag, etc */
@Library('my-shared-library@<version>') _
@Library('my-shared-library@1.0') _
/* Accessing multiple libraries with one statement */
@Library(['my-shared-library', 'otherlib@abc12341']) _
_: 代表引用
- 版本
- 分支,
@Library('my-shared-library@dev') _ tag标签,如@Library('my-shared-library@release1.0') _git commit id,如@Library('my-shared-library@e88044e73fea304a905dc00a1af2l97d945aa1e36') _
因为Jenkins交持同时添加多个共享库, 所以@Library注解还允许我们同时引入多个共享 库、如@Library( ['global-shared-library','my-shared-library@dev']) _。
需要注意的是,Jenkins处理多个共享库出现同名函数的方式是先定义者生效。也就是说如 global-shared-library与my-shared-library存在同名的sayHello, 而@Library引人时'global-shared-library' 在my-shared-library 前那么就只有global-shared-library的sayHello生效。
2.4 Jenkinsfile
@Library('jenkinslib') _
def tools = new org.devops.tools()
String workspace = "/home/vagrant/workspace"
//Pipeline
pipeline {
agent { node { label "hostmachine" //指定运行节点的标签或者名称
customWorkspace "${workspace}" //指定运行工作目录(可选)
}
}
options {
timestamps() //日志会有时间
skipDefaultCheckout() //删除隐式checkout scm 语句
disableConcurrentBuilds() //静止并行
timeout(time: 1, unit: 'HOURS') //流水线超时设置1h
}
stages {
//下载代码
stage("GetCode"){ //阶段名称
steps{ //名称
timeout(time:5, unit:"MINUTES"){ //步骤超时时间
script{ //填写运行代码
println('获取代码')
}
}
}
}
//构建
stage("Build"){
steps{
timeout(time:20, unit:"MINUTES"){
script{
println('应用打包')
mvnHome = tool "m2"
println(mvnHome)
sh "${mvnHome}/bin/mvn --version"
}
}
}
}
//代码扫描
stage("CodeScan"){
steps{
timeout(time:30, unit:"MINUTES"){
script{
print('代码扫描')
tools.PrintMes('This from shared Libray')
}
}
}
}
}

让我们看看build-log
Obtained Jenkinsfile from git https://github.com/Chao-Xi/JenkinslibTest.git
Running in Durability level: MAX_SURVIVABILITY
Loading library jenkinslib@master
Examining Chao-Xi/JenkinslibTest
Attempting to resolve master as a branch
Resolved master as branch master at revision c06d27a576408f760436629bc6b4948d8722ed89
using credential GitHub
Cloning the remote Git repository
Cloning with configured refspecs honoured and without tags
Cloning repository https://github.com/Chao-Xi/JenkinslibTest.git
...
19:07:38 代码扫描
[Pipeline] echo
19:07:38 This from shared Libray
...
2.4 共享库详细的介绍
回顾一下库目录
.
├── Jenkinsfile
├── Jenkinsfiletests
│ └── Jenkinsfile-mvn
├── README.md
├── src
│ └── org
│ ├── devops
│ │ ├── build.groovy
│ │ └── tools.groovy
│ └── showme
│ └── Utils.groovy
└── vars
├── hello.groovy
├── mvn.groovy
└── sayHello.groovy
6 directories, 9 files
首先看vars目录
放在vars目录下的是可以从pipeline直接调用的全局变量(使用变量这个名称实在有些奇怪)。变量的文件名即为在pipeline中调用的函数名。文件名为驼峰式(camelCased)的
使用vars目录下的全局变量可以调用Jenkins pipelne的步骤。正如sayHello.groovy脚本 直接使用了echo步骤
def call(String name = 'human'){
echo ”Hello, ${name).“
}
当我们在Jenkinsfile中写sayHello("world")时它实际调用的是sayHello. groovy文件中的call函数
call函数还支持接收闭包(Closure)。下例中,我们定义了一个mvn全局变量。
//var/mvn.groovy
def call(mvnExec) {
configFileProvider([configFile(fileId: 'maven-global-settings', variable: 'MAVEN_GLOBAL_ENV')]) {
mvnExec("${MAVEN_GLOBAI_ENV}")
}
}
以上call函数里的内容就是将configFileProvider哆嗦的写法封装在mvn变量中。这样我们就 可以更简捷地执行mvn命令了
@Library('global-shared-library@master') _
pipeline {
agent any
tools {
maven 'mvn-3.5.4'
}
stages {
stage('Build') {
steps
mvn { settings ->
sh "mvn -s ${settings} clean install"
}
}
}
}
}
接着我们来看src目录。
src目录是一个标准的Java源码结构,目录中的类被称为库类(Library class):而@Library(' global-shared-library@dev') _中的_代表一次性静态加载src目录下的所有代码到classpath中:
package org.showme
class Utils implements Serializable {
def getVersion(String BUILD_NUMBER ,String GIT_COMMIT){
return new Date().format( 'yyMM' ) + "-${BUILD_NUMBER}" + "-${GIT_COMMIT}"
}
}
提示:Utils实现了Serializable接口,是为了确保当pipeline被Jenkins挂起后能正确恢复。
在使用src目录中的类时,需要使用全包名。同时,因为写的是Groovy代码,所以还需要使 用script指令包起来, 示例如下:
@Library(['jenkinslib']) _
pipeline {
agent any
stages {
stage('Build') {
steps {
script{
def util = new org.showme.Utils()
def v = util.getVersion("${BUILD_NUMBER}", "${GIT_COMMIT}")
echo "${v}"
}
}
}
}
}
Script Path:./Jenkinsfiletests/Jenkinsfile-mvn
[Pipeline] { (Build)
[Pipeline] script
[Pipeline] {
[Pipeline] echo
2006-3-261d033b7fd541cadab80ddb110a3e7ddd93cdd7
[Pipeline] }
[Pipeline] // script
[Pipeline] }
[Pipeline] // stage
[Pipeline] }
[Pipeline] // withEnv
src目录中的类,还可以使用Groovy的。Grab注解,自动下载第三方依赖包。
package org.showme
@Grab(group = 'org.apache.commons', module = 'commons—lang3', version = '3.6')
import org.apache.commons.lang3.StringUtils
...
使用共享库实现pipeline模板
声明式pipeline在1.2版本后,可以在共享库中定义pipeline。通过此特性,我们可以定义pipeline的模板,根据不同的语言执行不同的pipeline。共享库代码如下:
// vars/generatePipeline.groovy
def call(String lang) {
if(lang == 'go') {
pipeline {
agent any
stages {
stage('set GOPATH') {
steps {
echo "GOPATH is ready"
}
}
}
}
} else if(lang == 'java') {
pipeline {
agent any
stages {
stage('clean install') {
steps {
sh "mvn clean install"
}
}
}
}
}
//其他语言
}
使用时,Jenkinsfile只有两行代码:
@Library(['jenkinslib']) _
generatePipeline('go')
如果大多数项目的结构都是标准化的,那么利用pipeline模板技术可以大大降低维护pipeline 的成本。
2.5 实践使用共享库格式化日志
本节使用前面一节课学习的共享库来实践,使用共享库来格式化日志输出。
2.6 安装ansicolor插件
注意:我们需要安装 插件AnsiColor,这样才能使用ansiColor()方法,可以在片段生成器查看更多的用法。
https://plugins.jenkins.io/ansicolor/
2.6 增加格式化输出方法
tools.groovy
package org.devops
//格式化输出
def PrintMes(value,color){
colors = ['red' : "\033[40;31m >>>>>>>>>>>${value}<<<<<<<<<<< \033[0m",
'blue' : "\033[47;34m ${value} \033[0m",
'green' : "[1;32m>>>>>>>>>>${value}>>>>>>>>>>[m",
'green1' : "\033[40;32m >>>>>>>>>>>${value}<<<<<<<<<<< \033[0m" ]
ansiColor('xterm') {
println(colors[color])
}
}
2.6 修改Jenkinsfile
在Jenkinsfile中使用@Library('jenkinslib') _来加载共享库,注意后面符号_用于加载。类的实例化def tools = new org.devops.tools(),使用类中的方法tools.PrintMes("获取代码",'green')。
@Library('jenkinslib') _
def tools = new org.devops.tools()
String workspace = "/home/vagrant/workspace"
//Pipeline
pipeline {
agent { node { label "hostmachine" //指定运行节点的标签或者名称
customWorkspace "${workspace}" //指定运行工作目录(可选)
}
}
options {
timestamps() //日志会有时间
skipDefaultCheckout() //删除隐式checkout scm 语句
disableConcurrentBuilds() //静止并行
timeout(time: 1, unit: 'HOURS') //流水线超时设置1h
}
stages {
//下载代码
stage("GetCode"){ //阶段名称
steps{ //名称
timeout(time:5, unit:"MINUTES"){ //步骤超时时间
script{ //填写运行代码
println('获取代码')
tools.PrintMes('代码扫描','green1')
}
}
}
}
//构建
stage("Build"){
steps{
timeout(time:20, unit:"MINUTES"){
script{
println('应用打包')
tools.PrintMes('代码扫描','blue')
mvnHome = tool "m2"
println(mvnHome)
sh "${mvnHome}/bin/mvn --version"
}
}
}
}
//代码扫描
stage("CodeScan"){
steps{
timeout(time:30, unit:"MINUTES"){
script{
print('代码扫描')
tools.PrintMes('代码扫描','green')
}
}
}
}
}
接下来在你的Jenkins上面运行一下吧,测试一下是否能够看到带有绿色的文字呢?
三、如何快速上手Jenkinsfile编写
3.1 如何快速上手Jenkinsfile编写
1、片段生成器
2、声明式语法生成器
3、全局变量参考
4、常用的流水线方法
3.2 片段生成器
使用片段生成器可以根据个人需要生成方法,有些方法来源于插件,则需要先安装相关的插件才能使用哦。

填写好参数后,点击生成,然后复制粘贴到Jenkinsfile。

3.3 声明式语法生成器
对于不熟悉声明式语法的伙伴,我们可以按照声明式语法生成器选择生成jenkinsfile的片段。在实际实践中很有帮助的。

3.4 全局变量
当我们在配置邮件通知时或者制作标签时,可以借用Jenkins自带的全局变量完成。
3.4 常用的env变量
BUILD_NUMBER //构建号
BUILD_ID //构建号
BUILD_DISPLAY_NAME //构建显示名称
JOB_NAME //项目名称
EXECUTOR_NUMBER //执行器数量
NODE_NAME //构建节点名称
WORKSPACE //工作目录
JENKINS_HOME //Jenkins home
JENKINS_URL //Jenkins地址
BUILD_URL //构建地址
JOB_URL //项目地址
3.4 常用的currendBuild变量
result currentResult //构建结果
displayName //构建名称 #111
description //构建描述
duration //持续时间
3.5 常用的流水线方法
3.5 JSON处理
def response = readJSON text: "${scanResult}"
println(scanResult)
//原生方法
import groovy.json.*
@NonCPS
def GetJson(text){
def prettyJson = JsonOutput.prettyPrint(text)
new JsonSlurperClassic().parseText(prettyJson)
}
3.5 使用凭据
withCredentials([string(credentialsId: "xxxxx", variable: "sonarToken")]) {
println(sonarToken)
}
3.5 下载代码
//Git
checkout([$class: 'GitSCM', branches: [[name: "brnachName"]],
doGenerateSubmoduleConfigurations: false,
extensions: [], submoduleCfg: [],
userRemoteConfigs: [[credentialsId: "${credentialsId}",
url: "${srcUrl}"]]])
//Svn
checkout([$class: 'SubversionSCM', additionalCredentials: [],
filterChangelog: false, ignoreDirPropChanges: false,
locations: [[credentialsId: "${credentialsId}",
depthOption: 'infinity', ignoreExternalsOption: true,
remote: "${svnUrl}"]], workspaceUpdater: [$class: 'CheckoutUpdater']]
)
3.5 展示报告
publishHTML([allowMissing: false,
alwaysLinkToLastBuild: false,
keepAll: true,
reportDir: './report/',
reportFiles: "a.html, b.html",
reportName: 'InterfaceTestReport',
reportTitles: 'HTML'])
3.5 交互输入
def result = input message: '选择xxxxx',
ok: '提交',
parameters: [extendedChoice( description: 'xxxxx',
descriptionPropertyValue: '',
multiSelectDelimiter: ',',
name: 'failePositiveCases',
quoteValue: false,
saveJSONParameterToFile: false,
type: 'PT_CHECKBOX',
value: "1,2,3",
visibleItemCount: 99)]
println(result)
3.5 构建用户
wrap([$class: 'BuildUser']){
echo "full name is $BUILD_USER"
echo "user id is $BUILD_USER_ID"
echo "user email is $BUILD_USER_EMAIL"
}
3.5 发送http请求
ApiUrl = "http://xxxxxx/api/project_branches/list?project=${projectName}"
Result = httpRequest authentication: 'xxxxxxxxx',
quiet: true,
contentType: 'APPLICATION_JSON' ,
url: "${ApiUrl}"







暂无评论内容