流水线步骤 #

Pipeline步骤(Steps)是Pipeline中最基本的执行单元,每个步骤执行特定的任务。本节将详细介绍常用的Pipeline步骤。

基本步骤 #

echo #

输出文本信息到控制台:

groovy
steps {
    echo 'Hello, Jenkins!'
    echo "Build number: ${BUILD_NUMBER}"
    echo """
    Multi-line
    text output
    """
}

sh #

执行Shell脚本:

groovy
steps {
    sh 'echo "Hello"'
    
    sh '''
        echo "Multi-line script"
        ls -la
        pwd
    '''
    
    def output = sh(script: 'git rev-parse HEAD', returnStdout: true).trim()
    echo "Commit: ${output}"
    
    def status = sh(script: 'test -f file.txt', returnStatus: true)
    if (status == 0) {
        echo 'File exists'
    }
}

参数说明:

参数 说明
script 要执行的脚本
returnStdout 是否返回标准输出
returnStatus 是否返回状态码
encoding 输出编码

bat #

执行Windows批处理命令:

groovy
steps {
    bat 'echo Hello'
    bat '''
        echo Multi-line script
        dir
    '''
}

powershell #

执行PowerShell脚本:

groovy
steps {
    powershell 'Write-Host "Hello"'
    powershell '''
        Get-ChildItem
        Get-Process
    '''
    powershell(script: 'Get-Service', returnStdout: true)
}

script #

执行Groovy脚本块:

groovy
steps {
    script {
        def files = findFiles(glob: '**/*.java')
        files.each { f ->
            echo "Found: ${f.name}"
        }
    }
}

代码管理步骤 #

checkout #

检出源代码:

groovy
steps {
    checkout scm
    
    checkout([
        $class: 'GitSCM',
        branches: [[name: '*/main']],
        doGenerateSubmoduleConfigurations: false,
        extensions: [
            [$class: 'CleanBeforeCheckout'],
            [$class: 'CloneOption', depth: 1, noTags: true, shallow: true]
        ],
        submoduleCfg: [],
        userRemoteConfigs: [[
            url: 'https://github.com/user/repo.git',
            credentialsId: 'github-creds'
        ]]
    ])
}

常用扩展:

groovy
extensions: [
    [$class: 'CleanBeforeCheckout'],
    [$class: 'CheckoutOption', timeout: 10],
    [$class: 'CloneOption', depth: 1, shallow: true],
    [$class: 'LocalBranch', localBranch: 'main'],
    [$class: 'RelativeTargetDirectory', relativeTargetDir: 'my-repo'],
    [$class: 'SparseCheckoutPaths', sparseCheckoutPaths: [[path: 'src/']]]
]

git #

简化的Git检出:

groovy
steps {
    git url: 'https://github.com/user/repo.git',
        branch: 'main',
        credentialsId: 'github-creds'
    
    git url: 'https://github.com/user/repo.git',
        branch: 'develop',
        changelog: false,
        poll: false
}

svn #

检出SVN代码:

groovy
steps {
    checkout([
        $class: 'SubversionSCM',
        locations: [[
            remote: 'https://svn.example.com/repo/trunk',
            credentialsId: 'svn-creds',
            local: '.'
        ]]
    ])
}

文件操作步骤 #

archiveArtifacts #

归档构建产物:

groovy
steps {
    archiveArtifacts artifacts: 'target/*.jar',
                     fingerprint: true,
                     allowEmptyArchive: false,
                     excludes: 'target/*.original',
                     caseSensitive: true
}

stash/unstash #

在节点间传递文件:

groovy
stage('Build') {
    steps {
        sh 'mvn clean package'
        stash includes: 'target/*.jar', name: 'artifacts', allowEmpty: false
    }
}

stage('Deploy') {
    agent { label 'production' }
    steps {
        unstash 'artifacts'
        sh 'kubectl apply -f k8s/'
    }
}

参数说明:

参数 说明
name stash名称
includes 包含的文件模式
excludes 排除的文件模式
allowEmpty 是否允许空stash
useDefaultExcludes 是否使用默认排除规则

cleanWs #

清理工作空间:

groovy
steps {
    cleanWs()
    
    cleanWs cleanWhenSuccess: false,
            cleanWhenFailure: true,
            deleteDirs: true,
            disableDeferredWipeout: true,
            patterns: [
                [pattern: 'target/**', type: 'INCLUDE'],
                [pattern: 'node_modules/**', type: 'INCLUDE']
            ]
}

dir #

切换工作目录:

groovy
steps {
    dir('subdirectory') {
        sh 'ls -la'
    }
    
    dir('/tmp/custom') {
        sh 'pwd'
    }
}

writeFile #

写入文件:

