Declarative Pipeline 基础语法
阅读原文时间:2022年06月09日阅读:1

Declarative Pipeline(声明式)核心概念

核心概念用来组织pipeline的运行流程

1.pipeline :声明其内容为一个声明式的pipeline脚本

2.agent:执行节点(job运行的slave或者master节点)

3.stages:阶段集合,包裹所有的阶段(例如:打包,部署等各个阶段)

4.stage:阶段,被stages包裹,一个stages可以有多个stage

5.steps:步骤,为每个阶段的最小执行单元,被stage包裹

6.post:执行构建后的操作,根据构建结果来执行对应的操作

根据上面几个概念就能够轻易的创建一个简单的pipeline

pipeline{
    agent any
    stages{
        stage("first stage"){
            steps("first steps"){
                echo "this is first step"
            }
        }
    }
    post{
        always{
            echo "this is ending..."
        }
    }
}

下面针对几个核心概念,逐一进行说明

1.pipeline

作用域:应用于全局最外层,表明该脚本为声明式pipeline

是否必须:必须

参数:无

2.agent

作用域:可用在全局与stage内

是否必须:是,

参数:any,none, label, node,docker,dockerfile

pipeline{
    agent any  //全局必须带有agent表明此pipeline执行节点
    stages{
        stage("first stage"){
            agent { label 'master' }  //具体执行的步骤节点,非必须
            steps{
                echo "this is first step"
            }
        }
    }
}

参数示例:

//运行在任意的可用节点上
agent any
//全局不指定运行节点,由各自stage来决定
agent none
//运行在指定标签的机器上,具体标签名称由agent配置决定
agent { label 'master' }
//node参数可以扩展节点信息
agent {
     node {
         label 'master'
         customWorkspace 'xxx'
    }
}
//使用指定运行的容器
agent { docker 'python'  }

3.stages

作用域:全局或者stage阶段内,每个作用域内只能使用一次

是否必须:全局必须

参数:无

pipeline{
    agent any
    stages{
        stage("first stage"){
            stages{  //嵌套在stage里
                stage("inside"){
                    steps{
                        echo "inside"
                    }
                }
            }
        }
        stage("stage2"){
            steps{
                echo "outside"
            }
        }
    }
}

看下运行结果,发现嵌套的stage也是能够展现在视图里面的

4.stage

作用域:被stages包裹,作用在自己的stage包裹范围内

是否必须:必须

参数:需要一个string参数,表示此阶段的工作内容

备注:stage内部可以嵌套stages,内部可单独制定运行的agent

5.steps

作用域:被stage包裹,作用在stage内部

是否必须:必须

参数:无

6.post

作用域:作用在pipeline结束后者stage结束后

条件:always、changed、failure、success、unstable、aborted

Declarative Pipeline(声明式)指令

指令是帮助pipeline更容易的执行命令,可以理解为一个封装好的公共函数和方法,提供给pipeline使用

1.environment:声明一个全局变量或者步骤内部的局部变量

pipeline{
    agent any
    environment {
        P1="parameters 1"
    }
    stages{
        stage("stage2"){
            environment {
                P2="parameters 2"
            }
            steps{
                echo "$P1"
                echo "$P2"
            }
        }
    }
}

2.options:options指令能够提供给脚本更多的选项

  • buildDiscarder:指定build history与console的保存数量

    用法:options { buildDiscarder(logRotator(numToKeepStr: '1')) }

  • disableConcurrentBuilds:设置job不能够同时运行

    用法:options { disableConcurrentBuilds() }

  • skipDefaultCheckout:跳过默认设置的代码check out

    用法:options { skipDefaultCheckout() }

  • skipStagesAfterUnstable:一旦构建状态变得UNSTABLE,跳过该阶段

    用法:options { skipStagesAfterUnstable() }

  • checkoutToSubdirectory:在工作空间的子目录进行check out

    用法:options { checkoutToSubdirectory('children_path') }

  • timeout:设置jenkins运行的超时时间,超过超时时间,job会自动被终止

    用法:options { timeout(time: 1, unit: 'MINUTES') }

  • retry :设置retry作用域范围的重试次数

    用法:options { retry(3) }

  • timestamps:为控制台输出增加时间戳

    用法:options { timestamps() }

备注:当options作用在stage内部的时候,可选的只能是跟stage相关的选项(skipDefaultCheckout、timeout、retry、timestamps)

以其中几个作为例子

pipeline{
    agent any
    options {
       timestamps()
       disableConcurrentBuilds()

    }
    stages{

        stage("stage1"){
            options { timeout(time:1,unit:'MINUTES')
                        retry(2)
            }
            steps{
                echo "beging===================="
                sh "xxx.sh"
            }
        }
    }
}

3.parameters:提供pipeline运行的参数

  • 作用域:被最外层pipeline所包裹,并且只能出现一次,参数可被全局使用

  • 好处:使用parameters好处是能够使参数也变成code,达到pipeline as code,pipeline中设置的参数会自动在job构建的时候生成,形成参数化构建

  • 用法:

    pipeline{
    agent any
    parameters {
    string(name: 'P1', defaultValue: 'it is p1', description: 'it is p1')
    booleanParam(name: 'P2', defaultValue: true, description: 'it is p2')
    }
    stages{
    stage("stage1"){
    steps{
    echo "$P1"
    echo "$P2"
    }
    }
    }
    }

自动生成的构建参数

4.triggers:触发器是自动化运行pipeline的方法

  • 作用域:被pipeline包裹,在符合条件下自动触发pipeline

