Scripted Pipeline 语法 #

Scripted Pipeline是基于Groovy的脚本式语法,提供了更高的灵活性和控制能力,适合复杂的构建场景。

基本结构 #

groovy
node {
    stage('Build') {
        echo 'Building...'
    }
}

Node和Stage #

node块 #

node块是Scripted Pipeline的核心,分配一个执行器和工作空间:

groovy
node {
    // 在任意可用节点执行
}

node('linux') {
    // 在标签为linux的节点执行
}

node('linux && docker') {
    // 在同时具有linux和docker标签的节点执行
}

stage块 #

stage块用于组织构建阶段:

groovy
node {
    stage('Checkout') {
        // 检出代码
    }
    
    stage('Build') {
        // 构建项目
    }
    
    stage('Test') {
        // 运行测试
    }
    
    stage('Deploy') {
        // 部署应用
    }
}

基本步骤 #

echo #

输出信息:

groovy
node {
    echo 'Hello, Jenkins!'
    echo "Build number: ${BUILD_NUMBER}"
    echo "Current time: ${new Date()}"
}

sh #

执行Shell命令:

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

bat #

执行Windows批处理命令:

groovy
node('windows') {
    bat 'echo Hello'
    bat '''
        echo Multi-line script
        dir
    '''
}

流程控制 #

if-else条件 #

groovy
node {
    stage('Deploy') {
        if (env.BRANCH_NAME == 'main') {
            sh 'deploy-to-prod.sh'
        } else if (env.BRANCH_NAME == 'develop') {
            sh 'deploy-to-dev.sh'
        } else {
            echo 'Skip deployment'
        }
    }
}

switch-case #

groovy
node {
    stage('Deploy') {
        switch(env.ENVIRONMENT) {
            case 'prod':
                sh 'deploy-prod.sh'
                break
            case 'staging':
                sh 'deploy-staging.sh'
                break
            case 'dev':
                sh 'deploy-dev.sh'
                break
            default:
                error "Unknown environment: ${env.ENVIRONMENT}"
        }
    }
}

for循环 #

groovy
node {
    def environments = ['dev', 'staging', 'prod']
    
    for (env in environments) {
        stage("Deploy to ${env}") {
            sh "deploy.sh ${env}"
        }
    }
}

each迭代 #

groovy
node {
    def services = [
        'api': 'api-service',
        'web': 'web-service',
        'worker': 'worker-service'
    ]
    
    services.each { name, service ->
        stage("Deploy ${name}") {
            sh "kubectl apply -f ${service}.yaml"
        }
    }
}

异常处理 #

try-catch-finally #

groovy
node {
    try {
        stage('Build') {
            sh 'mvn clean package'
        }
        
        stage('Test') {
            sh 'mvn test'
        }
        
        stage('Deploy') {
            sh 'deploy.sh'
        }
        
        currentBuild.result = 'SUCCESS'
        
    } catch (Exception e) {
        currentBuild.result = 'FAILURE'
        echo "Build failed: ${e.message}"
        throw e
        
    } finally {
        stage('Cleanup') {
            sh 'cleanup.sh'
        }
    }
}

error #

立即终止构建:

groovy
node {
    stage('Validate') {
        def config = readFile 'config.json'
        if (!config) {
            error 'Configuration file not found'
        }
    }
}

unstable #

标记构建为不稳定:

groovy
node {
    stage('Test') {
        def result = sh(script: 'npm test', returnStatus: true)
        if (result != 0) {
            currentBuild.result = 'UNSTABLE'
            echo 'Some tests failed'
        }
    }
}

并行执行 #

parallel步骤 #

groovy
node {
    stage('Test') {
        parallel(
            unitTest: {
                sh 'mvn test'
            },
            integrationTest: {
                sh 'mvn verify -P integration'
            },
            e2eTest: {
                sh 'npm run test:e2e'
            },
            failFast: true
        )
    }
}

动态并行 #

groovy
node {
    def tests = [:]
    
    def testFiles = findFiles(glob: '**/*Test.java')
    testFiles.each { f ->
        tests["Test ${f.name}"] = {
            sh "mvn test -Dtest=${f.name}"
        }
    }
    
    stage('Run Tests') {
        parallel tests
    }
}

并行失败处理 #

groovy
node {
    stage('Test') {
        def result = parallel(
            unitTest: {
                try {
                    sh 'mvn test'
                } catch (e) {
                    echo "Unit tests failed: ${e.message}"
                    throw e
                }
            },
            integrationTest: {
                sh 'mvn verify -P integration'
            },
            failFast: false
        )
    }
}

函数定义 #

基本函数 #

groovy
def buildApp(String appName) {
    echo "Building ${appName}"
    sh "mvn clean package -pl ${appName}"
}

def deployApp(String appName, String env) {
    echo "Deploying ${appName} to ${env}"
    sh "kubectl apply -f ${appName}/${env}.yaml"
}

