learn-tech/专栏/Jenkins持续交付和持续部署/10.实现自动化引擎之JenkinsPipeline脚本式语法.md
2024-10-16 06:37:41 +08:00

21 KiB
Raw Blame History

                        因收到Google相关通知网站将会择期关闭。相关通知内容
                        
                        
                        10.实现自动化引擎之Jenkins Pipeline脚本式语法
                        前面章节介绍了pipeline的声明式语法 本节通过对比来介绍一下脚本式语法的使用。虽然两种类型的语法都是建立在底层流水线的子系统上的语法但是脚本式流水线在使用一些常用插件的时候显得更加容易脚本式流水线只有stage指令在声明式流水线中的stages和steps关键字在脚本式流水线中是不能使用的。下面针对在声明式流水线中介绍到的部分关键字介绍一下在脚本式流水线中的实现方法。

node

声明式流水线中使用agent关键字指定执行Jenkins流水线的代理在脚本式流水线中通过node关键字来指定代理节点。

基本示例如下

node('jenkins-slave1') { stage('test1') { sh 'hostname' }

}

其中jenkins-slave为jenkins代理节点的名称或者标签名称如果”()“为空则默认会从所有的节点中选择一个slave节点

除了可以为所有stage提供一个全局的代理节点以外也可以为不同的stage提供不同的代理节点。

如下示例

node() { stage('test-node'){ node('jenkins-slave1'){ stage('test1'){ sh 'hostname' }
} } stage('test-node2'){ node('jenkins-slave169'){ stage('test2'){ sh 'hostname' } } } }

该示例为不同的stage步骤使用了不同的slave节点

使用node也可以用容器作为agent比如

node() { docker.image('maven').inside { stage('te') { sh "hostname" } } docker.image('nginx').inside { stage('te1') { sh "hostname" } } }

该配置针对不同的流水线stage使用不同的镜像启动容器提供脚本执行的环境docker.image().inside()是docker pipeline插件里的一个指定镜像并启动容器的方法在以后的章节会介绍这里只需要知道可以这么操作即可

基于上面示例的思考,是不是可以在不同的节点上分别启动容器来进行不同的工作呢?答案是可以的,如下示例

node() { stage('test1'){ node('slave1'){ docker.image('maven').inside { stage('te') { sh "hostname" } } } } stage('test2'){ node('slave2'){ docker.image('nginx').inside { stage('te1') { sh "hostname" } } } } }

该示例会在两个slave节点上分别启动容器作为流水线的执行环境。既然使用脚本式语法能实现在不同的slave节点启动不同的容器在声明式语法中也能做到在以后的章节中会有说明

tool

声明式流水线中使用tools定义在jenkins系统中设置的工具的环境变量在脚本式流水线中使用def 和tool关键字来使用这些工具例如

node{ def maven = tool name: 'maven-3.5.4' env.PATH = "${maven}/bin:${env.PATH}" stage('test'){ sh 'mvn --version' } }

该指令的语法片段也可以通过片段生成器生成。

withEnv

在声明式pipeline中通过environment来定义流水线中用到的环境变量而在脚本式pipeline中使用withEnv来定义全局或者局部环境变量。

比如定义maven的环境变量

node() { withEnv(["PATH+MAVEN=${tool 'maven-3.5.4'}/bin"]) { sh 'mvn --version' } }

也可以写成这样

def maven = 'maven-3.5.4' withEnv(["PATH+MAVEN=${tool maven}/bin"]) { sh 'mvn --version'

}

也可以直接指定实际的值

withEnv(['MYTOOL_HOME=/usr/local/']) { sh '$MYTOOL_HOME/bin/start' }

parallel(并行)

parallel除了可以使用声明式语法定义外也可以使用脚本式语法定义。

下面的例子可以放到脚本式语法中执行

node { stage('Test') { parallel slave1: { node('jenkins-slave1') { try { sh 'hostname' } finally { echo "test" } } }, slave169: { node('jenkins-slave169') { sh 'hostname' } } } }

同时在两个节点执行shell命令

properties

声明式脚本通过parameters来定义流水线中使用到的参数在脚本式语法中则需要通过properties关键字来定义在stage中使用的参数可以通过片段生成器生参考下面成相应的语法片段。

如下示例-

生成的语法片段如下

properties([ buildDiscarder( logRotator(artifactDaysToKeepStr: '', artifactNumToKeepStr: '', daysToKeepStr: '2', numToKeepStr: '4') ), parameters([string(defaultValue: 'v1', description: '镜像tag', name: 'tag', trim: true) ]) ])

示例如下