目前包含三种自动触发的方式:

第一种:cron

  • 作用:以指定的时间来运行pipeline
  • 用法:triggers { cron('*/1 * * * *') }

第二种:pollSCM

  • 作用:以固定的时间检查代码仓库更新(或者当代码仓库有更新时)自动触发pipeline构建
  • 用法:triggers { pollSCM('H */4 * * 1-5') }或者triggers { pollSCM() }(后者需要配置post-commit/post-receive钩子)

第三种:upstream

  • 作用:可以利用上游Job的运行状态来进行触发

  • 用法:triggers { upstream(upstreamProjects: 'job1,job2', threshold: hudson.model.Result.SUCCESS) }

    pipeline{
    agent any
    //说明:当test_8或者test_7运行成功的时候,自动触发
    triggers { upstream(upstreamProjects: 'test_8,test_7', threshold: hudson.model.Result.SUCCESS) }
    stages{
    stage("stage1"){
    steps{
    echo "hello"
    }
    }
    }
    }

5.tools:用于引用配置好的工具

引用的工具需要在管理页面的全局工具配置里配置过

pipeline {
    agent any
    tools {
        maven 'apache-maven-3.0.1'
    }
    stages {
        stage('Example') {
            steps {
                sh 'mvn --version'
            }
        }
    }
}

6.input:input指令允许暂时中断pipeline执行,等待用户输入,根据用户输入进行下一步动作

pipeline {
    agent any
    stages {
        stage('Example') {
            input {
                message "Should we continue?"
                ok "Yes, we should."
                submitter "alice,bob"
                parameters {
                    string(name: 'PERSON', defaultValue: 'Mr Jenkins', description: 'Who should I say hello to?')
                }
            }
            steps {
                echo "Hello, ${PERSON}, nice to meet you."
            }
        }
    }
}

看下效果

7.when:根据when指令的判断结果来决定是否执行后面的阶段

可选条件

  • branch :判断分支名称是否符合预期

    用法:when { branch 'master' }

  • environment : 判断环境变量是否符合预期

    用法:when { environment name: 'DEPLOY_TO', value: 'production' }

  • expression:判断表达式是否符合预期

    用法:when { expression { return params.DEBUG_BUILD } }

  • not : 判断条件是否为假

    用法:when { not { branch 'master' } }

  • allOf:判断所有条件是不是都为真

    用法:when { allOf { branch 'master'; environment name: 'DEPLOY_TO', value: 'production' } }

  • anyOf:判断是否有一个条件为真

    用法:when { anyOf { branch 'master'; branch 'staging' } }

特别的:如果我们想要在进入agent之前进行判断,需要将beforeAgent设置为true

pipeline {
    agent none
    stages {
        stage('Example Build') {
            steps {
                echo 'Hello World'
            }
        }
        stage('Example Deploy') {
            agent {
                label "some-label"
            }
            when {
                beforeAgent true //设置先对条件进行判断,符合预期才进入steps
                branch 'production'
            }
            steps {
                echo 'Deploying'
            }
        }
    }
}

并行执行

通过将阶段设置为parallel来表明该stage为并行运行,但是需要注意以下几点

  • 一个stage只能有一个steps或者parallel

  • 嵌套的stages里不能使用parallel

  • parallel不能包含agent或者tools

  • 通过设置failFast 为true表示:并行的job中如果其中的一个失败,则终止其他并行的stage

    pipeline {
    agent any
    stages {
    stage('Non-Parallel Stage') {
    steps {
    echo 'Non-parallel'
    }
    }
    stage('Parallel Stage') {
    agent any
    failFast true
    parallel {
    stage('parallel 1') {
    agent any
    steps {
    echo "parallel 1"
    }
    }
    stage('parallel 2') {
    steps {
    echo "parallel 2"
    }
    }
    }
    }
    }
    }

脚本

在声明式的pipeline中默认无法使用脚本语法,但是pipeline提供了一个脚本环境入口:script{},通过使用script来包裹脚本语句,即可使用脚本语法

  • 条件判断:

    pipeline {
    agent any
    stages {
    stage('stage 1') {
    steps {
    script{
    if ( "1" =="1" ) {
    echo "lalala"
    }else {
    echo "oooo"
    }
    }
    }
    }
    }
    }

  • 异常处理

    pipeline {
    agent any
    stages {
    stage('stage 1') {
    steps {
    script{
    try {
    sh 'exit 1'
    }
    catch (exc) {
    echo 'Something failed'

                    }
                }
            }
        }
    }

    }

局部变量的定义和传递

自定义变量(局部)

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

环境变量(局部)

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

exit code, stdout and stderr 返回值和输出

其做法是,把stdout定向到一个文件,sh 配置 returnStatus: true,它的返回是一个0或非0的整数,然后从文件读取stdout的内容。stderr同理可得。

def status = sh(returnStatus: true, script: "git merge --no-edit $branches > merge_output.txt")
if (status != 0) {
  currentBuild.result = 'FAILED'
  def output = readFile('merge_output.txt').trim()
  slackSend channel: SLACK_CHANNEL, message: "<${env.JOB_URL}|${env.JOB_NAME}> ran into an error merging the PR branches into the ${TARGET_BRANCH} branch:\n```\n${output}\n```\n<${env.BUILD_URL}/console|See the full output>", color: 'warning', tokenCredentialId: 'slack-token'
  error 'Merge conflict'
}
sh 'rm merge_output.txt'