groovy
steps {
    writeFile file: 'config.json',
              text: '{"key": "value"}',
              encoding: 'UTF-8'
    
    writeFile file: 'script.sh',
              text: '''#!/bin/bash
echo "Hello"
''',
              encoding: 'UTF-8'
}

readFile #

读取文件:

groovy
steps {
    def content = readFile file: 'config.json', encoding: 'UTF-8'
    echo "Content: ${content}"
}

fileExists #

检查文件是否存在:

groovy
steps {
    if (fileExists 'config.json') {
        echo 'Config file exists'
    }
}

findFiles #

查找文件:

groovy
steps {
    def files = findFiles(glob: '**/*.java')
    files.each { f ->
        echo "Found: ${f.name}"
        echo "Path: ${f.path}"
        echo "Size: ${f.length} bytes"
        echo "Last modified: ${f.lastModified}"
        echo "Is directory: ${f.directory}"
    }
}

deleteDir #

删除目录:

groovy
steps {
    deleteDir()
    
    dir('target') {
        deleteDir()
    }
}

touch #

创建或更新文件时间戳:

groovy
steps {
    touch file: 'marker.txt', timestamp: System.currentTimeMillis()
}

测试相关步骤 #

junit #

发布JUnit测试报告:

groovy
steps {
    junit '**/target/surefire-reports/*.xml'
    
    junit testResults: '**/target/surefire-reports/*.xml',
           allowEmptyResults: true,
           healthScaleFactor: 1.0,
           keepLongStdio: true,
           testDataPublishers: [
               [$class: 'ClaimTestDataPublisher'],
               [$class: 'StabilityTestDataPublisher']
           ]
}

publishHTML #

发布HTML报告:

groovy
steps {
    publishHTML(target: [
        allowMissing: false,
        alwaysLinkToLastBuild: true,
        keepAll: true,
        reportDir: 'target/site',
        reportFiles: 'index.html',
        reportName: 'Site Report',
        reportTitles: 'My Report'
    ])
}

recordIssues #

记录代码问题:

groovy
steps {
    recordIssues(tools: [
        java(),
        spotBugs(),
        checkStyle(),
        pmdParser()
    ])
    
    recordIssues(
        tools: [java()],
        qualityGates: [[threshold: 10, type: 'TOTAL', status: 'WARNING']],
        filters: [excludeFile('**/generated/**')]
    )
}

代码质量步骤 #

withSonarQubeEnv #

集成SonarQube:

groovy
steps {
    withSonarQubeEnv('SonarQube') {
        sh 'mvn sonar:sonar'
    }
}

waitForQualityGate #

等待质量门禁:

groovy
steps {
    timeout(time: 5, unit: 'MINUTES') {
        def qg = waitForQualityGate()
        if (qg.status != 'OK') {
            error "Quality gate failed: ${qg.status}"
        }
    }
}

凭据步骤 #

withCredentials #

使用凭据:

groovy
steps {
    withCredentials([usernamePassword(
        credentialsId: 'docker-registry',
        usernameVariable: 'DOCKER_USER',
        passwordVariable: 'DOCKER_PASS'
    )]) {
        sh "docker login -u ${DOCKER_USER} -p ${DOCKER_PASS}"
    }
}

凭据类型:

groovy
withCredentials([
    usernamePassword(
        credentialsId: 'creds',
        usernameVariable: 'USER',
        passwordVariable: 'PASS'
    ),
    sshUserPrivateKey(
        credentialsId: 'ssh-key',
        keyFileVariable: 'SSH_KEY',
        usernameVariable: 'SSH_USER'
    ),
    file(
        credentialsId: 'kube-config',
        variable: 'KUBECONFIG'
    ),
    string(
        credentialsId: 'api-token',
        variable: 'API_TOKEN'
    ),
    certificate(
        credentialsId: 'client-cert',
        keystoreVariable: 'KEYSTORE',
        passwordVariable: 'PASSWORD'
    )
]) {
}

usernamePassword #

groovy
withCredentials([usernameColonPassword(
    credentialsId: 'creds',
    variable: 'USERPASS'
)]) {
    sh "curl -u ${USERPASS} https://api.example.com"
}

Docker步骤 #

withDockerContainer #

在Docker容器中执行:

groovy
steps {
    withDockerContainer('maven:3.8-openjdk-11') {
        sh 'mvn clean package'
    }
}

withDockerRegistry #

登录Docker仓库:

groovy
steps {
    withDockerRegistry(credentialsId: 'docker-registry', url: 'https://registry.example.com') {
        sh 'docker push myapp:latest'
    }
}

docker #

Docker操作:

groovy
steps {
    script {
        def image = docker.build('myapp:${BUILD_NUMBER}')
        image.push()
        image.push('latest')
        
        docker.image('maven:3.8').inside {
            sh 'mvn clean package'
        }
    }
}