properties([ buildDiscarder( logRotator(artifactDaysToKeepStr: '', artifactNumToKeepStr: '', daysToKeepStr: '2', numToKeepStr: '4') ), parameters([ string( defaultValue: 'v1', description: '镜像tag', name: 'tag', trim: true ) ]) ])

node{ stage('test1'){ echo "${params.tag}" } }

这里需要说明的是properties语法片段既可以放到node{}里也可以放到node{}上面

异常处理

在使用jenkins的时候难免遇到job执行失败的情况。声明式语法中可以通过post关键字对故障进行处理而在脚本式流水线中则需要使用 try/catch/finally块来处理故障。当step失败 ,无论什么原因,它们都会抛出一个异常。

如下示例

node { stage('Example') { try { sh 'exit 1' currentBuild.result = 'SUCCESS' } catch (exc) { currentBuild.result = 'FAILURE' throw exc }finally{ if(currentBuild.currentResult == "ABORTED" || currentBuild.currentResult == "FAILURE" || currentBuild.currentResult == "UNSTABLE"') { echo "---currentBuild.currentResult result is:${currentBuild.currentResult }" } else { echo "---currentBuild.currentResult result is:${currentBuild.currentResult }" } } } }

需要注意的是try语句后面一定要有catch或者finally语句catch与finally语句可以同时存在也可以只存在一个。

Jenkinsfile

通过学习pipeline的两种语法对于如何编写脚本式和声明式pipeline脚本应该有了基本的认识实际工作中使用的jenkinsfile大多数是用声明式脚本编写的当然脚本式也能用)至于在什么情况下使用哪种类型的语法取决于个人对两种语法的熟悉程度。有些情况下比如在后面章节要用到的docker插件kubernetes插件使用脚本式语法可以很方便的与这些插件集成而如果使用声明式语法就会复杂一些所以在准备使用pipeline之前有必要先做一下调研。

jenkinsfile脚本文件通常用于放到源码仓库中在配置jenkins流水线项目时通过checkout script from SCM的方式拉取jenkinsfile和所需的文件拉取下来以后会自动进行流水线操作。

比如在定义流水线脚本使用方式时使用Pipeline Script的方式配置

node { checkout scm }

说明

checkout scm这里应该为实际的拉取代码的指令用于从代码仓库拉取代码。比如git clone等。 此时放到Jenkinsfile中的流水线脚本就不需要使用node{}块包含了

也可以通过Pipeline script from SCM的方式配置如下所示-

需要注意的是脚本路径指定的文件名称为从git仓库获取的根目录下的文件名称。文件拉取到本地后就会自动执行流水线操作。

了解了Jenkinsfile文件如何使用后下面介绍一下针对两种pipeline语法都能使用的一些指令和关键字。

变量

pipeline脚本支持使用变量。在pipeline中的变量分为多种形式可以自定义变量可以使用jenkins的环境变量也可以将通过命令获取的结果动态设置为变量等。下面对这些变量一一进行介绍

自定义变量

Jenkins 使用与 Groovy 相同的规则进行变量赋值。Groovy 支持使用单引号或双引号声明一个字符串,例如:

def singlyQuoted = 'Hello' def doublyQuoted = "World"

对于变量的引用,可以使用单引号和双引号。如果没有特殊字符,用单引号和双引号是一样的;如果要执行多行命令,用三个单引号或三个双引号。如果有特殊字符需要解释,用双引号,需要转义的情况,用\转义符。

在单引号之间的所有特殊字符都失去了特殊含义;而在双引号之间的绝大多数特殊字符都失去了特殊含义,除了以下特例:

$ 美元号用来提取变量的值 ` 反冒号用于执行命令 \ 反斜杠用来转义字符

比如:

def username = 'Jenkins' echo 'Hello Mr. ${username}' echo "I said, Hello Mr. ${username}"

其结果是:

Hello Mr. ${username} I said, Hello Mr. Jenkins

执行命令示例如下:

sh ''' whoami pwd ls -ltra '''

结果

  • whoami root
  • pwd /var/lib/jenkins/workspace/test-mytest
  • ls -ltra total 4 drwxr-xr-x 2 root root 6 Mar 16 17:02 . drwxr-xr-x 8 root root 4096 Mar 16 17:02 ..

而对于定义多个变量如果每个变量都都使用def指令定义那酒显得有些重复造轮子了。groovy官方也给出了解决办法对于上面的示例可以写成这样

def cc=[username='jenkins',version="v2.19.0"]

引用时也简单

echo "$cc.usernmae $cc.version"

如果要在脚本式语法中使用通过def命令定义的变量需要使用script{}块将变量定义的步骤包含起来。

环境变量

在 Jenkins 流水线中可以定义整个流水线使用的环境变量也可以使用jenkins自带的环境变量。

对于如何自定义环境变量取决于使用的是声明式还是脚本式流水线语法它们设置环境变量的方法不同。关于在声明式和脚本式语法中使用环境变量的方法在前面已经介绍过这里不再多说下面看一下使用jenkins内置的环境变量。

Jenkins 流水线通过全局变量 env 提供环境变量,它在 Jenkinsfile 文件的任何地方都可以使用。Jenkins 流水线中可访问的完整的环境变量列表记录在 ${YOUR_JENKINS_URL}/pipeline-syntax/globals#env

比如:

BUILD_ID- 当前构建的 ID与早期jenkins版本的 BUILD_NUMBER 是完全相同的。

BUILD_NUMBER- 当前构建号,比如 “153”。

BUILD_TAG- 字符串 jenkins-${JOB_NAME}-${BUILD_NUMBER}。可以放到源代码、jar 等文件中便于识别。

BUILD_URL- 可以定位此次构建结果的 URL比如 http://buildserver/jenkins/job/MyJobName/17/

EXECUTOR_NUMBER- 用于识别执行当前构建的执行者的唯一编号(在同一台机器的所有执行者中)。这个就是你在“构建执行状态”中看到的编号,只不过编号从 0 开始,而不是 1。

JAVA_HOME- 如果你的任务配置了特定的 JDK工具那么这个变量就被设置为此 JDK 的 JAVA_HOME。当设置了此变量时PATH 也将包括 JAVA_HOME 的 bin 子目录。

JENKINS_URL- Jenkins 服务器的完整 URL比如 https://example.com:port/jenkins/ (注意:只有在“系统设置”中设置了 Jenkins URL 才可用)。

JOB_NAME- 本次构建的项目名称

NODE_NAME- 运行本次构建的节点名称。对于 master 节点则为 “master”。

WORKSPACE- workspace 的绝对路径。也是job的路径

……

更多变量内容参考pipeline-sytanx即可

示例如下

pipeline { agent any stages { stage('Example') { steps { echo "Running ${env.BUILD_ID} on ${env.JENKINS_URL}" } } } }

动态设置变量

环境变量可以在运行时设置可以通过shell命令linux sh、Windows 批处理脚本bat和 Powershell 脚本powershell获取变量的值并提供给下面的各stage阶段使用。各种脚本都可以返回 returnStatus 或 returnStdout。

下面是一个使用 shshell的声明式脚本的例子既有 returnStatus 也有 returnStdout

pipeline { agent any environment { // 使用 returnStdout CC = """ ${sh( returnStdout: true, script: 'echo "clang"' )} """

    // 使用 returnStatus
    EXIT_STATUS = """
        ${sh(
            returnStatus: true,
            script: 'exit 1'
        )}
    """
}
stages {
    stage('Example') {
        environment {
            DEBUG_FLAGS = '-g'
        }
        steps {
            sh 'printenv'
        }
    }
}

}

说明:

使用 returnStdout 时,返回的字符串末尾会追加一个空格。可以使用 .trim() 将其移除。 该指令可以通过片段生成器生成语法片段

相对于脚本式语法就简单的多

node { stage('s'){ script { cc=sh(returnStdout: true, script: 'hostname').trim() } echo "$cc" } }

条件判断

Jenkinsfile 从顶部开始向下串行执行执行过程中难免遇到使用条件判断的情况声明式语法中可以通过when关键字做一些基础的判断但是在脚本式语法中无法使用when关键字。而使用if/else语句在两种pipeline语法中都可以使用并且提高条件判断的灵活性。

在pipeline中使用的if/else语句同样遵循Groovy语法如下示例

node { stage('Example') { if (env.BRANCH_NAME == 'master') { echo 'I only execute on the master branch' } else { echo 'I execute elsewhere' } } }

或者如下示例

script{ test_result=sh(script:"ls /tmp/uu.txt",returnStatus:true) echo test_result if(test_result == 0){ echo "file is exist"
}else if(test_resultt == 2){ echo "file is not exist" }else{ error("command is error,please check")
} } }

fileExists

该关键字用来判断当前工作空间下指定的文件是否存在,文件的路径设定时使用基于当前工作空间的相对路径,返回的结果为布尔值。该关键字的使用语法可以通过片段生成器来生成。

如下示例

script { json_file = "${env.WORKSPACE}/testdata/test_json.json" if(fileExists(json_file) == true) { echo("json file is exists") }else { error("here haven't find json file") } }

说明:

该示例用于判断json_file变量指定的文件是否存在 error指令用于定义并返回自定义的错误信息

dir

dir()方法用于改变当前的工作目录在dir语句块填写要进入的目录路径即可。

示例如下

stages{ stage("dir") { steps{ echo env.WORKSPACE dir("${env.WORKSPACE}/fw-base-nop"){ sh "pwd" } } } }

deletedir

deleteDir()方法默认递归删除WORKSPACE下的文件和文件夹没有参数通常它与dir步骤一起使用用于删除指定目录下的内容。

示例如下

stage("deleteDir") { steps{ script{ sh("ls -al ${env.WORKSPACE}") deleteDir() // clean up current work directory sh("ls -al ${env.WORKSPACE}") } } }

或者删除指定目录

node { stage('s'){ dir('/base'){ deleteDir() }

}

}

script

script 步骤需要script-pipeline块并在流水线中执行。 对于大多数用例来说声明式流水线中的script步骤是不必要的该关键字大多数情况下用来执行命令。

示例如下

pipeline { agent any stages { stage('Example') { steps { echo 'Hello World'

            script {
                def browsers = ['chrome', 'firefox']
                for (int i = 0; i < browsers.size(); ++i) {
                    echo "Testing the ${browsers[i]} browser"
                }
            }
        }
    }
}

}

其中通过def定义的变量可以在下面的stage的中被引用

stash/unstash

stash用于将文件保存起来以便同一次构建的其他stage或step使用。 如果整个流水线在同一台机器上执行那stash是多余的。stash一般用于跨Jenkins node使用 stash步骤会将文件存储在tar文件中对于大文件的stash操作将会消耗Jenkins master的计算资源。Jenkins官方文档推荐当文件大小为5100MB时应该考虑使用其他替代方案。

stash 指令的参数如下:

name字符串类型保存文件的集合的唯一标识 allowEmpty布尔类型允许stash内容为空 excludes字符串类型排除文件如果排除多个使用「逗号」分隔 includes字符串类型stash文件留空表示全部 useDefaultExcludes布尔类型true使用Ant风格路径默认排除文件 Ant风格路径表达式

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

与stash对应的是unstash。该指令用于取出之前stash的文件。- unstash步骤只有一个name参数即stash时的唯一标识。通常情况下stash与unstash步骤同时使用。示例如下

pipeline { agent none stages { stage('stash') { agent { label "master" } steps { dir('target') { stash(name: "abc", include: "xx.jar") } } } stage("unstash") { agent { label "jenkins-slave1" } steps { script { unstash("abc") cp xx.jar /data ... } } } } }

unstash根据stash指定的name的值获取文件等流水线执行完毕后文件会删除

archiveArtifacts

jenkins流水线的archiveArtifacts指令也是用来保存文档文件与stash不同的是该指令会将文件保存到本地流水线执行完成后不会被销毁。保存的文件会放到到 Jenkins 的 jobs/JOB_NAME/builds/BUILD_NO目录下

示例如下

pipeline { agent any stages { stage('Archive') { steps { archiveArtifacts artifacts: '**/target/*.jar', onlyIfSuccessful: true } } } }

archiveArtifacts指令包含多个参数其中artifacts参数为必须参数onlyIfSuccessful参数表示只有成功时保存文件。其他参数使用可以参考官方文档

片段生成器

jenkins内置的Snippet Generator工具片段生成器用于将插件或者关键字的使用语法动态的生成steps语法片段添加到流水线中。片段生成器界面可以从pipeline项目中最底部的pipeline syntax超链接中进入也可以直接通过${YOUR_JENKINS_URL}/pipeline-syntax访问。

Jenkins对于片段生成器分成了两种类别一种是基于pipeline声明式语法的片段生成器Declarative Directive Generator菜单另一种是基于jenkins内置插件以及脚本式语法使用片段生成器菜单的片段生成器。下面分别对这两种生成器进行介绍

基于声明式语法的片段生成器用于在不知道关键字语法的情况下生成语法片段。同样是访问上面的连接点击Declarative Directive Generator菜单如下所示-

可以看到在下拉框中显示了这些指令的相关语法选中任意指令配置好后点击下面的Generate Declarative Directive即可生成相应的语法片段

而对于声明式脚本以及内置插件使用的片段生成器直接点击”片段生成器“菜单即可点击”Sample Setp“会列出所有支持的插件列表选中一个比如下图所示

根据需要配置好必须项点击下方的Generate Pipeline Script就会生成相应的代码片段

然后就可以复制粘贴放到pipeline项目中的脚本中去了。

有关Jenkins脚本式语法的内容就介绍到这里在下一节中将使用实践案例在对本节以及上节学习的内容进行实践以加深理解