node {
    stage('Build') {
        buildApp('myapp')
    }
    
    stage('Deploy') {
        deployApp('myapp', 'prod')
    }
}

带默认参数的函数 #

groovy
def deploy(String appName, String env = 'dev') {
    echo "Deploying ${appName} to ${env}"
    sh "deploy.sh ${appName} ${env}"
}

node {
    deploy('myapp')           // 部署到dev
    deploy('myapp', 'prod')   // 部署到prod
}

返回值的函数 #

groovy
def getGitCommit() {
    return sh(script: 'git rev-parse HEAD', returnStdout: true).trim()
}

def getVersion() {
    def pom = readFile 'pom.xml'
    def matcher = pom =~ '<version>(.+?)</version>'
    return matcher ? matcher[0][1] : 'unknown'
}

node {
    stage('Info') {
        def commit = getGitCommit()
        def version = getVersion()
        echo "Commit: ${commit}"
        echo "Version: ${version}"
    }
}

闭包参数 #

groovy
def withRetry(int times, Closure body) {
    int count = 0
    while (count < times) {
        try {
            body()
            return
        } catch (e) {
            count++
            if (count >= times) {
                throw e
            }
            echo "Retry ${count}/${times}"
            sleep 5
        }
    }
}

node {
    stage('Deploy') {
        withRetry(3) {
            sh 'deploy.sh'
        }
    }
}

环境变量 #

设置环境变量 #

groovy
node {
    env.APP_NAME = 'myapp'
    env.VERSION = "${BUILD_NUMBER}"
    
    withEnv(['PATH+MAVEN=/opt/maven/bin', 'JAVA_HOME=/usr/lib/jvm/java-11']) {
        sh 'mvn clean package'
    }
}

读取环境变量 #

groovy
node {
    echo "Build number: ${env.BUILD_NUMBER}"
    echo "Job name: ${env.JOB_NAME}"
    echo "Workspace: ${env.WORKSPACE}"
    
    def customVar = env.CUSTOM_VAR ?: 'default'
    echo "Custom var: ${customVar}"
}

凭据使用 #

withCredentials #

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

SSH凭据 #

groovy
node {
    withCredentials([sshUserPrivateKey(
        credentialsId: 'ssh-key',
        keyFileVariable: 'SSH_KEY',
        usernameVariable: 'SSH_USER'
    )]) {
        sh "ssh -i ${SSH_KEY} ${SSH_USER}@server 'deploy.sh'"
    }
}

文件凭据 #

groovy
node {
    withCredentials([file(
        credentialsId: 'kube-config',
        variable: 'KUBECONFIG'
    )]) {
        sh 'kubectl apply -f deployment.yaml'
    }
}

工具配置 #

使用预配置工具 #

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

withMaven #

groovy
node {
    withMaven(maven: 'Maven 3.8', jdk: 'JDK 11') {
        sh 'mvn clean package'
    }
}

构建触发 #

构建其他任务 #

groovy
node {
    stage('Trigger') {
        build job: 'downstream-job',
              parameters: [
                  string(name: 'VERSION', value: "${BUILD_NUMBER}")
              ],
              wait: true,
              propagate: true
    }
}

等待其他任务 #

groovy
node {
    stage('Wait') {
        def buildInfo = build job: 'upstream-job',
                              wait: true,
                              propagate: false
        
        if (buildInfo.result == 'SUCCESS') {
            echo 'Upstream build succeeded'
        } else {
            error 'Upstream build failed'
        }
    }
}

输入和确认 #

input步骤 #

groovy
node {
    stage('Deploy') {
        def input = input message: 'Deploy to production?',
                           ok: 'Deploy',
                           parameters: [
                               choice(name: 'ENVIRONMENT',
                                      choices: ['prod', 'staging'],
                                      description: 'Select environment')
                           ],
                           submitter: 'admin,deploy-team'
        
        echo "Deploying to ${input.ENVIRONMENT}"
        sh "deploy.sh ${input.ENVIRONMENT}"
    }
}

确认对话框 #

groovy
node {
    stage('Deploy') {
        def proceed = input message: 'Continue with deployment?',
                             ok: 'Yes',
                             submitter: 'admin'
        
        sh 'deploy.sh'
    }
}

文件操作 #

读写文件 #

groovy
node {
    def content = readFile 'config.json'
    echo "Content: ${content}"
    
    writeFile file: 'output.txt',
              text: "Build ${BUILD_NUMBER}",
              encoding: 'UTF-8'
}

文件查找 #

groovy
node {
    def files = findFiles(glob: '**/*.java')
    files.each { f ->
        echo "Found: ${f.name} (${f.length} bytes)"
    }
    
    def exists = fileExists 'config.json'
    if (exists) {
        echo 'Config file exists'
    }
}

