Published on

《Jenkins 2.x实践指南》读书笔记-Jenkins 2.x pipeline语法

Authors
  • avatar
    Name
    老杨的博客
    Twitter

[TOC]

1. 大概了解 Groovy

可以通过Groovy 教程来了解。

2. pipeline 介绍

Jenkins pipeline 其实就是基于 Groovy 语言实现的一种 DSL(领域特定语言),用于描述整条流水线是如何进行的。流水线的内容包括执行编译、打包、测试、输出测试报告等步骤。

2.1 pipeline 最简结构

pipeline {
    agent any
    stages {
        stage('Stage 1') {
            steps {
                echo 'Hello world!'
            }
        }
    }
}
  • pipeline:代表整条流水线,包含整条流水线的逻辑。
  • stage部分:阶段,代表流水线的阶段。每个阶段都必须有名称。本例中,build就是此阶段的名称。
  • stages部分:流水线中多个stage的容器。stages部分至少包含一个stage
  • steps部分:代表阶段中的一个或多个具体步骤(step)的容器。steps部分至少包含一个步骤,本例中,echo就是一个步骤。在一个stage中有且只有一个steps
  • agent部分:指定流水线的执行位置(Jenkins agent)。流水线中的每个阶段都必须在某个地方(物理机、虚拟机或 Docker 容器)执行,agent部分即指定具体在哪里执行。

更多更详细 pipeline 步骤参考文档: https://jenkins.io/zh/doc/pipeline/steps/

以上每一个部分(section)都是必需的,少一个,Jenkins 都会报错。

众所周知,jenkins 好用最大体现它的众多插件满足各种需求。并不是所有的插件都支持 pipeline 的。 jenkins 插件兼容 pipeline 列表: https://github.com/jenkinsci/pipeline-plugin/blob/master/COMPATIBILITY.md

2.2 post

post部分包含的是在整个 pipeline 或阶段完成后一些附加的步骤。post部分是可选的,所以并不包含在 pipeline 最简结构中。但这并不代表它作用不大。 根据 pipeline 或阶段的完成状态,post部分分成多种条件块,包括:

  • always:不论当前完成状态是什么,都执行。
  • changed:只要当前完成状态与上一次完成状态不同就执行。
  • fixed:上一次完成状态为失败或不稳定(unstable),当前完成状态为成功时执行。
  • regression:上一次完成状态为成功,当前完成状态为失败、不稳定或中止(aborted)时执行。
  • aborted:当前执行结果是中止状态时(一般为人为中止)执行。
  • failure:当前完成状态为失败时执行。
  • success:当前完成状态为成功时执行。
  • unstable:当前完成状态为不稳定时执行。
  • cleanup:清理条件块。不论当前完成状态是什么,在其他所有条件块执行完成后都执行。post部分可以同时包含多种条件块。以下是post部分的完整示例。
pipeline {
    agent any

    stages {
        stage('Build') {
            steps {
                sh 'echo Build stage ...'
            }
            post {
                always {
                    echo "post condition executed: always ..."
                }
                changed {
                    echo "post condition executed: changed ..."
                }
                aborted {
                    echo "post condition executed: aborted ..."
                }
                regression {
                    echo "post condition executed: regression ..."
                }
            }
        }

        stage('Test'){
            steps {
                sh 'echo Test stage ...'
            }
            post {
                aborted {
                    echo "post condition executed: aborted ..."
                }
                failure {
                    echo "post condition executed: failure ..."
                }
                success {
                    echo "post condition executed: success ..."
                }
            }
        }

        stage('Deploy') {
            steps {
                sh 'echo Deploy stage ...'
            }
        }
    }
        post {
        unstable {
            echo "post condition executed: unstable ..."
        }
        unsuccessful {
            echo "post condition executed: unsuccessful ..."
        }
        cleanup {
            echo "post condition executed: cleanup ..."
        }
    }
  }

2.3 pipeline 支持的指令

显然,基本结构满足不了现实多变的需求。所以,Jenkins pipeline 通过各种指令(directive)来丰富自己。指令可以被理解为对 Jenkins pipeline 基本结构的补充。 Jenkins pipeline 支持的指令有:

  • environment:用于设置环境变量,可定义在stagepipeline部分。
  • tools:可定义在pipelinestage部分。它会自动下载并安装我们指定的工具,并将其加入PATH变量中。
  • input:定义在stage部分,会暂停pipeline,提示你输入内容。
  • options:用于配置 Jenkins pipeline 本身的选项,比如options {retry(3)}指当pipeline失败时再重试 2 次。options指令可定义在stagepipeline部分。
  • parallel:并行执行多个step。在pipeline插件 1.2 版本后,parallel开始支持对多个阶段进行并行执行。
  • parameters:与input不同,parameters是执行pipeline前传入的一些参数。
  • triggers:用于定义执行pipeline的触发器。
  • when:当满足when定义的条件时,阶段才执行。

