尝试本地配置sonarQube scanner
This commit is contained in:
parent
92ae9761a2
commit
1103cd95e0
14
Jenkinsfile
vendored
14
Jenkinsfile
vendored
@ -9,6 +9,7 @@ pipeline {
|
||||
|
||||
tools {
|
||||
go 'go' // 使用Jenkins手动配置的Go工具
|
||||
sonarRunnerInstallation 'sonarQube' // 使用Jenkins配置的SonarQube工具
|
||||
}
|
||||
|
||||
environment {
|
||||
@ -157,20 +158,15 @@ pipeline {
|
||||
echo "SONAR_SCANNER_HOME: $SONAR_SCANNER_HOME"
|
||||
echo "PATH: $PATH"
|
||||
|
||||
# 查找sonar-scanner工具位置
|
||||
SCANNER_PATH=""
|
||||
# 直接使用配置的sonar-scanner
|
||||
if [ -n "$SONAR_SCANNER_HOME" ] && [ -f "$SONAR_SCANNER_HOME/bin/sonar-scanner" ]; then
|
||||
SCANNER_PATH="$SONAR_SCANNER_HOME/bin/sonar-scanner"
|
||||
elif [ -f "/var/jenkins_home/tools/hudson.plugins.sonar.SonarRunnerInstallation/sonarQube/bin/sonar-scanner" ]; then
|
||||
SCANNER_PATH="/var/jenkins_home/tools/hudson.plugins.sonar.SonarRunnerInstallation/sonarQube/bin/sonar-scanner"
|
||||
elif command -v sonar-scanner >/dev/null 2>&1; then
|
||||
SCANNER_PATH="sonar-scanner"
|
||||
else
|
||||
echo "❌ 无法找到sonar-scanner,跳过扫描"
|
||||
exit 0
|
||||
echo "❌ SONAR_SCANNER_HOME未正确配置"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "✅ 找到sonar-scanner: $SCANNER_PATH"
|
||||
echo "✅ 使用sonar-scanner: $SCANNER_PATH"
|
||||
|
||||
# 运行SonarQube扫描
|
||||
"$SCANNER_PATH" \
|
||||
|
769
Jenkinsfile.template
Normal file
769
Jenkinsfile.template
Normal file
@ -0,0 +1,769 @@
|
||||
pipeline {
|
||||
agent any
|
||||
|
||||
// 参数化配置 - 让模板更通用
|
||||
parameters {
|
||||
choice(
|
||||
name: 'DEPLOY_ENV',
|
||||
choices: ['auto', 'production', 'staging', 'development'],
|
||||
description: '部署环境 (auto=根据分支自动选择)'
|
||||
)
|
||||
booleanParam(
|
||||
name: 'SKIP_TESTS',
|
||||
defaultValue: false,
|
||||
description: '跳过单元测试'
|
||||
)
|
||||
booleanParam(
|
||||
name: 'SKIP_SONAR',
|
||||
defaultValue: false,
|
||||
description: '跳过代码质量扫描'
|
||||
)
|
||||
booleanParam(
|
||||
name: 'FORCE_REBUILD',
|
||||
defaultValue: false,
|
||||
description: '强制重新构建Docker镜像'
|
||||
)
|
||||
string(
|
||||
name: 'GO_VERSION',
|
||||
defaultValue: '1.21',
|
||||
description: 'Go版本'
|
||||
)
|
||||
string(
|
||||
name: 'IMAGE_REGISTRY',
|
||||
defaultValue: '',
|
||||
description: 'Docker镜像仓库 (留空使用本地)'
|
||||
)
|
||||
}
|
||||
|
||||
options {
|
||||
buildDiscarder(logRotator(
|
||||
numToKeepStr: '20',
|
||||
daysToKeepStr: '30',
|
||||
artifactNumToKeepStr: '10'
|
||||
))
|
||||
timeout(time: 60, unit: 'MINUTES')
|
||||
timestamps()
|
||||
parallelsAlwaysFailFast()
|
||||
skipDefaultCheckout()
|
||||
}
|
||||
|
||||
tools {
|
||||
go 'go' // Jenkins中配置的Go工具名称
|
||||
}
|
||||
|
||||
environment {
|
||||
// ===========================================
|
||||
// 📝 项目配置 - 根据你的项目修改这些变量
|
||||
// ===========================================
|
||||
PROJECT_NAME = 'golang-demo' // 🔧 修改为你的项目名
|
||||
DEPLOY_SERVER = '116.62.163.84' // 🔧 修改为你的服务器
|
||||
SSH_CREDENTIAL_ID = 'deploy-server-ssh-key' // 🔧 修改为你的SSH凭据ID
|
||||
|
||||
// SonarQube配置 (可选)
|
||||
SONAR_HOST_URL = 'http://116.62.163.84:15010' // 🔧 修改为你的SonarQube地址
|
||||
SONAR_PROJECT_KEY = "${PROJECT_NAME}"
|
||||
SONAR_CREDENTIAL_ID = 'sonar-token' // 🔧 修改为你的SonarQube凭据ID
|
||||
|
||||
// ===========================================
|
||||
// 🔧 环境端口配置
|
||||
// ===========================================
|
||||
PROD_PORT = '15021' // 生产环境端口
|
||||
STAGING_PORT = '15022' // 预发布环境端口
|
||||
DEV_PORT = '15023' // 开发环境端口
|
||||
|
||||
// ===========================================
|
||||
// 🔧 自动化配置 - 通常不需要修改
|
||||
// ===========================================
|
||||
CGO_ENABLED = '0'
|
||||
GOOS = 'linux'
|
||||
GOARCH = 'amd64'
|
||||
|
||||
// 动态变量
|
||||
IMAGE_NAME = "${PROJECT_NAME}"
|
||||
IMAGE_TAG = "${BUILD_NUMBER}"
|
||||
FULL_IMAGE_NAME = "${params.IMAGE_REGISTRY ? params.IMAGE_REGISTRY + '/' : ''}${IMAGE_NAME}:${IMAGE_TAG}"
|
||||
}
|
||||
|
||||
stages {
|
||||
stage('🔄 初始化') {
|
||||
parallel {
|
||||
stage('检出代码') {
|
||||
steps {
|
||||
echo "🔄 检出代码..."
|
||||
checkout scm
|
||||
|
||||
script {
|
||||
env.GIT_COMMIT_SHORT = sh(
|
||||
script: "git rev-parse --short HEAD",
|
||||
returnStdout: true
|
||||
).trim()
|
||||
|
||||
env.GIT_BRANCH = sh(
|
||||
script: "git rev-parse --abbrev-ref HEAD",
|
||||
returnStdout: true
|
||||
).trim()
|
||||
|
||||
// 根据分支自动确定部署环境
|
||||
if (params.DEPLOY_ENV == 'auto') {
|
||||
if (env.BRANCH_NAME == 'main' || env.BRANCH_NAME == 'master') {
|
||||
env.DEPLOY_ENV = 'production'
|
||||
env.DEPLOY_PORT = env.PROD_PORT
|
||||
} else if (env.BRANCH_NAME == 'staging' || env.BRANCH_NAME == 'release') {
|
||||
env.DEPLOY_ENV = 'staging'
|
||||
env.DEPLOY_PORT = env.STAGING_PORT
|
||||
} else {
|
||||
env.DEPLOY_ENV = 'development'
|
||||
env.DEPLOY_PORT = env.DEV_PORT
|
||||
}
|
||||
} else {
|
||||
env.DEPLOY_ENV = params.DEPLOY_ENV
|
||||
switch(params.DEPLOY_ENV) {
|
||||
case 'production':
|
||||
env.DEPLOY_PORT = env.PROD_PORT
|
||||
break
|
||||
case 'staging':
|
||||
env.DEPLOY_PORT = env.STAGING_PORT
|
||||
break
|
||||
default:
|
||||
env.DEPLOY_PORT = env.DEV_PORT
|
||||
}
|
||||
}
|
||||
|
||||
env.CONTAINER_NAME = "${PROJECT_NAME}-${DEPLOY_ENV}"
|
||||
}
|
||||
|
||||
echo "📋 构建信息:"
|
||||
echo " 项目: ${PROJECT_NAME}"
|
||||
echo " 分支: ${env.BRANCH_NAME}"
|
||||
echo " 提交: ${env.GIT_COMMIT_SHORT}"
|
||||
echo " 环境: ${env.DEPLOY_ENV}"
|
||||
echo " 端口: ${env.DEPLOY_PORT}"
|
||||
}
|
||||
}
|
||||
|
||||
stage('环境检查') {
|
||||
steps {
|
||||
echo '🔍 检查构建环境...'
|
||||
sh '''
|
||||
echo "=== 系统信息 ==="
|
||||
uname -a
|
||||
|
||||
echo "=== Go版本 ==="
|
||||
go version
|
||||
|
||||
echo "=== Go环境 ==="
|
||||
go env GOROOT GOPATH GOPROXY GOSUMDB
|
||||
|
||||
echo "=== Docker版本 ==="
|
||||
docker --version
|
||||
docker system info --format "{{.Name}}: {{.ServerVersion}}"
|
||||
|
||||
echo "=== 工作目录 ==="
|
||||
pwd && ls -la
|
||||
|
||||
echo "=== 检查Go项目结构 ==="
|
||||
if [ -f "go.mod" ]; then
|
||||
echo "✅ 发现go.mod文件"
|
||||
cat go.mod | head -10
|
||||
else
|
||||
echo "⚠️ 未发现go.mod文件,可能不是Go模块项目"
|
||||
fi
|
||||
'''
|
||||
echo "✅ 环境检查完成"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stage('📦 依赖管理') {
|
||||
steps {
|
||||
echo '📦 管理Go依赖...'
|
||||
sh '''
|
||||
echo "下载依赖..."
|
||||
go mod download -x
|
||||
|
||||
echo "验证依赖..."
|
||||
go mod verify
|
||||
|
||||
echo "整理依赖..."
|
||||
go mod tidy
|
||||
|
||||
echo "检查依赖更新..."
|
||||
go list -u -m all | head -20 || true
|
||||
|
||||
echo "✅ 依赖管理完成"
|
||||
'''
|
||||
}
|
||||
}
|
||||
|
||||
stage('🔍 代码质量') {
|
||||
parallel {
|
||||
stage('静态检查') {
|
||||
steps {
|
||||
echo '🔍 运行Go静态检查...'
|
||||
sh '''
|
||||
echo "运行go vet..."
|
||||
go vet ./... || {
|
||||
echo "❌ go vet 发现问题"
|
||||
exit 1
|
||||
}
|
||||
|
||||
echo "检查代码格式..."
|
||||
UNFORMATTED=$(gofmt -l .)
|
||||
if [ -n "$UNFORMATTED" ]; then
|
||||
echo "❌ 以下文件格式不正确:"
|
||||
echo "$UNFORMATTED"
|
||||
echo "请运行: go fmt ./..."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "检查import排序..."
|
||||
if command -v goimports >/dev/null 2>&1; then
|
||||
goimports -l . | head -10
|
||||
fi
|
||||
|
||||
echo "✅ 静态检查通过"
|
||||
'''
|
||||
}
|
||||
}
|
||||
|
||||
stage('安全扫描') {
|
||||
when {
|
||||
not { params.SKIP_TESTS }
|
||||
}
|
||||
steps {
|
||||
echo '🔒 运行安全扫描...'
|
||||
sh '''
|
||||
echo "检查已知漏洞..."
|
||||
if command -v govulncheck >/dev/null 2>&1; then
|
||||
govulncheck ./... || echo "⚠️ govulncheck未安装或发现问题"
|
||||
else
|
||||
echo "⚠️ govulncheck未安装,跳过漏洞检查"
|
||||
fi
|
||||
|
||||
echo "检查敏感信息..."
|
||||
if command -v git-secrets >/dev/null 2>&1; then
|
||||
git secrets --scan --recursive . || echo "⚠️ 发现敏感信息"
|
||||
fi
|
||||
|
||||
echo "✅ 安全扫描完成"
|
||||
'''
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stage('🧪 测试') {
|
||||
when {
|
||||
not { params.SKIP_TESTS }
|
||||
}
|
||||
parallel {
|
||||
stage('单元测试') {
|
||||
steps {
|
||||
echo '🧪 运行单元测试...'
|
||||
sh '''
|
||||
echo "运行测试..."
|
||||
mkdir -p test-results
|
||||
|
||||
# 运行测试并生成多种格式的报告
|
||||
go test -v -coverprofile=coverage.out -covermode=atomic \
|
||||
-json ./... > test-results/test-report.json
|
||||
|
||||
# 生成JUnit格式的测试报告(如果有go-junit-report)
|
||||
if command -v go-junit-report >/dev/null 2>&1; then
|
||||
cat test-results/test-report.json | go-junit-report > test-results/junit.xml
|
||||
fi
|
||||
|
||||
echo "生成覆盖率报告..."
|
||||
go tool cover -html=coverage.out -o coverage.html
|
||||
go tool cover -func=coverage.out | tee coverage-summary.txt
|
||||
|
||||
# 提取覆盖率百分比
|
||||
COVERAGE=$(go tool cover -func=coverage.out | grep total | grep -oE '[0-9]+\.[0-9]+%')
|
||||
echo "📊 总覆盖率: $COVERAGE"
|
||||
echo "$COVERAGE" > coverage.txt
|
||||
|
||||
echo "✅ 单元测试完成"
|
||||
'''
|
||||
}
|
||||
post {
|
||||
always {
|
||||
script {
|
||||
// 发布测试报告
|
||||
if (fileExists('test-results/junit.xml')) {
|
||||
publishTestResults testResultsPattern: 'test-results/junit.xml'
|
||||
}
|
||||
|
||||
// 发布覆盖率报告
|
||||
if (fileExists('coverage.html')) {
|
||||
publishHTML([
|
||||
allowMissing: false,
|
||||
alwaysLinkToLastBuild: true,
|
||||
keepAll: true,
|
||||
reportDir: '.',
|
||||
reportFiles: 'coverage.html',
|
||||
reportName: 'Go Coverage Report',
|
||||
reportTitles: ''
|
||||
])
|
||||
}
|
||||
|
||||
// 归档测试产物
|
||||
archiveArtifacts artifacts: 'coverage.out,coverage.txt,test-results/*', allowEmptyArchive: true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stage('性能测试') {
|
||||
steps {
|
||||
echo '⚡ 运行性能测试...'
|
||||
sh '''
|
||||
echo "运行基准测试..."
|
||||
if go test -bench=. -benchmem ./... > benchmark.txt 2>&1; then
|
||||
echo "✅ 基准测试完成"
|
||||
cat benchmark.txt
|
||||
else
|
||||
echo "⚠️ 未发现基准测试或测试失败"
|
||||
fi
|
||||
'''
|
||||
}
|
||||
post {
|
||||
always {
|
||||
archiveArtifacts artifacts: 'benchmark.txt', allowEmptyArchive: true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stage('📊 代码扫描') {
|
||||
when {
|
||||
not { params.SKIP_SONAR }
|
||||
}
|
||||
steps {
|
||||
echo '📊 运行SonarQube代码扫描...'
|
||||
script {
|
||||
try {
|
||||
withCredentials([string(credentialsId: "${SONAR_CREDENTIAL_ID}", variable: 'SONAR_TOKEN')]) {
|
||||
withSonarQubeEnv('sonarQube') {
|
||||
sh '''
|
||||
# 动态查找sonar-scanner
|
||||
SCANNER_PATH=""
|
||||
for path in \
|
||||
"$SONAR_SCANNER_HOME/bin/sonar-scanner" \
|
||||
"/var/jenkins_home/tools/hudson.plugins.sonar.SonarRunnerInstallation/sonarQube/bin/sonar-scanner" \
|
||||
"$(which sonar-scanner 2>/dev/null)"; do
|
||||
if [ -f "$path" ]; then
|
||||
SCANNER_PATH="$path"
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
if [ -z "$SCANNER_PATH" ]; then
|
||||
echo "❌ 无法找到sonar-scanner"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "✅ 使用sonar-scanner: $SCANNER_PATH"
|
||||
|
||||
# 运行扫描
|
||||
"$SCANNER_PATH" \\
|
||||
-Dsonar.projectKey=${SONAR_PROJECT_KEY} \\
|
||||
-Dsonar.projectName="${PROJECT_NAME}" \\
|
||||
-Dsonar.projectVersion=${BUILD_NUMBER} \\
|
||||
-Dsonar.sources=. \\
|
||||
-Dsonar.exclusions=**/*_test.go,**/vendor/**,**/*.mod,**/*.sum,**/test-results/** \\
|
||||
-Dsonar.tests=. \\
|
||||
-Dsonar.test.inclusions=**/*_test.go \\
|
||||
-Dsonar.test.exclusions=**/vendor/** \\
|
||||
-Dsonar.go.coverage.reportPaths=coverage.out \\
|
||||
-Dsonar.sourceEncoding=UTF-8
|
||||
'''
|
||||
}
|
||||
}
|
||||
echo "✅ SonarQube扫描完成"
|
||||
} catch (Exception e) {
|
||||
echo "⚠️ SonarQube扫描失败: ${e.getMessage()}"
|
||||
if (env.DEPLOY_ENV == 'production') {
|
||||
error("生产环境必须通过代码质量检查")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stage('🔨 构建') {
|
||||
parallel {
|
||||
stage('编译应用') {
|
||||
steps {
|
||||
echo '🔨 编译Go应用...'
|
||||
sh '''
|
||||
echo "开始编译..."
|
||||
|
||||
# 设置构建信息
|
||||
BUILD_TIME=$(date -u '+%Y-%m-%d_%H:%M:%S')
|
||||
LDFLAGS="-w -s -X main.Version=${BUILD_NUMBER} -X main.GitCommit=${GIT_COMMIT_SHORT} -X main.BuildTime=${BUILD_TIME}"
|
||||
|
||||
# 编译
|
||||
CGO_ENABLED=${CGO_ENABLED} GOOS=${GOOS} GOARCH=${GOARCH} \\
|
||||
go build -ldflags="$LDFLAGS" -o ${PROJECT_NAME} .
|
||||
|
||||
echo "验证二进制文件..."
|
||||
ls -lh ${PROJECT_NAME}
|
||||
file ${PROJECT_NAME}
|
||||
|
||||
# 尝试获取版本信息
|
||||
./${PROJECT_NAME} --version 2>/dev/null || echo "应用不支持--version参数"
|
||||
|
||||
echo "✅ 编译完成"
|
||||
'''
|
||||
}
|
||||
post {
|
||||
success {
|
||||
archiveArtifacts artifacts: "${PROJECT_NAME}", fingerprint: true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stage('准备部署文件') {
|
||||
steps {
|
||||
echo '📝 准备部署配置文件...'
|
||||
sh '''
|
||||
# 创建部署脚本
|
||||
cat > deploy.sh << 'EOF'
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
PROJECT_NAME="${PROJECT_NAME}"
|
||||
CONTAINER_NAME="${CONTAINER_NAME}"
|
||||
DEPLOY_PORT="${DEPLOY_PORT}"
|
||||
IMAGE_TAG="${IMAGE_TAG}"
|
||||
|
||||
echo "🚀 部署 $PROJECT_NAME 到 ${DEPLOY_ENV} 环境..."
|
||||
|
||||
# 停止旧容器
|
||||
echo "停止旧容器..."
|
||||
docker stop $CONTAINER_NAME 2>/dev/null || true
|
||||
docker rm $CONTAINER_NAME 2>/dev/null || true
|
||||
|
||||
# 启动新容器
|
||||
echo "启动新容器..."
|
||||
docker run -d \\
|
||||
--name $CONTAINER_NAME \\
|
||||
--restart unless-stopped \\
|
||||
-p $DEPLOY_PORT:8080 \\
|
||||
-e GIN_MODE=release \\
|
||||
-e DEPLOY_ENV=${DEPLOY_ENV} \\
|
||||
$PROJECT_NAME:$IMAGE_TAG
|
||||
|
||||
echo "✅ 部署完成"
|
||||
echo "🔗 访问地址: http://$(hostname -I | awk '{print $1}'):$DEPLOY_PORT"
|
||||
EOF
|
||||
|
||||
chmod +x deploy.sh
|
||||
echo "✅ 部署脚本准备完成"
|
||||
'''
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stage('🐳 Docker镜像') {
|
||||
steps {
|
||||
echo '🐳 构建Docker镜像...'
|
||||
script {
|
||||
// 检查是否需要重新构建
|
||||
def shouldBuild = params.FORCE_REBUILD
|
||||
if (!shouldBuild) {
|
||||
def imageExists = sh(
|
||||
script: "docker images -q ${IMAGE_NAME}:${IMAGE_TAG}",
|
||||
returnStdout: true
|
||||
).trim()
|
||||
shouldBuild = imageExists.isEmpty()
|
||||
}
|
||||
|
||||
if (shouldBuild) {
|
||||
sh '''
|
||||
echo "开始构建Docker镜像..."
|
||||
|
||||
# 确保二进制文件存在
|
||||
if [ ! -f "${PROJECT_NAME}" ]; then
|
||||
echo "❌ 二进制文件不存在"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
chmod +x ${PROJECT_NAME}
|
||||
|
||||
# 构建镜像
|
||||
docker build \\
|
||||
--build-arg PROJECT_NAME=${PROJECT_NAME} \\
|
||||
--build-arg BUILD_DATE=$(date -u +"%Y-%m-%dT%H:%M:%SZ") \\
|
||||
--build-arg VCS_REF=${GIT_COMMIT_SHORT} \\
|
||||
--build-arg VERSION=${BUILD_NUMBER} \\
|
||||
-t ${IMAGE_NAME}:${IMAGE_TAG} \\
|
||||
-t ${IMAGE_NAME}:latest \\
|
||||
.
|
||||
|
||||
echo "镜像构建完成"
|
||||
docker images ${IMAGE_NAME}:${IMAGE_TAG}
|
||||
|
||||
# 检查镜像大小
|
||||
IMAGE_SIZE=$(docker images --format "table {{.Size}}" ${IMAGE_NAME}:${IMAGE_TAG} | tail -n 1)
|
||||
echo "📦 镜像大小: $IMAGE_SIZE"
|
||||
'''
|
||||
|
||||
// 推送到镜像仓库(如果配置了)
|
||||
if (params.IMAGE_REGISTRY) {
|
||||
sh '''
|
||||
echo "推送镜像到仓库..."
|
||||
docker tag ${IMAGE_NAME}:${IMAGE_TAG} ${FULL_IMAGE_NAME}
|
||||
docker push ${FULL_IMAGE_NAME}
|
||||
echo "✅ 镜像推送完成"
|
||||
'''
|
||||
}
|
||||
} else {
|
||||
echo "✅ 镜像已存在,跳过构建"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stage('🧪 镜像测试') {
|
||||
steps {
|
||||
echo '🧪 测试Docker镜像...'
|
||||
script {
|
||||
def testContainerName = "test-${PROJECT_NAME}-${BUILD_NUMBER}"
|
||||
def testPort = "808${BUILD_NUMBER % 100}"
|
||||
|
||||
try {
|
||||
sh """
|
||||
echo "启动测试容器..."
|
||||
docker run -d --name ${testContainerName} \\
|
||||
-p ${testPort}:8080 \\
|
||||
-e GIN_MODE=test \\
|
||||
${IMAGE_NAME}:${IMAGE_TAG}
|
||||
|
||||
echo "等待应用启动..."
|
||||
sleep 15
|
||||
|
||||
echo "检查容器状态..."
|
||||
docker ps | grep ${testContainerName}
|
||||
docker logs ${testContainerName}
|
||||
|
||||
echo "测试应用端点..."
|
||||
for i in \$(seq 1 10); do
|
||||
echo "尝试连接 \$i/10..."
|
||||
if curl -f http://localhost:${testPort}/health; then
|
||||
echo "✅ 健康检查通过"
|
||||
break
|
||||
elif curl -f http://localhost:${testPort}/ping; then
|
||||
echo "✅ Ping检查通过"
|
||||
break
|
||||
else
|
||||
if [ \$i -eq 10 ]; then
|
||||
echo "❌ 镜像测试失败"
|
||||
exit 1
|
||||
fi
|
||||
echo "等待3秒后重试..."
|
||||
sleep 3
|
||||
fi
|
||||
done
|
||||
|
||||
echo "✅ 镜像测试通过"
|
||||
"""
|
||||
} finally {
|
||||
// 清理测试容器
|
||||
sh """
|
||||
docker stop ${testContainerName} || true
|
||||
docker rm ${testContainerName} || true
|
||||
"""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stage('🚀 部署') {
|
||||
when {
|
||||
anyOf {
|
||||
branch 'main'
|
||||
branch 'master'
|
||||
branch 'develop'
|
||||
branch 'staging'
|
||||
expression { params.DEPLOY_ENV != 'auto' }
|
||||
}
|
||||
}
|
||||
steps {
|
||||
echo "🚀 部署到${env.DEPLOY_ENV}环境..."
|
||||
script {
|
||||
sshagent([env.SSH_CREDENTIAL_ID]) {
|
||||
sh '''
|
||||
echo "📤 传输部署文件..."
|
||||
|
||||
# 保存镜像
|
||||
docker save ${IMAGE_NAME}:${IMAGE_TAG} -o ${PROJECT_NAME}-${IMAGE_TAG}.tar
|
||||
|
||||
# 传输文件
|
||||
scp -o StrictHostKeyChecking=no \\
|
||||
${PROJECT_NAME}-${IMAGE_TAG}.tar \\
|
||||
deploy.sh \\
|
||||
root@${DEPLOY_SERVER}:/tmp/
|
||||
|
||||
# 远程部署
|
||||
ssh -o StrictHostKeyChecking=no root@${DEPLOY_SERVER} << EOF
|
||||
cd /tmp
|
||||
|
||||
echo "🔄 加载Docker镜像..."
|
||||
docker load -i ${PROJECT_NAME}-${IMAGE_TAG}.tar
|
||||
|
||||
echo "🚀 执行部署..."
|
||||
chmod +x deploy.sh
|
||||
./deploy.sh
|
||||
|
||||
echo "🧹 清理临时文件..."
|
||||
rm -f ${PROJECT_NAME}-${IMAGE_TAG}.tar deploy.sh
|
||||
|
||||
echo "✅ 部署完成"
|
||||
EOF
|
||||
|
||||
# 清理本地临时文件
|
||||
rm -f ${PROJECT_NAME}-${IMAGE_TAG}.tar
|
||||
'''
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stage('🏥 健康检查') {
|
||||
when {
|
||||
anyOf {
|
||||
branch 'main'
|
||||
branch 'master'
|
||||
branch 'develop'
|
||||
branch 'staging'
|
||||
expression { params.DEPLOY_ENV != 'auto' }
|
||||
}
|
||||
}
|
||||
steps {
|
||||
echo '🏥 执行应用健康检查...'
|
||||
script {
|
||||
sleep(time: 30, unit: 'SECONDS')
|
||||
|
||||
def healthCheckPassed = false
|
||||
for (int i = 1; i <= 5; i++) {
|
||||
try {
|
||||
def response = sh(
|
||||
script: "curl -s -o /dev/null -w '%{http_code}' http://${DEPLOY_SERVER}:${env.DEPLOY_PORT}/health",
|
||||
returnStdout: true
|
||||
).trim()
|
||||
|
||||
if (response == "200") {
|
||||
echo "✅ 第${i}次健康检查通过"
|
||||
healthCheckPassed = true
|
||||
break
|
||||
} else {
|
||||
echo "⚠️ 第${i}次健康检查失败,状态码: ${response}"
|
||||
}
|
||||
} catch (Exception e) {
|
||||
echo "⚠️ 第${i}次健康检查异常: ${e.getMessage()}"
|
||||
}
|
||||
|
||||
if (i < 5) {
|
||||
echo "等待30秒后重试..."
|
||||
sleep(time: 30, unit: 'SECONDS')
|
||||
}
|
||||
}
|
||||
|
||||
if (!healthCheckPassed && env.DEPLOY_ENV == 'production') {
|
||||
error("❌ 生产环境健康检查失败,回滚部署")
|
||||
} else if (!healthCheckPassed) {
|
||||
echo "⚠️ 健康检查失败,但允许${env.DEPLOY_ENV}环境继续"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
post {
|
||||
always {
|
||||
script {
|
||||
echo '🧹 执行清理工作...'
|
||||
|
||||
// 清理构建产物
|
||||
sh """
|
||||
rm -f ${PROJECT_NAME} coverage.out coverage.html deploy.sh
|
||||
rm -rf test-results/
|
||||
docker image prune -f || true
|
||||
docker builder prune -f || true
|
||||
"""
|
||||
|
||||
// 发布构建徽章
|
||||
def badgeText = "${env.DEPLOY_ENV}: ${currentBuild.currentResult}"
|
||||
def badgeColor = currentBuild.currentResult == 'SUCCESS' ? 'brightgreen' : 'red'
|
||||
addBadge(icon: "success.gif", text: badgeText, color: badgeColor)
|
||||
}
|
||||
}
|
||||
|
||||
success {
|
||||
script {
|
||||
def message = """
|
||||
🎉 ${PROJECT_NAME} 构建成功!
|
||||
|
||||
📋 项目: ${env.JOB_NAME}
|
||||
🔢 构建号: ${env.BUILD_NUMBER}
|
||||
🌿 分支: ${env.BRANCH_NAME ?: 'unknown'}
|
||||
📝 提交: ${env.GIT_COMMIT_SHORT ?: 'unknown'}
|
||||
🌍 环境: ${env.DEPLOY_ENV}
|
||||
⏱️ 耗时: ${currentBuild.durationString}
|
||||
🔗 访问: http://${DEPLOY_SERVER}:${env.DEPLOY_PORT}
|
||||
📊 报告: ${env.BUILD_URL}
|
||||
"""
|
||||
|
||||
echo message
|
||||
|
||||
// 发送通知 (可选)
|
||||
// slackSend(color: 'good', message: message)
|
||||
// emailext(subject: "✅ ${PROJECT_NAME} 构建成功", body: message)
|
||||
}
|
||||
}
|
||||
|
||||
failure {
|
||||
script {
|
||||
def message = """
|
||||
💥 ${PROJECT_NAME} 构建失败!
|
||||
|
||||
📋 项目: ${env.JOB_NAME}
|
||||
🔢 构建号: ${env.BUILD_NUMBER}
|
||||
🌿 分支: ${env.BRANCH_NAME ?: 'unknown'}
|
||||
📝 提交: ${env.GIT_COMMIT_SHORT ?: 'unknown'}
|
||||
🌍 环境: ${env.DEPLOY_ENV}
|
||||
⏱️ 耗时: ${currentBuild.durationString}
|
||||
🔗 日志: ${env.BUILD_URL}console
|
||||
📧 修复后请重新构建
|
||||
"""
|
||||
|
||||
echo message
|
||||
|
||||
// 清理失败的资源
|
||||
sh """
|
||||
docker stop test-${PROJECT_NAME}-${BUILD_NUMBER} || true
|
||||
docker rm test-${PROJECT_NAME}-${BUILD_NUMBER} || true
|
||||
"""
|
||||
|
||||
// 发送失败通知
|
||||
// slackSend(color: 'danger', message: message)
|
||||
// emailext(subject: "❌ ${PROJECT_NAME} 构建失败", body: message)
|
||||
}
|
||||
}
|
||||
|
||||
cleanup {
|
||||
script {
|
||||
try {
|
||||
cleanWs(deleteDirs: true)
|
||||
echo "✅ 工作空间清理完成"
|
||||
} catch (Exception e) {
|
||||
echo "⚠️ 清理失败: ${e.getMessage()}"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user