通知步骤 #

mail #

发送邮件:

groovy
steps {
    mail to: 'team@example.com',
         subject: "Build ${BUILD_NUMBER}",
         body: "Build completed: ${BUILD_URL}",
         from: 'jenkins@example.com',
         replyTo: 'no-reply@example.com',
         mimeType: 'text/html'
}

emailext #

扩展邮件:

groovy
steps {
    emailext(
        subject: "Build ${BUILD_NUMBER}: ${currentBuild.result}",
        body: '''${SCRIPT, template="groovy-html.template"}''',
        to: 'team@example.com',
        from: 'jenkins@example.com',
        replyTo: 'no-reply@example.com',
        mimeType: 'text/html',
        attachLog: true,
        attachmentsPattern: '**/target/*.jar',
        recipientProviders: [
            [$class: 'DevelopersRecipientProvider'],
            [$class: 'RequesterRecipientProvider']
        ]
    )
}

slack #

发送Slack消息:

groovy
steps {
    slackSend(
        channel: '#builds',
        color: 'good',
        message: "Build ${BUILD_NUMBER} succeeded: ${BUILD_URL}",
        baseUrl: 'https://hooks.slack.com/services/',
        tokenCredentialId: 'slack-token'
    )
}

输入步骤 #

input #

等待用户输入:

groovy
stage('Deploy') {
    steps {
        input message: 'Deploy to production?',
               ok: 'Yes, deploy!',
               submitter: 'admin,deploy-team',
               submitterParameter: 'APPROVER',
               parameters: [
                   choice(name: 'ENVIRONMENT',
                          choices: ['prod', 'staging'],
                          description: 'Select environment'),
                   string(name: 'VERSION',
                          defaultValue: "${BUILD_NUMBER}",
                          description: 'Version to deploy')
               ]
    }
}

milestone #

确保构建顺序:

groovy
stage('Deploy') {
    steps {
        milestone 1
        sh 'deploy.sh'
    }
}

等待步骤 #

waitUntil #

等待条件满足:

groovy
steps {
    waitUntil {
        script {
            def r = sh script: 'curl -s http://localhost:8080/health', returnStatus: true
            return r == 0
        }
    }
    
    waitUntil(initialRecurrencePeriod: 5000, quiet: false) {
        script {
            return fileExists 'marker.txt'
        }
    }
}

sleep #

暂停执行:

groovy
steps {
    sleep time: 10, unit: 'SECONDS'
    sleep 30
    sleep(time: 1, unit: 'MINUTES')
}

retry #

重试执行:

groovy
steps {
    retry(3) {
        sh 'deploy.sh'
    }
    
    retry(count: 3, conditions: [
        unstable(),
        failure()
    ]) {
        sh 'test.sh'
    }
}

timeout #

超时控制:

groovy
steps {
    timeout(time: 5, unit: 'MINUTES') {
        sh 'long-running-task.sh'
    }
    
    timeout(time: 30, activity: true) {
        input message: 'Approve deployment?'
    }
}

构建触发步骤 #

build #

触发其他任务:

groovy
steps {
    build job: 'downstream-job',
          parameters: [
              string(name: 'VERSION', value: "${BUILD_NUMBER}"),
              booleanParam(name: 'DEPLOY', value: true)
          ],
          wait: true,
          propagate: true,
          quietPeriod: 10
}

triggerRemoteJob #

触发远程任务:

groovy
steps {
    triggerRemoteJob(
        project: 'remote-job',
        auth: 'credentials-id',
        parameters: 'VERSION=${BUILD_NUMBER}',
        shouldNotFailBuild: true
    )
}

工具步骤 #

tool #

使用预配置工具:

groovy
steps {
    def mvnHome = tool 'Maven 3.8'
    def javaHome = tool name: 'JDK 11', type: 'jdk'
    
    env.PATH = "${mvnHome}/bin:${env.PATH}"
    env.JAVA_HOME = javaHome
    
    sh 'mvn clean package'
}

withMaven #

Maven环境:

groovy
steps {
    withMaven(maven: 'Maven 3.8', jdk: 'JDK 11') {
        sh 'mvn clean package'
    }
    
    withMaven(
        maven: 'Maven 3.8',
        jdk: 'JDK 11',
        mavenSettingsConfig: 'maven-settings',
        mavenLocalRepo: '.repository'
    ) {
        sh 'mvn clean package'
    }
}

withAnt #

Ant环境:

groovy
steps {
    withAnt(installation: 'Ant 1.10') {
        sh 'ant build'
    }
}

withGradle #

Gradle环境:

groovy
steps {
    withGradle {
        sh 'gradle build'
    }
}

