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