在使用指令时,需要注意的是每个指令都有自己的“作用域”。如果指令使用的位置不正确,Jenkins 将会报错。

2.4 配置 pipeline 本身

2.4.1 全局 options

options 指令允许从流水线内部配置特定于流水线的选项。 流水线提供了许多这样的选项, 比如 buildDiscarder,但也可以由插件提供, 比如 timestamps.

RequiredNo
ParametersNone
AllowedOnly once, inside the pipeline block.

可用选项

buildDiscarder 为最近的流水线运行的特定数量保存组件和控制台输出。例如: options { buildDiscarder(logRotator(numToKeepStr: '1')) }

disableConcurrentBuilds 不允许同时执行流水线。 可被用来防止同时访问共享资源等。 例如: options { disableConcurrentBuilds() }

overrideIndexTriggers 允许覆盖分支索引触发器的默认处理。 如果分支索引触发器在多分支或组织标签中禁用, options { overrideIndexTriggers(true) } 将只允许它们用于促工作。否则, options { overrideIndexTriggers(false) } 只会禁用改作业的分支索引触发器。

skipDefaultCheckoutagent 指令中,跳过从源代码控制中检出代码的默认情况。例如: options { skipDefaultCheckout() }

skipStagesAfterUnstable 一旦构建状态变得 UNSTABLE,跳过该阶段。例如: options { skipStagesAfterUnstable() }

checkoutToSubdirectory 在工作空间的子目录中自动地执行源代码控制检出。例如: options { checkoutToSubdirectory('foo') }

timeout 设置流水线运行的超时时间, 在此之后,Jenkins 将中止流水线。例如: options { timeout(time: 1, unit: 'HOURS') }

retry 在失败时, 重新尝试整个流水线的指定次数。 例如: options { retry(3) }

timestamps 预谋所有由流水线生成的控制台输出,与该流水线发出的时间一致。 例如: options { timestamps() }

newContainerPerStageagentdockerdockerfile时,指定在同一个 Jenkins 节点上,每个stage都分别运行在一个新的容器中,而不是所有stage都运行在同一个容器中。例如: options { newContainerPerStage() }

Example
pipeline {
    agent any
    options {
        timeout(time: 1, unit: 'HOURS')
    }
    stages {
        stage('Example') {
            steps {
                echo 'Hello World'
            }
        }
    }
}

指定一个小时的全局执行超时, 在此之后,Jenkins 将中止流水线运行。

2.4.2 阶段 option

stageoptions 指令类似于流水线根目录上的 options 指令。然而, stage -级别 options 只能包括 retry, timeout, 或 timestamps 等步骤, 或与 stage 相关的声明式选项,如 skipDefaultCheckout

stage, options 指令中的步骤在进入 agent 之前被调用或在 when 条件出现时进行检查。

可选的阶段选项

skipDefaultCheckoutagent 指令中跳过默认的从源代码控制中检出代码。例如: options { skipDefaultCheckout() }

timeout 设置此阶段的超时时间, 在此之后, Jenkins 会终止该阶段。 例如: options { timeout(time: 1, unit: 'HOURS') }

retry 在失败时, 重试此阶段指定次数。 例如: options { retry(3) }

timestamps 预谋此阶段生成的所有控制台输出以及该行发出的时间一致。例如: options { timestamps() }

Example
pipeline {
    agent any
    stages {
        stage('Example') {
            options {
                timeout(time: 1, unit: 'HOURS')
            }
            steps {
                echo 'Hello World'
            }
        }
    }
}

指定 Example 阶段的执行超时时间, 在此之后,Jenkins 将中止流水线运行。

2.4.3 在声明式 pipeline 中使用脚本

声明式 pipeline 是不能直接在steps块中写 Groovy 代码。 Jenkins pipeline 专门提供了一个script步骤,你能在script步骤中像写代码一样写 pipeline 逻辑。

pipeline {
    agent any
    stages {
        stage('Build') {
            steps {
                script {
                    result = sh (script: "git log -1|grep 'Release'", returnStatus: true)
                    echo "result: ${result}"
                }
            }
        }
    }
}

在 script 块中的其实就是 Groovy 代码。大多数时候,我们是不需要使用script步骤的。如果在script步骤中写了大量的逻辑,则说明你应该把这些逻辑拆分到不同的阶段,或者放到共享库中。共享库是一种扩展 Jenkins pipeline 的技术。

