10.1 共享库概述
什么是共享库
共享库定义:
Jenkins共享库(Shared Libraries)是一种将Pipeline代码模块化和重用的机制。
它允许团队创建可重用的Pipeline代码,并在多个项目中共享。
核心特性:
- 代码重用:避免重复编写相同的Pipeline逻辑
- 标准化:确保所有项目使用一致的构建流程
- 维护性:集中管理和更新Pipeline代码
- 版本控制:支持库的版本管理和回滚
- 安全性:提供受信任的代码执行环境
共享库优势:
传统Pipeline vs 共享库:
传统Pipeline:
- 每个项目重复编写相似代码
- 难以维护和更新
- 不一致的实现方式
- 缺乏标准化流程
共享库:
- 代码重用和标准化
- 集中维护和更新
- 版本化管理
- 更好的测试覆盖
- 团队协作和知识共享
使用场景:
适用场景:
1. 多项目环境
- 多个项目使用相似的构建流程
- 需要标准化CI/CD流程
- 团队协作开发
2. 复杂Pipeline逻辑
- 复杂的部署策略
- 多环境管理
- 高级错误处理
3. 企业级应用
- 合规性要求
- 安全策略实施
- 审计和监控
4. DevOps最佳实践
- 基础设施即代码
- 持续改进
- 知识管理
共享库架构
目录结构:
shared-library/
├── src/ # Groovy源代码
│ └── com/
│ └── company/
│ └── jenkins/
│ ├── Build.groovy
│ ├── Deploy.groovy
│ └── Utils.groovy
├── vars/ # 全局变量和函数
│ ├── buildApp.groovy
│ ├── deployApp.groovy
│ ├── notifyTeam.groovy
│ └── runTests.groovy
├── resources/ # 资源文件
│ ├── scripts/
│ │ ├── deploy.sh
│ │ └── test.sh
│ ├── templates/
│ │ ├── Dockerfile.template
│ │ └── k8s-deployment.yaml
│ └── config/
│ ├── sonar.properties
│ └── checkstyle.xml
└── test/ # 测试代码
├── groovy/
│ └── com/
│ └── company/
│ └── jenkins/
│ ├── BuildTest.groovy
│ └── DeployTest.groovy
└── resources/
└── test-data/
组件说明:
核心组件:
1. src/ 目录
- 包含Groovy类和方法
- 支持面向对象编程
- 可以导入和使用Java库
- 适合复杂的业务逻辑
2. vars/ 目录
- 包含全局变量和函数
- 可以直接在Pipeline中调用
- 支持参数化调用
- 适合简单的工具函数
3. resources/ 目录
- 存储静态资源文件
- 脚本、模板、配置文件
- 可以在Pipeline中读取
- 支持文件模板化
4. test/ 目录
- 单元测试和集成测试
- 确保库的质量和稳定性
- 支持TDD开发模式
- 自动化测试执行
10.2 创建共享库
基础设置
创建库仓库:
# 1. 创建Git仓库
mkdir jenkins-shared-library
cd jenkins-shared-library
git init
# 2. 创建基本目录结构
mkdir -p src/com/company/jenkins
mkdir -p vars
mkdir -p resources/{scripts,templates,config}
mkdir -p test/groovy/com/company/jenkins
# 3. 创建README文件
cat > README.md << 'EOF'
# Jenkins Shared Library
这是公司的Jenkins共享库,包含标准化的CI/CD流程和工具函数。
## 使用方法
```groovy
@Library('company-shared-library') _
pipeline {
agent any
stages {
stage('Build') {
steps {
buildApp()
}
}
}
}
函数列表
buildApp()
: 构建应用deployApp()
: 部署应用runTests()
: 运行测试notifyTeam()
: 发送通知
EOF
4. 提交初始代码
git add . git commit -m “Initial shared library structure” git remote add origin https://github.com/company/jenkins-shared-library.git git push -u origin main
**Jenkins配置:**
```groovy
// 在Jenkins中配置共享库
// 管理Jenkins -> 系统配置 -> Global Pipeline Libraries
// 库配置示例:
Name: company-shared-library
Default version: main
Retrieval method: Modern SCM
Source Code Management: Git
Repository URL: https://github.com/company/jenkins-shared-library.git
Credentials: github-credentials
Behaviors:
- Discover branches
- Discover pull requests from origin
- Discover pull requests from forks
vars目录函数
buildApp.groovy:
#!/usr/bin/env groovy
/**
* 构建应用程序
* @param config 构建配置
*/
def call(Map config = [:]) {
// 默认配置
def defaultConfig = [
buildTool: 'maven',
javaVersion: '11',
skipTests: false,
profile: 'default'
]
// 合并配置
config = defaultConfig + config
echo "开始构建应用,配置: ${config}"
// 设置Java环境
def javaHome = tool name: "JDK-${config.javaVersion}", type: 'jdk'
env.JAVA_HOME = javaHome
env.PATH = "${javaHome}/bin:${env.PATH}"
// 根据构建工具执行构建
switch (config.buildTool.toLowerCase()) {
case 'maven':
buildWithMaven(config)
break
case 'gradle':
buildWithGradle(config)
break
case 'npm':
buildWithNpm(config)
break
default:
error "不支持的构建工具: ${config.buildTool}"
}
echo '应用构建完成'
}
/**
* Maven构建
*/
private def buildWithMaven(config) {
def mvnHome = tool name: 'Maven-3.8.1', type: 'maven'
def mvnCmd = "${mvnHome}/bin/mvn"
// 构建命令
def goals = ['clean', 'compile']
if (!config.skipTests) {
goals.add('test')
}
goals.add('package')
def profiles = config.profile != 'default' ? "-P${config.profile}" : ''
def command = "${mvnCmd} ${goals.join(' ')} ${profiles}"
echo "执行Maven命令: ${command}"
sh command
// 发布测试结果
if (!config.skipTests) {
publishTestResults testResultsPattern: '**/target/surefire-reports/*.xml'
}
// 归档构建产物
archiveArtifacts artifacts: '**/target/*.jar,**/target/*.war', fingerprint: true
}
/**
* Gradle构建
*/
private def buildWithGradle(config) {
def gradleHome = tool name: 'Gradle-7.0', type: 'gradle'
def gradleCmd = "${gradleHome}/bin/gradle"
def tasks = ['clean', 'build']
if (config.skipTests) {
tasks.add('-x test')
}
def command = "${gradleCmd} ${tasks.join(' ')}"
echo "执行Gradle命令: ${command}"
sh command
// 发布测试结果
if (!config.skipTests) {
publishTestResults testResultsPattern: '**/build/test-results/test/*.xml'
}
// 归档构建产物
archiveArtifacts artifacts: '**/build/libs/*.jar', fingerprint: true
}
/**
* NPM构建
*/
private def buildWithNpm(config) {
def nodeHome = tool name: 'NodeJS-16', type: 'nodejs'
env.PATH = "${nodeHome}/bin:${env.PATH}"
echo '安装依赖'
sh 'npm ci'
if (!config.skipTests) {
echo '运行测试'
sh 'npm test'
// 发布测试结果
publishTestResults testResultsPattern: 'test-results.xml'
}
echo '构建应用'
sh 'npm run build'
// 归档构建产物
archiveArtifacts artifacts: 'dist/**', fingerprint: true
}
deployApp.groovy:
#!/usr/bin/env groovy
/**
* 部署应用程序
* @param config 部署配置
*/
def call(Map config = [:]) {
// 默认配置
def defaultConfig = [
environment: 'staging',
strategy: 'rolling',
replicas: 3,
healthCheck: true,
rollbackOnFailure: true,
timeout: 600
]
// 合并配置
config = defaultConfig + config
echo "开始部署应用到 ${config.environment} 环境"
echo "部署配置: ${config}"
// 验证配置
validateDeployConfig(config)
try {
// 根据环境选择部署策略
switch (config.environment.toLowerCase()) {
case 'development':
case 'dev':
deployToDev(config)
break
case 'staging':
case 'test':
deployToStaging(config)
break
case 'production':
case 'prod':
deployToProduction(config)
break
default:
error "不支持的部署环境: ${config.environment}"
}
// 健康检查
if (config.healthCheck) {
performHealthCheck(config)
}
echo "应用成功部署到 ${config.environment} 环境"
} catch (Exception e) {
echo "部署失败: ${e.getMessage()}"
// 自动回滚
if (config.rollbackOnFailure) {
echo '执行自动回滚'
rollbackDeployment(config)
}
throw e
}
}
/**
* 验证部署配置
*/
private def validateDeployConfig(config) {
def requiredFields = ['environment']
requiredFields.each { field ->
if (!config.containsKey(field) || !config[field]) {
error "缺少必需的配置字段: ${field}"
}
}
// 验证副本数
if (config.replicas < 1) {
error "副本数必须大于0: ${config.replicas}"
}
// 验证超时时间
if (config.timeout < 60) {
error "超时时间必须至少60秒: ${config.timeout}"
}
}
/**
* 部署到开发环境
*/
private def deployToDev(config) {
echo '部署到开发环境'
// 简单的Docker部署
sh """
docker stop myapp-dev || true
docker rm myapp-dev || true
docker run -d --name myapp-dev -p 8080:8080 \
myapp:${env.BUILD_NUMBER}
"""
}
/**
* 部署到测试环境
*/
private def deployToStaging(config) {
echo '部署到测试环境'
// Kubernetes部署
def deploymentYaml = libraryResource('templates/k8s-deployment.yaml')
// 替换模板变量
deploymentYaml = deploymentYaml
.replace('{{APP_NAME}}', 'myapp')
.replace('{{IMAGE_TAG}}', env.BUILD_NUMBER)
.replace('{{ENVIRONMENT}}', 'staging')
.replace('{{REPLICAS}}', config.replicas.toString())
// 写入临时文件
writeFile file: 'deployment.yaml', text: deploymentYaml
// 应用部署
sh 'kubectl apply -f deployment.yaml'
// 等待部署完成
sh "kubectl rollout status deployment/myapp-staging --timeout=${config.timeout}s"
}
/**
* 部署到生产环境
*/
private def deployToProduction(config) {
echo '部署到生产环境'
// 生产环境需要额外的安全检查
if (!env.BRANCH_NAME?.equals('main')) {
error '只能从main分支部署到生产环境'
}
// 蓝绿部署或滚动更新
switch (config.strategy.toLowerCase()) {
case 'blue-green':
deployBlueGreen(config)
break
case 'rolling':
deployRolling(config)
break
case 'canary':
deployCanary(config)
break
default:
error "不支持的部署策略: ${config.strategy}"
}
}
/**
* 蓝绿部署
*/
private def deployBlueGreen(config) {
echo '执行蓝绿部署'
// 获取当前活跃环境
def currentEnv = sh(
script: 'kubectl get service myapp-prod -o jsonpath="{.spec.selector.version}"',
returnStdout: true
).trim()
def newEnv = currentEnv == 'blue' ? 'green' : 'blue'
echo "当前环境: ${currentEnv}, 新环境: ${newEnv}"
// 部署到新环境
sh """
kubectl set image deployment/myapp-prod-${newEnv} \
myapp=myapp:${env.BUILD_NUMBER}
kubectl rollout status deployment/myapp-prod-${newEnv} --timeout=${config.timeout}s
"""
// 切换流量
sh "kubectl patch service myapp-prod -p '{\"spec\":{\"selector\":{\"version\":\"${newEnv}\"}}}'"
echo "流量已切换到 ${newEnv} 环境"
}
/**
* 滚动更新
*/
private def deployRolling(config) {
echo '执行滚动更新'
sh """
kubectl set image deployment/myapp-prod \
myapp=myapp:${env.BUILD_NUMBER}
kubectl rollout status deployment/myapp-prod --timeout=${config.timeout}s
"""
}
/**
* 金丝雀部署
*/
private def deployCanary(config) {
echo '执行金丝雀部署'
// 部署金丝雀版本(10%流量)
sh """
kubectl set image deployment/myapp-canary \
myapp=myapp:${env.BUILD_NUMBER}
kubectl rollout status deployment/myapp-canary --timeout=${config.timeout}s
"""
// 等待监控数据
echo '等待金丝雀版本监控数据...'
sleep(time: 300, unit: 'SECONDS')
// 检查金丝雀版本健康状态
def canaryHealthy = checkCanaryHealth()
if (canaryHealthy) {
echo '金丝雀版本健康,执行全量部署'
deployRolling(config)
} else {
error '金丝雀版本不健康,停止部署'
}
}
/**
* 健康检查
*/
private def performHealthCheck(config) {
echo '执行健康检查'
def maxRetries = 10
def retryInterval = 30
for (int i = 0; i < maxRetries; i++) {
try {
def healthUrl = getHealthCheckUrl(config.environment)
def response = sh(
script: "curl -f ${healthUrl}",
returnStatus: true
)
if (response == 0) {
echo '健康检查通过'
return
}
} catch (Exception e) {
echo "健康检查失败 (${i + 1}/${maxRetries}): ${e.getMessage()}"
}
if (i < maxRetries - 1) {
echo "等待 ${retryInterval} 秒后重试"
sleep(time: retryInterval, unit: 'SECONDS')
}
}
error '健康检查失败,应用可能未正常启动'
}
/**
* 获取健康检查URL
*/
private def getHealthCheckUrl(environment) {
def urls = [
'development': 'http://localhost:8080/health',
'staging': 'http://staging.company.com/health',
'production': 'http://api.company.com/health'
]
return urls[environment.toLowerCase()] ?: 'http://localhost:8080/health'
}
/**
* 检查金丝雀版本健康状态
*/
private def checkCanaryHealth() {
// 这里可以集成监控系统API
// 检查错误率、响应时间等指标
echo '检查金丝雀版本指标'
// 模拟检查逻辑
def errorRate = sh(
script: 'curl -s http://monitoring.company.com/api/error-rate/canary',
returnStdout: true
).trim().toDouble()
def responseTime = sh(
script: 'curl -s http://monitoring.company.com/api/response-time/canary',
returnStdout: true
).trim().toDouble()
echo "金丝雀版本错误率: ${errorRate}%"
echo "金丝雀版本响应时间: ${responseTime}ms"
// 健康标准
return errorRate < 1.0 && responseTime < 500
}
/**
* 回滚部署
*/
private def rollbackDeployment(config) {
echo "回滚 ${config.environment} 环境的部署"
switch (config.environment.toLowerCase()) {
case 'development':
case 'dev':
sh 'docker stop myapp-dev && docker start myapp-dev-backup'
break
case 'staging':
case 'test':
sh 'kubectl rollout undo deployment/myapp-staging'
break
case 'production':
case 'prod':
sh 'kubectl rollout undo deployment/myapp-prod'
break
}
echo '回滚完成'
}
runTests.groovy:
#!/usr/bin/env groovy
/**
* 运行测试套件
* @param config 测试配置
*/
def call(Map config = [:]) {
// 默认配置
def defaultConfig = [
types: ['unit', 'integration'],
parallel: true,
coverage: true,
quality: true,
security: false,
performance: false,
failFast: false
]
// 合并配置
config = defaultConfig + config
echo "开始运行测试,配置: ${config}"
try {
if (config.parallel) {
runTestsParallel(config)
} else {
runTestsSequential(config)
}
// 生成测试报告
generateTestReports(config)
echo '所有测试完成'
} catch (Exception e) {
echo "测试失败: ${e.getMessage()}"
if (config.failFast) {
throw e
} else {
currentBuild.result = 'UNSTABLE'
}
}
}
/**
* 并行运行测试
*/
private def runTestsParallel(config) {
def parallelTests = [:]
config.types.each { testType ->
parallelTests["${testType} Tests"] = {
runTestType(testType, config)
}
}
// 添加其他测试类型
if (config.coverage) {
parallelTests['Coverage Analysis'] = {
runCoverageAnalysis(config)
}
}
if (config.quality) {
parallelTests['Code Quality'] = {
runQualityAnalysis(config)
}
}
if (config.security) {
parallelTests['Security Scan'] = {
runSecurityScan(config)
}
}
if (config.performance) {
parallelTests['Performance Test'] = {
runPerformanceTest(config)
}
}
parallel parallelTests
}
/**
* 顺序运行测试
*/
private def runTestsSequential(config) {
config.types.each { testType ->
runTestType(testType, config)
}
if (config.coverage) {
runCoverageAnalysis(config)
}
if (config.quality) {
runQualityAnalysis(config)
}
if (config.security) {
runSecurityScan(config)
}
if (config.performance) {
runPerformanceTest(config)
}
}
/**
* 运行特定类型的测试
*/
private def runTestType(testType, config) {
echo "运行 ${testType} 测试"
switch (testType.toLowerCase()) {
case 'unit':
runUnitTests(config)
break
case 'integration':
runIntegrationTests(config)
break
case 'e2e':
case 'end-to-end':
runE2ETests(config)
break
case 'api':
runApiTests(config)
break
case 'ui':
runUITests(config)
break
default:
echo "未知测试类型: ${testType}"
}
}
/**
* 运行单元测试
*/
private def runUnitTests(config) {
echo '执行单元测试'
// 根据项目类型选择测试命令
if (fileExists('pom.xml')) {
sh 'mvn test'
publishTestResults testResultsPattern: '**/target/surefire-reports/*.xml'
} else if (fileExists('build.gradle')) {
sh 'gradle test'
publishTestResults testResultsPattern: '**/build/test-results/test/*.xml'
} else if (fileExists('package.json')) {
sh 'npm test'
publishTestResults testResultsPattern: 'test-results.xml'
} else {
echo '未找到支持的构建文件,跳过单元测试'
}
}
/**
* 运行集成测试
*/
private def runIntegrationTests(config) {
echo '执行集成测试'
// 启动测试数据库
sh 'docker run -d --name test-db -e POSTGRES_DB=testdb -e POSTGRES_PASSWORD=test postgres:13'
try {
// 等待数据库启动
sleep(time: 30, unit: 'SECONDS')
if (fileExists('pom.xml')) {
sh 'mvn verify -P integration-tests'
publishTestResults testResultsPattern: '**/target/failsafe-reports/*.xml'
} else if (fileExists('build.gradle')) {
sh 'gradle integrationTest'
publishTestResults testResultsPattern: '**/build/test-results/integrationTest/*.xml'
} else {
echo '未找到支持的构建文件,跳过集成测试'
}
} finally {
// 清理测试数据库
sh 'docker stop test-db && docker rm test-db'
}
}
/**
* 运行端到端测试
*/
private def runE2ETests(config) {
echo '执行端到端测试'
// 启动应用
sh 'docker run -d --name app-under-test -p 8080:8080 myapp:latest'
try {
// 等待应用启动
sleep(time: 60, unit: 'SECONDS')
// 运行E2E测试
if (fileExists('cypress.json')) {
sh 'npx cypress run --record --key $CYPRESS_RECORD_KEY'
} else if (fileExists('selenium-tests/')) {
sh 'mvn test -P e2e-tests'
} else {
echo '未找到E2E测试配置,跳过端到端测试'
}
} finally {
// 清理应用
sh 'docker stop app-under-test && docker rm app-under-test'
}
}
/**
* 运行API测试
*/
private def runApiTests(config) {
echo '执行API测试'
if (fileExists('postman/')) {
sh 'newman run postman/collection.json -e postman/environment.json --reporters cli,junit'
publishTestResults testResultsPattern: 'newman-results.xml'
} else if (fileExists('rest-assured-tests/')) {
sh 'mvn test -P api-tests'
publishTestResults testResultsPattern: '**/target/surefire-reports/*.xml'
} else {
echo '未找到API测试配置,跳过API测试'
}
}
/**
* 运行UI测试
*/
private def runUITests(config) {
echo '执行UI测试'
if (fileExists('selenium-tests/')) {
sh 'mvn test -P ui-tests'
publishTestResults testResultsPattern: '**/target/surefire-reports/*.xml'
} else if (fileExists('playwright.config.js')) {
sh 'npx playwright test'
publishTestResults testResultsPattern: 'test-results.xml'
} else {
echo '未找到UI测试配置,跳过UI测试'
}
}
/**
* 运行覆盖率分析
*/
private def runCoverageAnalysis(config) {
echo '执行代码覆盖率分析'
if (fileExists('pom.xml')) {
sh 'mvn jacoco:report'
publishHTML([
allowMissing: false,
alwaysLinkToLastBuild: true,
keepAll: true,
reportDir: 'target/site/jacoco',
reportFiles: 'index.html',
reportName: 'Coverage Report'
])
} else if (fileExists('package.json')) {
sh 'npm run coverage'
publishHTML([
allowMissing: false,
alwaysLinkToLastBuild: true,
keepAll: true,
reportDir: 'coverage',
reportFiles: 'index.html',
reportName: 'Coverage Report'
])
}
}
/**
* 运行代码质量分析
*/
private def runQualityAnalysis(config) {
echo '执行代码质量分析'
if (fileExists('pom.xml')) {
sh 'mvn sonar:sonar'
} else if (fileExists('sonar-project.properties')) {
sh 'sonar-scanner'
} else {
echo '未找到SonarQube配置,跳过代码质量分析'
}
}
/**
* 运行安全扫描
*/
private def runSecurityScan(config) {
echo '执行安全扫描'
// 依赖安全扫描
if (fileExists('pom.xml')) {
sh 'mvn dependency-check:check'
publishHTML([
allowMissing: false,
alwaysLinkToLastBuild: true,
keepAll: true,
reportDir: 'target/dependency-check-report',
reportFiles: 'dependency-check-report.html',
reportName: 'Security Report'
])
} else if (fileExists('package.json')) {
sh 'npm audit --audit-level moderate'
}
// 代码安全扫描
if (fileExists('.bandit')) {
sh 'bandit -r . -f json -o bandit-report.json'
}
}
/**
* 运行性能测试
*/
private def runPerformanceTest(config) {
echo '执行性能测试'
if (fileExists('jmeter-tests/')) {
sh 'jmeter -n -t jmeter-tests/test-plan.jmx -l results.jtl'
perfReport sourceDataFiles: 'results.jtl'
} else if (fileExists('k6-tests/')) {
sh 'k6 run k6-tests/load-test.js'
} else {
echo '未找到性能测试配置,跳过性能测试'
}
}
/**
* 生成测试报告
*/
private def generateTestReports(config) {
echo '生成测试报告'
// 聚合测试结果
def testResults = [
total: 0,
passed: 0,
failed: 0,
skipped: 0
]
// 这里可以解析各种测试结果文件
// 生成统一的测试报告
echo "测试结果汇总: ${testResults}"
// 发送测试报告
if (config.notify) {
notifyTestResults(testResults)
}
}
/**
* 发送测试结果通知
*/
private def notifyTestResults(results) {
def message = """
测试执行完成
总计: ${results.total}
通过: ${results.passed}
失败: ${results.failed}
跳过: ${results.skipped}
"""
slackSend(
channel: '#ci-cd',
color: results.failed > 0 ? 'warning' : 'good',
message: message
)
}
notifyTeam.groovy:
#!/usr/bin/env groovy
/**
* 发送团队通知
* @param config 通知配置
*/
def call(Map config = [:]) {
// 默认配置
def defaultConfig = [
channels: ['slack'],
status: currentBuild.currentResult ?: 'SUCCESS',
includeChanges: true,
includeTests: true,
includeCoverage: false
]
// 合并配置
config = defaultConfig + config
echo "发送团队通知,配置: ${config}"
// 构建通知消息
def message = buildNotificationMessage(config)
// 发送到各个渠道
config.channels.each { channel ->
sendToChannel(channel, message, config)
}
}
/**
* 构建通知消息
*/
private def buildNotificationMessage(config) {
def status = config.status
def emoji = getStatusEmoji(status)
def color = getStatusColor(status)
def message = [
title: "${emoji} 构建 ${status}",
fields: []
]
// 基本信息
message.fields.add([
title: '项目',
value: env.JOB_NAME,
short: true
])
message.fields.add([
title: '构建号',
value: "#${env.BUILD_NUMBER}",
short: true
])
message.fields.add([
title: '分支',
value: env.BRANCH_NAME ?: 'unknown',
short: true
])
message.fields.add([
title: '持续时间',
value: currentBuild.durationString,
short: true
])
// 变更信息
if (config.includeChanges) {
def changes = getChangeInfo()
if (changes) {
message.fields.add([
title: '变更',
value: changes,
short: false
])
}
}
// 测试信息
if (config.includeTests) {
def testInfo = getTestInfo()
if (testInfo) {
message.fields.add([
title: '测试结果',
value: testInfo,
short: false
])
}
}
// 覆盖率信息
if (config.includeCoverage) {
def coverageInfo = getCoverageInfo()
if (coverageInfo) {
message.fields.add([
title: '代码覆盖率',
value: coverageInfo,
short: true
])
}
}
// 构建链接
message.fields.add([
title: '构建链接',
value: "<${env.BUILD_URL}|查看详情>",
short: false
])
message.color = color
return message
}
/**
* 获取状态表情符号
*/
private def getStatusEmoji(status) {
def emojis = [
'SUCCESS': '✅',
'FAILURE': '❌',
'UNSTABLE': '⚠️',
'ABORTED': '⏹️',
'NOT_BUILT': '⚪'
]
return emojis[status] ?: '❓'
}
/**
* 获取状态颜色
*/
private def getStatusColor(status) {
def colors = [
'SUCCESS': 'good',
'FAILURE': 'danger',
'UNSTABLE': 'warning',
'ABORTED': '#808080',
'NOT_BUILT': '#808080'
]
return colors[status] ?: '#808080'
}
/**
* 获取变更信息
*/
private def getChangeInfo() {
def changes = currentBuild.changeSets
if (!changes || changes.isEmpty()) {
return null
}
def changeList = []
changes.each { changeSet ->
changeSet.each { change ->
def author = change.author.displayName
def message = change.msg.take(50)
changeList.add("• ${author}: ${message}")
}
}
return changeList.take(5).join('\n')
}
/**
* 获取测试信息
*/
private def getTestInfo() {
try {
def testResult = currentBuild.rawBuild.getAction(hudson.tasks.test.AbstractTestResultAction.class)
if (testResult) {
def total = testResult.totalCount
def failed = testResult.failCount
def skipped = testResult.skipCount
def passed = total - failed - skipped
return "总计: ${total}, 通过: ${passed}, 失败: ${failed}, 跳过: ${skipped}"
}
} catch (Exception e) {
echo "获取测试信息失败: ${e.getMessage()}"
}
return null
}
/**
* 获取覆盖率信息
*/
private def getCoverageInfo() {
try {
// 这里可以集成JaCoCo或其他覆盖率工具的API
// 返回覆盖率百分比
return "85%"
} catch (Exception e) {
echo "获取覆盖率信息失败: ${e.getMessage()}"
}
return null
}
/**
* 发送到指定渠道
*/
private def sendToChannel(channel, message, config) {
switch (channel.toLowerCase()) {
case 'slack':
sendToSlack(message, config)
break
case 'email':
sendToEmail(message, config)
break
case 'teams':
sendToTeams(message, config)
break
case 'webhook':
sendToWebhook(message, config)
break
default:
echo "不支持的通知渠道: ${channel}"
}
}
/**
* 发送到Slack
*/
private def sendToSlack(message, config) {
def slackChannel = config.slackChannel ?: '#ci-cd'
slackSend(
channel: slackChannel,
color: message.color,
message: formatSlackMessage(message)
)
}
/**
* 格式化Slack消息
*/
private def formatSlackMessage(message) {
def text = message.title + '\n'
message.fields.each { field ->
text += "*${field.title}:* ${field.value}\n"
}
return text
}
/**
* 发送邮件
*/
private def sendToEmail(message, config) {
def recipients = config.emailRecipients ?: 'team@company.com'
def subject = "${message.title} - ${env.JOB_NAME} #${env.BUILD_NUMBER}"
def body = formatEmailMessage(message)
emailext(
subject: subject,
body: body,
to: recipients,
mimeType: 'text/html'
)
}
/**
* 格式化邮件消息
*/
private def formatEmailMessage(message) {
def html = """
<html>
<body>
<h2>${message.title}</h2>
<table border="1" cellpadding="5" cellspacing="0">
"""
message.fields.each { field ->
html += "<tr><td><strong>${field.title}</strong></td><td>${field.value}</td></tr>"
}
html += """
</table>
</body>
</html>
"""
return html
}
/**
* 发送到Teams
*/
private def sendToTeams(message, config) {
def webhookUrl = config.teamsWebhook
if (!webhookUrl) {
echo 'Teams webhook URL未配置'
return
}
def payload = [
'@type': 'MessageCard',
'@context': 'http://schema.org/extensions',
'themeColor': message.color == 'good' ? '00FF00' : (message.color == 'danger' ? 'FF0000' : 'FFFF00'),
'summary': message.title,
'sections': [[
'activityTitle': message.title,
'facts': message.fields.collect { field ->
['name': field.title, 'value': field.value]
}
]]
]
httpRequest(
httpMode: 'POST',
url: webhookUrl,
contentType: 'APPLICATION_JSON',
requestBody: groovy.json.JsonBuilder(payload).toString()
)
}
/**
* 发送到Webhook
*/
private def sendToWebhook(message, config) {
def webhookUrl = config.webhookUrl
if (!webhookUrl) {
echo 'Webhook URL未配置'
return
}
def payload = [
'build': [
'number': env.BUILD_NUMBER,
'status': config.status,
'url': env.BUILD_URL,
'project': env.JOB_NAME,
'branch': env.BRANCH_NAME
],
'message': message
]
httpRequest(
httpMode: 'POST',
url: webhookUrl,
contentType: 'APPLICATION_JSON',
requestBody: groovy.json.JsonBuilder(payload).toString()
)
}
本章小结
本章介绍了Jenkins共享库的基础知识,包括:
- 共享库概述:了解共享库的概念、优势和架构
- 创建共享库:学习如何创建和配置共享库
- vars目录函数:掌握全局函数的编写和使用
共享库是实现Pipeline代码重用和标准化的重要工具,能够显著提高团队的开发效率和代码质量。
下一章预告
下一章我们将继续学习共享库的高级特性,包括src目录类、资源文件使用和库的测试与发布。
练习与思考
理论练习
共享库设计:
- 分析团队的Pipeline需求
- 设计共享库的结构和功能
- 考虑版本管理策略
函数抽象:
- 识别可重用的Pipeline逻辑
- 设计通用的函数接口
- 考虑配置的灵活性
实践练习
创建基础共享库:
- 搭建共享库项目结构
- 实现基本的构建和部署函数
- 配置Jenkins使用共享库
函数开发:
- 开发通知函数
- 实现测试执行函数
- 添加错误处理和重试逻辑
思考题
最佳实践:
- 如何设计易于维护的共享库?
- 如何平衡通用性和特定需求?
- 如何确保共享库的向后兼容性?
团队协作:
- 如何管理共享库的开发和发布?
- 如何处理不同团队的需求差异?
- 如何推广共享库的使用?