第五章:Pipeline 语法详解

深入学习 Declarative Pipeline 语法,包括 directives、steps、脚本式步骤。

最后更新: 2024-01-15
页面目录

Pipeline 语法详解

本章详细介绍 Declarative Pipeline 的完整语法和使用技巧。

Pipeline 结构概览

pipeline {
    // 指令定义
    directive {
        // 配置
    }
    
    stages {
        stage('Stage 1') {
            steps {
                // 步骤
            }
        }
    }
}

Agent Directive

可用选项

// 任意可用节点
agent any

// 指定标签
agent { label 'linux && docker' }

// 多个标签(必须同时满足)
agent { label 'linux && 64bit' }

// 排除标签
agent {
    label '!windows'
}

// Docker 容器
agent {
    docker {
        image 'maven:3.8-openjdk-11'
        args '-v /repo:/root/.m2'
        reuseNode true
    }
}

// Docker Compose
agent {
    dockerfile {
        filename 'Dockerfile.test'
        additionalBuildArgs '--build-arg version=1.0'
    }
}

// Kubernetes
agent {
    kubernetes {
        label 'jenkins-agent'
        defaultContainer 'jnlp'
        yamlFile 'pod-template.yaml'
    }
}

// 不分配节点(配合 stage agent)
agent none

Stages Directive

stages {
    // 至少包含一个 stage
    stage('构建') {
        // ...
    }
    
    stage('测试') {
        // ...
    }
}

Stage Directive

stage('名称') {
    agent { /* ... */ }
    environment { /* ... */ }
    tools { /* ... */ }
    input { /* ... */ }
    when { /* ... */ }
    steps { /* ... */ }
    post { /* ... */ }
}

Steps Directive

基本步骤

steps {
    // 打印消息
    echo 'Hello World'
    
    // 执行 Shell 命令
    sh 'ls -la'
    sh(script: 'mvn clean', returnStdout: true)
    sh(label: 'Build', script: 'make')
    
    // Windows Batch
    bat 'dir'
    bat 'call build.bat'
    
    // PowerShell (需要插件)
    powershell 'Get-Process'
    
    // 切换目录
    dir('/path/to/dir') {
        sh 'pwd'
    }
    
    // 设置变量
    script {
        def version = sh(script: 'cat version.txt', returnStdout: true).trim()
        env.APP_VERSION = version
    }
    
    // 静默执行
    sshagent(['ssh-key']) {
        sh 'ssh -i key.pem user@host "command"'
    }
}

高级步骤

steps {
    // 脚本块
    script {
        if (env.BRANCH_NAME == 'main') {
            echo '部署生产环境'
        } else {
            echo '部署测试环境'
        }
    }
    
    // 条件执行
    script {
        if (fileExists('package.json')) {
            sh 'npm install'
        }
    }
    
    // 循环
    script {
        ['dev', 'staging', 'prod'].each { envName ->
            echo "部署到 ${envName}"
        }
    }
    
    // 错误处理
    script {
        try {
            sh 'mvn test'
        } catch (Exception e) {
            echo "测试失败: ${e.message}"
        } finally {
            echo '清理'
        }
    }
    
    // 超时重试
    retry(3) {
        sh 'curl -X POST https://api.example.com/deploy'
    }
    
    // 等待时间
    timeout(time: 5, unit: 'MINUTES') {
        waitUntil {
            script {
                def result = sh(script: 'curl -s http://service/health', returnStdout: true)
                return result.contains('OK')
            }
        }
    }
}

认证凭据

steps {
    // 使用凭据 ID
    withCredentials([string(credentialsId: 'api-key', variable: 'API_KEY')]) {
        sh 'curl -H "X-API-Key: $API_KEY" https://api.example.com'
    }
    
    // 用户名密码
    withCredentials([usernamePassword(credentialsId: 'github',
                                       usernameVariable: 'USER',
                                       passwordVariable: 'PASS')]) {
        sh 'git push https://$USER:$PASS@github.com/org/repo.git'
    }
    
    // SSH 密钥
    sshagent(['ssh-key']) {
        sh 'ssh -i key.pem user@host'
    }
    
    // 文件凭据
    withCredentials([file(credentialsId: 'config', variable: 'CONFIG_FILE')]) {
        sh 'app --config $CONFIG_FILE'
    }
}

Input Directive

stage('Deploy Approval') {
    steps {
        input {
            message '是否部署到生产环境?'
            ok '批准'
            submitter 'admin,dev-lead'
            parameters {
                choice(name: 'DEPLOY_ENV',
                       choices: ['staging', 'production'],
                       description: '选择部署环境')
                string(name: 'DEPLOY_NOTES',
                       defaultValue: '',
                       description: '部署备注')
            }
        }
    }
}