环境步骤 #

withEnv #

设置环境变量:

groovy
steps {
    withEnv(['PATH+MAVEN=/opt/maven/bin', 'JAVA_HOME=/usr/lib/jvm/java-11']) {
        sh 'mvn clean package'
    }
    
    withEnv(['MY_VAR=value', 'ANOTHER_VAR=another']) {
        echo "MY_VAR: ${env.MY_VAR}"
    }
}

withAnt #

配置环境:

groovy
steps {
    withEnv(["BUILD_ENV=production"]) {
        sh 'deploy.sh'
    }
}

锁定步骤 #

lock #

锁定资源:

groovy
steps {
    lock(resource: 'production-server') {
        sh 'deploy.sh'
    }
    
    lock(resource: 'production-server', inversePrecedence: true, quantity: 1) {
        sh 'deploy.sh'
    }
    
    lock(label: 'production', quantity: 2) {
        sh 'deploy.sh'
    }
}

完整示例 #

综合使用各种步骤 #

groovy
pipeline {
    agent any
    
    environment {
        APP_NAME = 'myapp'
        VERSION = "${BUILD_NUMBER}"
    }
    
    stages {
        stage('Checkout') {
            steps {
                checkout scm
                script {
                    env.GIT_COMMIT = sh(script: 'git rev-parse HEAD', returnStdout: true).trim()
                }
                echo "Commit: ${env.GIT_COMMIT}"
            }
        }
        
        stage('Build') {
            steps {
                withMaven(maven: 'Maven 3.8', jdk: 'JDK 11') {
                    sh 'mvn clean package -DskipTests'
                }
                archiveArtifacts artifacts: 'target/*.jar', fingerprint: true
                stash includes: 'target/*.jar', name: 'artifacts'
            }
        }
        
        stage('Test') {
            parallel {
                stage('Unit Tests') {
                    steps {
                        sh 'mvn test'
                    }
                    post {
                        always {
                            junit '**/target/surefire-reports/*.xml'
                        }
                    }
                }
                stage('Integration Tests') {
                    steps {
                        sh 'mvn verify -P integration'
                    }
                    post {
                        always {
                            junit '**/target/failsafe-reports/*.xml'
                        }
                    }
                }
            }
        }
        
        stage('SonarQube') {
            steps {
                withSonarQubeEnv('SonarQube') {
                    sh 'mvn sonar:sonar'
                }
            }
        }
        
        stage('Quality Gate') {
            steps {
                timeout(time: 5, unit: 'MINUTES') {
                    waitForQualityGate abortPipeline: true
                }
            }
        }
        
        stage('Build Image') {
            steps {
                script {
                    docker.withRegistry('https://registry.example.com', 'docker-registry') {
                        def image = docker.build("${APP_NAME}:${VERSION}")
                        image.push()
                        image.push('latest')
                    }
                }
            }
        }
        
        stage('Deploy') {
            when {
                branch 'main'
            }
            steps {
                input message: 'Deploy to production?',
                       submitter: 'admin'
                
                lock(resource: 'production-environment') {
                    unstash 'artifacts'
                    
                    withCredentials([file(credentialsId: 'kube-config', variable: 'KUBECONFIG')]) {
                        sh "kubectl set image deployment/${APP_NAME} ${APP_NAME}=${APP_NAME}:${VERSION}"
                    }
                    
                    waitUntil {
                        script {
                            def r = sh(script: 'kubectl rollout status deployment/${APP_NAME}', returnStatus: true)
                            return r == 0
                        }
                    }
                }
            }
        }
    }
    
    post {
        always {
            cleanWs()
        }
        success {
            emailext(
                subject: "Success: ${JOB_NAME} #${BUILD_NUMBER}",
                body: '''Build succeeded!
                
Commit: ${GIT_COMMIT}
URL: ${BUILD_URL}''',
                to: 'team@example.com'
            )
            slackSend(
                channel: '#builds',
                color: 'good',
                message: "Build ${BUILD_NUMBER} succeeded: ${BUILD_URL}"
            )
        }
        failure {
            emailext(
                subject: "Failed: ${JOB_NAME} #${BUILD_NUMBER}",
                body: 'Build failed!',
                to: 'team@example.com',
                attachLog: true
            )
            slackSend(
                channel: '#builds',
                color: 'danger',
                message: "Build ${BUILD_NUMBER} failed: ${BUILD_URL}"
            )
        }
    }
}

下一步学习 #

小结 #

  • 步骤是Pipeline的基本执行单元
  • 支持丰富的内置步骤
  • 可以使用插件扩展更多步骤
  • 合理组合步骤实现复杂流程
  • 使用Pipeline Syntax生成器辅助编写
最后更新:2026-03-28