2.5 pipeline 内置基础步骤

这里介绍 pipeline 内置的一些步骤。

2.5.1 文件目录相关步骤

deleteDir 删除当前目录,它是一个无参步骤,删除的是当前工作目录。通常它与dir步骤一起使用,用于删除指定目录下的内容。

dir 切换到目录。默认 pipeline 工作在工作空间目录下,dir步骤可以让我们切换到其它目录。例如:dir("/var/logs") { deleteDir() }

fileExists 判断文件是否存在。fileExists('/tmp/a.jar')判断/tmp/a.jar文件是否存在。如果参数是相对路径,则判断在相对当前工作目录下,该文件是否存在。结果返回布尔类型。

isUnix 判断是否为类 Unix 系统。如果当前 pipeline 运行在一个类 Unix 系统上,则返回true

pwd 确认当前目录。pwd与 Linux 的pwd命令一样,返回当前所在目录。它有一个布尔类型的可选参数:tmp,如果参数值为true,则返回与当前工作空间关联的临时目录。

writeFile 将内容写入指定文件中。 writeFile支持的参数有:

  • file:文件路径,可以是绝对路径,也可以是相对路径。
  • text:要写入的文件内容。
  • encoding(可选):目标文件的编码。如果留空,则使用操作系统默认的编码。如果写的是 Base64 的数据,则可以使用 Base64 编码。

readFile 读取指定文件的内容,以文本返回。 readFile支持的参数有:

  • file:路径,可以是绝对路径,也可以是相对路径。
  • encoding(可选):读取文件时使用的编码。
script {
    // "amVua2lucyBib29r" 是"jenkins book"进行Base64编码后的值
    writeFile(file: "base64File", text: "amVua2lucyBib29r", encoding: "Base64")
    def content = readFile(file: "base64File", encoding: "UTF-8")
    echo "${content}"
    // 打印结果: jenkins book
}

2.5.2 制品相关步骤

stash 保存临时文件。 stash步骤可以将一些文件保存起来,以便被同一次构建的其他步骤或阶段使用。如果整个 pipeline 的所有阶段在同一台机器上执行,则stash步骤是多余的。所以,通常需要stash的文件都是要跨 Jenkins node 使用的。

stash步骤会将文件存储在tar文件中,对于大文件的stash操作将会消耗 Jenkins master 的计算资源。Jenkins 官方文档推荐,当文件大小为 5∼100MB 时,应该考虑使用其他替代方案。

stash步骤的参数列表如下:

  • name:字符串类型,保存文件的集合的唯一标识。
  • allowEmpty:布尔类型,允许stash内容为空。
  • excludes:字符串类型,将哪些文件排除。如果排除多个文件,则使用逗号分隔。留空代表不排除任何文件。
  • includes:字符串类型,stash哪些文件,留空代表当前文件夹下的所有文件。
  • useDefaultExcludes:布尔类型,如果为true,则代表使用 Ant 风格路径默认排除文件列表。

除了name参数,其他参数都是可选的。excludesincludes使用的是 Ant 风格路径表达式。

unstash 取出之前 stash 的文件。 unstash步骤只有一个name参数,即stash时的唯一标识。通常stash 与unstash步骤同时使用。以下是完整示例。

pipeline {
    agent none
    stages {
        stage('stash') {
            agent { label "master" }
            steps {
                script {
                    writeFile file: "a.txt", text: "$BUILD_NUMBER"
                    stash(name: "abc", include: "a.txt")
                }
            }
        }
        stage("unstash") {
            agent { label "node2" }
            steps {
                script {
                    unstash("abc")
                    def content = readFile("a.txt")
                    echo "${content}"
                }
            }
        }
    }
}

stash步骤在 master 节点上执行,而unstash步骤在 node2 节点上执行。

2.5.3 命令相关步骤

与命令相关的步骤其实是 Pipeline:Nodes and Processes 插件提供的步骤。由于它是 Pipeline 插件的一个组件,所以基本不需要单独安装。

sh 执行 shell 命令。 sh步骤支持的参数有:

  • script:将要执行的 shell 脚本,通常在类 UNIX 系统上可以是多行脚本。
  • encoding:脚本执行后输出日志的编码,默认值为脚本运行所在系统的编码。
  • returnStatus:布尔类型,默认脚本返回的是状态码,如果是一个非零的状态码,则会引发 pipeline 执行失败。如果returnStatus参数为true,则不论状态码是什么,pipeline 的执行都不会受影响。
  • returnStdout:布尔类型,如果为true,则任务的标准输出将作为步骤的返回值,而不是打印到构建日志中(如果有错误,则依然会打印到日志中)。除了script参数,其他参数都是可选的。