// 或者使用脚本式
steps {
    script {
        def userInput = input(
            id: 'deploy',
            message: '部署确认',
            parameters: [
                choice(name: 'ENV', choices: ['dev', 'staging', 'prod'], description: '环境'),
                string(name: 'VERSION', description: '版本号')
            ]
        )
        
        env.DEPLOY_ENV = userInput.ENV
        env.DEPLOY_VERSION = userInput.VERSION
    }
}

Options Directive

pipeline {
    options {
        // 构建历史保留策略
        buildDiscarder(logRotator(
            numToKeepStr: '30',
            daysToKeepStr: '7',
            artifactNumToKeepStr: '10'
        ))
        
        // 禁用并发构建
        disableConcurrentBuilds()
        
        // 启用并发构建(带锁)
        disableConcurrentBuildsAbortable()
        
        // 超时设置
        timeout(time: 1, unit: 'HOURS')
        timeout(time: 30, unit: 'SECONDS')
        
        // 重试次数
        retry(3)
        
        // 保持工作区
        preserveStashes()
        preserveStashes(buildCount: 5)
        
        // 时间戳
        timestamps()
        
        // 跳过默认 Checkout
        skipDefaultCheckout()
        
        // 跳过StagesAfterUnstable
        skipStagesAfterUnstable()
        
        // 覆盖构建序号
        overrideIndexTriggers(false)
        
        // Parallels 失败后强制终止
        parallelsAlwaysFailFast()
        
        // 锁定资源
        lock(resource: 'deploy-server') {
            sh './deploy.sh'
        }
    }
    
    stages {
        stage('Deploy') {
            // stage 级别的 options
            options {
                timeout(time: 5, unit: 'MINUTES')
                lock(resource: 'database-${params.DB}')
            }
            steps {
                // ...
            }
        }
    }
}

Tools Directive

pipeline {
    agent any
    
    tools {
        maven 'maven-3.8'
        jdk 'jdk-11'
        gradle 'gradle-7.0'
        go 'go-1.17'
        nodejs 'nodejs-16'
    }
    
    stages {
        stage('Build') {
            steps {
                sh 'mvn --version'
                sh 'java -version'
            }
        }
    }
}

注意:需要在 Jenkins 全局工具配置中预先安装这些工具。

When Directive

条件类型

when {
    // 分支条件
    branch 'main'
    branch 'feature/*'
    branch pattern: 'release-\\d+', comparator: 'REGEXP'
    
    // 环境变量
    environment name: 'DEPLOY_ENV', value: 'production'
    environment name: 'BRANCH_NAME', matches: 'main|develop'
    
    // 表达式
    expression { params.BUILD_DEBUG == true }
    expression { return fileExists('special-file.txt') }
    
    // 条件组合
    allOf {
        branch 'main'
        environment name: 'DEPLOY', value: 'true'
    }
    
    anyOf {
        branch 'main'
        branch 'release/*'
        tag 'v*'
    }
    
    not {
        branch 'feature/*'
    }
}

嵌套条件

when {
    anyOf {
        allOf {
            branch 'main'
            environment name: 'ENABLE_PROD', value: 'true'
        }
        allOf {
            branch 'release/*'
            expression { params.IS_STABLE == true }
        }
    }
}

Environment Directive

pipeline {
    agent any
    
    environment {
        APP_NAME = 'myapp'
        APP_VERSION = '1.0.0'
        JAVA_OPTS = '-Xmx512m -Xms256m'
        
        // 从凭据获取
        API_KEY = credentials('api-key-id')
    }
    
    stages {
        stage('Build') {
            environment {
                // stage 级别的环境变量
                BUILD_MODE = 'release'
            }
            steps {
                sh 'printenv | grep APP_'
            }
        }
    }
}

Post Directive

执行顺序

post {
    // 总是执行(无论成功失败)
    always { }
    
    // 仅成功时执行
    success { }
    
    // 仅失败时执行
    failure { }
    
    // 仅不稳定时执行
    unstable { }
    
    // 仅中止时执行
    aborted { }
    
    // 最后执行(在 always 之后)
    cleanup { }
}

常用 post 步骤