目录操作 #

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

暂存文件 #

stash/unstash #

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

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

多个stash #

groovy
node {
    stage('Build') {
        sh 'mvn clean package'
        stash includes: 'target/*.jar', name: 'jars'
        stash includes: 'target/site/**', name: 'docs'
    }
}

node {
    stage('Deploy') {
        unstash 'jars'
        sh 'deploy.sh'
    }
}

node {
    stage('Publish Docs') {
        unstash 'docs'
        sh 'publish-docs.sh'
    }
}

锁定资源 #

lock步骤 #

groovy
node {
    stage('Deploy') {
        lock(resource: 'production-server', inversePrecedence: true) {
            sh 'deploy.sh'
        }
    }
}

锁定多个资源 #

groovy
node {
    stage('Integration Test') {
        lock(resources: ['db-server', 'cache-server'], quantity: 1) {
            sh 'run-integration-tests.sh'
        }
    }
}

完整示例 #

复杂CI/CD流水线 #

groovy
def buildApp(String appName) {
    echo "Building ${appName}"
    sh "mvn clean package -pl ${appName} -am"
}

def runTests(String appName) {
    sh "mvn test -pl ${appName}"
    junit "**/${appName}/target/surefire-reports/*.xml"
}

def deploy(String appName, String env) {
    echo "Deploying ${appName} to ${env}"
    sh "kubectl set image deployment/${appName} ${appName}=myregistry/${appName}:${BUILD_NUMBER} --namespace=${env}"
}

node {
    def apps = ['api', 'web', 'worker']
    
    try {
        stage('Checkout') {
            checkout scm
        }
        
        stage('Build') {
            parallel apps.collectEntries { app ->
                ["Build ${app}": {
                    buildApp(app)
                }]
            }
        }
        
        stage('Test') {
            parallel apps.collectEntries { app ->
                ["Test ${app}": {
                    runTests(app)
                }]
            }
        }
        
        stage('SonarQube') {
            withSonarQubeEnv('SonarQube') {
                sh 'mvn sonar:sonar'
            }
        }
        
        stage('Quality Gate') {
            timeout(time: 5, unit: 'MINUTES') {
                def qg = waitForQualityGate()
                if (qg.status != 'OK') {
                    error "Quality gate failed: ${qg.status}"
                }
            }
        }
        
        stage('Build Images') {
            apps.each { app ->
                sh "docker build -t myregistry/${app}:${BUILD_NUMBER} ${app}/"
            }
            
            withCredentials([usernamePassword(
                credentialsId: 'docker-registry',
                usernameVariable: 'DOCKER_USER',
                passwordVariable: 'DOCKER_PASS'
            )]) {
                sh "docker login -u ${DOCKER_USER} -p ${DOCKER_PASS} myregistry"
                apps.each { app ->
                    sh "docker push myregistry/${app}:${BUILD_NUMBER}"
                }
            }
        }
        
        if (env.BRANCH_NAME == 'main') {
            stage('Deploy to Production') {
                input message: 'Deploy to production?',
                       ok: 'Deploy',
                       submitter: 'admin'
                
                lock(resource: 'production-environment') {
                    apps.each { app ->
                        deploy(app, 'production')
                    }
                }
            }
        }
        
        currentBuild.result = 'SUCCESS'
        
    } catch (Exception e) {
        currentBuild.result = 'FAILURE'
        echo "Build failed: ${e.message}"
        throw e
        
    } finally {
        stage('Cleanup') {
            cleanWs()
        }
        
        mail to: 'team@example.com',
             subject: "${currentBuild.result}: ${JOB_NAME} #${BUILD_NUMBER}",
             body: "Build ${currentBuild.result}\nURL: ${BUILD_URL}"
    }
}

Declarative vs Scripted #

特性 Declarative Scripted
语法 结构化声明 Groovy脚本
学习曲线
灵活性 中等
可读性 中等
错误检查 编译时 运行时
适用场景 大多数CI/CD 复杂逻辑

最佳实践 #

1. 优先使用Declarative #

groovy
// 推荐
pipeline {
    agent any
    stages { ... }
}

// 仅在需要复杂逻辑时使用Scripted
node {
    // 复杂的Groovy逻辑
}

2. 合理使用try-catch #

groovy
try {
    // 可能失败的操作
} catch (e) {
    currentBuild.result = 'FAILURE'
    throw e
}

3. 使用函数封装逻辑 #

groovy
def deploy(String env) {
    // 封装部署逻辑
}

4. 避免过度复杂 #

groovy
// 保持简单
stage('Build') {
    sh 'mvn clean package'
}

下一步学习 #

小结 #

  • Scripted Pipeline基于Groovy
  • 提供更高的灵活性
  • 适合复杂的构建场景
  • 需要Groovy编程知识
  • 大多数情况下推荐Declarative
最后更新:2026-03-28