returnStatusreturnStdout参数一般不会同时使用,因为返回值只能有一个。如果同时使用,则只有 returnStatus 参数生效。

bat、powershell bat步骤执行的是 Windows 的批处理命令。powershell步骤执行的是 PowerShell 脚本,支持 3+版本。这两个步骤支持的参数与 sh 步骤的一样。

2.5.4 其他步骤

error 主动报错,中止当前 pipeline。 error 步骤的执行类似于抛出一个异常。它只有一个必需参数:message。通常省略参数:error("there's an error")tool 使用预定义的工具。 如果在 Global Tool Configuration(全局工具配置)中配置了工具,那么可以通过tool步骤得到工具路径。 tool步骤支持的参数有:

  • name:工具名称。
  • type(可选):工具类型,指该工具安装类的全路径类名。

每个插件的type值都不一样,而且绝大多数插件的文档根本不写type值。除了到该插件的源码中查找,还有一种方法可以让我们快速找到type值,就是前往 Jenkins pipeline 代码片段生成器中生成该tool步骤的代码即可。

timeout 代码块超时时间。 为 timeout 步骤闭包内运行的代码设置超时时间限制。如果超时,将抛出一个org.jenkinsci.plugins.workflow.steps.FlowInterruptedException异常。timeout步骤支持如下参数:

  • time:整型,超时时间。
  • unit(可选):时间单位,支持的值有NANOSECONDSMICROSECONDSMILLISECONDSSECONDSMINUTES(默认)、HOURSDAYS
  • activity(可选):布尔类型,如果值为true,则只有当日志没有活动后,才真正算作超时。

waitUntil 等待条件满足。 不断重复waitUntil块内的代码,直到条件为truewaitUntil不负责处理块内代码的异常,遇到异常时直接向外抛出。waitUntil步骤最好与timeout步骤共同使用,避免死循环。示例如下:

timeout(50) {
    waitUntil {
        script {
            def r = sh script: 'curl http://example', returnStatus: true
            retturn (r == 0)
        }
    }
}

retry 重复执行块 执行 N 次闭包内的脚本。如果其中某次执行抛出异常,则只中止本次执行,并不会中止整个retry的执行。同时,在执行retry的过程中,用户是无法中止 pipeline 的。

steps {
    retry(20) {
        script {
            sh script: 'curl http://example', returnStatus: true
        }
    }
}

sleep 让 pipeline 休眠一段时间。 sleep步骤可用于简单地暂停 pipeline,其支持的参数有:

  • time:整型,休眠时间。
  • unit(可选):时间单位,支持的值有NANOSECONDSMICROSECONDSMILLISECONDSSECONDS(默认)、MINUTESHOURSDAYS

2.5.5 小贴士

  1. Jenkins 提供了一个 pipeline 代码片段生成器,通过界面操作就可以生成代码。(只有 pipeline 项目有“Pipeline Syntax 菜单”)
  2. VS Code 扩展:Jenkins Pipeline Linter Connector,支持对 Jenkinsfile 的语法检验。
  3. 使用 Workspace Cleanup 插件清理空间。
  4. Ant 风格路径表达式。

Apache Ant 样式的路径有三种通配符匹配方法,利用它们可以组合出多种路径模式:

WildcardDescription
?匹配任意单字符
*匹配 0 或者任意数量的字符,不包含/
**匹配 0 或者更多数量的目录,不包含/

Ant 风格路径匹配实例:

PathDescription
/app/*.x匹配(Matches)app路径下所有.x文件
/app/p?ttern匹配(Matches) /app/pattern/app/pXttern,但是不包括/app/pttern
/**/example匹配项目根路径下 /project/example, /project/foow/example, 和 /example
/app/**/dir/file.匹配(Matches) /app/dir/file.jsp, /app/foo/dir/file.html,/app/foo/bar/dir/file.pdf, 和 /app/dir/file.java
/**/*.jsp匹配项目根路径下任何的.jsp 文件

需要注意的是,路径匹配遵循最长匹配原则(has more characters),例如/app/dir/file.jsp符合/*_/_.jsp/app/dir/*.jsp两个路径模式,那么最终就是根据后者来匹配。

参考资料: [1] 《Jenkins 2.x 实战指南》 [2] https://jenkins.io/zh/doc/book/pipeline/syntax/ [3] https://jenkins.io/zh/doc/pipeline/steps/