post {
    always {
        // 归档日志
        writeFile file: 'build.log', text: currentBuild.rawBuild.log
        archiveArtifacts artifacts: 'build.log', allowEmptyArchive: true
        
        // 清理工作区
        cleanWs()
        
        // 删除工作区(详细)
        dir('${WORKSPACE}@script') {
            deleteDir()
        }
        dir('${WORKSPACE}@tmp') {
            deleteDir()
        }
        deleteDir()
        
        // 清理 Docker
        sh 'docker system prune -f'
    }
    
    success {
        // 归档产物
        archiveArtifacts artifacts: 'target/*.jar', fingerprint: true
        
        // 发布 JUnit 报告
        junit 'target/surefire-reports/*.xml'
        
        // 发送通知
        emailext(
            subject: "构建成功: ${env.JOB_NAME}",
            body: "构建 #${env.BUILD_NUMBER} 成功",
            to: 'team@example.com'
        )
    }
    
    failure {
        // Slack 通知
        slackSend(
            channel: '#jenkins',
            color: 'danger',
            message: "构建失败: ${env.JOB_NAME} #${env.BUILD_NUMBER}"
        )
        
        // 钉钉通知
        dingtalk(
            robot: 'build-bot',
            type: 'markdown',
            title: '构建失败',
            text: "**构建失败**\n项目: ${env.JOB_NAME}\n构建号: ${env.BUILD_NUMBER}"
        )
    }
}

Matrix Directive

pipeline {
    agent none
    
    stages {
        stage('Matrix Build') {
            matrix {
                axes {
                    axis {
                        name 'PLATFORM'
                        values 'linux', 'windows', 'macos'
                    }
                    axis {
                        name 'ARCH'
                        values 'x64', 'arm64'
                    }
                    axis {
                        name 'JDK'
                        values '11', '17'
                    }
                }
                
                // 排除特定组合
                excludes {
                    exclude {
                        axis {
                            name 'PLATFORM'
                            values 'windows'
                        }
                        axis {
                            name 'ARCH'
                            values 'arm64'
                        }
                    }
                }
                
                stages {
                    stage('Build') {
                        steps {
                            echo "Building ${PLATFORM}/${ARCH}/JDK${JDK}"
                            sh "./build.sh"
                        }
                    }
                    
                    stage('Test') {
                        steps {
                            echo "Testing ${PLATFORM}/${ARCH}/JDK${JDK}"
                            sh "./test.sh"
                        }
                    }
                }
            }
        }
    }
}

Scripted Pipeline 语法

虽然 Declarative 是推荐的写法,但了解 Scripted 语法有助于理解 Jenkins Pipeline 的本质。

节点和阶段

node('docker') {
    stage('Checkout') {
        checkout scm
    }
    
    stage('Build') {
        sh 'mvn clean package'
    }
    
    stage('Test') {
        sh 'mvn test'
    }
}

条件分支

if (env.BRANCH_NAME == 'main') {
    stage('Deploy Prod') {
        sh './deploy-prod.sh'
    }
} else if (env.BRANCH_NAME == 'develop') {
    stage('Deploy Dev') {
        sh './deploy-dev.sh'
    }
} else {
    stage('Skip Deploy') {
        echo 'Feature branch, skipping deploy'
    }
}

循环

def branches = ['dev', 'staging', 'prod']

for (int i = 0; i < branches.size(); i++) {
    def branch = branches[i]
    stage("Deploy ${branch}") {
        sh "./deploy.sh ${branch}"
    }
}

函数定义

def buildApp(String version) {
    sh "mvn clean package -Dversion=${version}"
}

def notify(String channel, String message) {
    slackSend channel: channel, message: message
}

pipeline {
    agent any
    stages {
        stage('Build') {
            steps {
                script {
                    buildApp('1.0.0')
                    notify('#ci', 'Build completed')
                }
            }
        }
    }
}

常用 Step 速查表

Step 说明 示例
echo 打印消息 echo 'Hello'
sh 执行 Shell sh 'ls -la'
bat 执行 Windows Batch bat 'dir'
powershell 执行 PowerShell powershell 'Get-Process'
checkout 检出代码 checkout scm
git Git 操作 git url: '...'
dir 切换目录 dir('/path') { }
fileExists 检查文件 fileExists('file.txt')
archiveArtifacts 归档产物 archiveArtifacts '*.jar'
junit JUnit 报告 junit 'test-reports/*.xml'
stash 暂存文件 stash includes: '**/*'
unstash 恢复文件 unstash 'workspace'
timeout 超时设置 timeout(5) { }
retry 重试 retry(3) { }
waitUntil 等待条件 waitUntil { }
input 暂停等待输入 input 'Continue?'
error 抛出错误 error 'Failed'
catchError 捕获错误 catchError { }
withCredentials 使用凭据 withCredentials([...]) { }
withEnv 设置环境变量 withEnv([...]) { }
script 脚本块 script { }

下一步

现在您已经掌握了 Pipeline 语法。接下来让我们学习实际应用中的 Pipeline 模板和示例。

👉 Pipeline 实战


📖 参考资源