ci: per-service version files, DB migration support ready
- Each service now has its own VERSION.<service> file (all at 1.0.0) - Jenkinsfile bumps each service's version independently - versions.json updated per-service (no cross-service overwrite) - Summary log shows all service:version pairs on success Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
这个提交包含在:
父节点
5593ad790e
当前提交
e774c4ef25
74
Jenkinsfile
vendored
74
Jenkinsfile
vendored
@ -5,7 +5,7 @@ pipeline {
|
||||
choice(
|
||||
name: 'SERVICE',
|
||||
choices: ['all', 'tenant-service', 'im-service', 'push-service', 'update-service', 'file-service', 'license-service', 'demo-service'],
|
||||
description: '要构建的服务模块(all = 全部,包含所有微服务)'
|
||||
description: '要构建的服务模块(all = 全部,每个服务独立版本号)'
|
||||
)
|
||||
}
|
||||
|
||||
@ -18,7 +18,6 @@ pipeline {
|
||||
COMPOSE_FILE = '/opt/xuqm/deploy/compose.production.yaml'
|
||||
VERSIONS_FILE = '/opt/xuqm/deploy/versions.json'
|
||||
DOCKER_BUILDKIT = '1'
|
||||
VERSION_FILE = 'VERSION'
|
||||
}
|
||||
|
||||
options {
|
||||
@ -39,16 +38,25 @@ pipeline {
|
||||
}
|
||||
}
|
||||
|
||||
stage('Resolve Version') {
|
||||
stage('Resolve Versions') {
|
||||
steps {
|
||||
script {
|
||||
def current = fileExists(env.VERSION_FILE) ? readFile(env.VERSION_FILE).trim() : '1.0.0'
|
||||
def allServices = ['tenant-service', 'im-service', 'push-service', 'update-service', 'file-service', 'license-service', 'demo-service']
|
||||
def targets = params.SERVICE == 'all' ? allServices : [params.SERVICE]
|
||||
|
||||
// serviceVersions: Map<svcName, newVersion>
|
||||
def serviceVersions = [:]
|
||||
for (svc in targets) {
|
||||
def vf = "VERSION.${svc}"
|
||||
def current = fileExists(vf) ? readFile(vf).trim() : '1.0.0'
|
||||
def parts = current.tokenize('.')
|
||||
while (parts.size() < 3) parts.add('0')
|
||||
def newVer = "${parts[0]}.${parts[1]}.${parts[2].toInteger() + 1}"
|
||||
env.SERVICE_VERSION = newVer
|
||||
writeFile file: env.VERSION_FILE, text: newVer
|
||||
echo "Server: ${current} → ${newVer}"
|
||||
serviceVersions[svc] = newVer
|
||||
writeFile file: vf, text: newVer
|
||||
echo "${svc}: ${current} → ${newVer}"
|
||||
}
|
||||
env.SERVICE_VERSIONS_JSON = groovy.json.JsonOutput.toJson(serviceVersions)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -57,20 +65,19 @@ pipeline {
|
||||
steps {
|
||||
withCredentials([string(credentialsId: 'ACR_PASSWORD', variable: 'ACR_PASS')]) {
|
||||
script {
|
||||
def services = params.SERVICE == 'all'
|
||||
? ['tenant-service', 'im-service', 'push-service', 'update-service', 'file-service', 'license-service', 'demo-service']
|
||||
: [params.SERVICE]
|
||||
|
||||
for (svc in services) {
|
||||
def serviceVersions = new groovy.json.JsonSlurper().parseText(env.SERVICE_VERSIONS_JSON)
|
||||
for (entry in serviceVersions) {
|
||||
def svc = entry.key
|
||||
def ver = entry.value
|
||||
def base = "${ACR_REGISTRY}/${ACR_NAMESPACE}/${svc}"
|
||||
def versionedImage = "${base}:${env.SERVICE_VERSION}"
|
||||
def versionedImage = "${base}:${ver}"
|
||||
def latestImage = "${base}:latest"
|
||||
echo "Building ${svc} ${env.SERVICE_VERSION}..."
|
||||
echo "Building ${svc}:${ver}..."
|
||||
bat """
|
||||
docker login ${ACR_REGISTRY} -u ${ACR_USERNAME} -p %ACR_PASS%
|
||||
if %errorlevel% neq 0 exit /b 1
|
||||
docker pull --platform=linux/amd64 ${latestImage} || echo Pull failed, will build fresh
|
||||
docker build --platform=linux/amd64 --build-arg SERVICE_MODULE=${svc} --build-arg SERVICE_VERSION=${env.SERVICE_VERSION} --build-arg BUILDKIT_INLINE_CACHE=1 --cache-from ${latestImage} -t ${versionedImage} -t ${latestImage} .
|
||||
docker build --platform=linux/amd64 --build-arg SERVICE_MODULE=${svc} --build-arg SERVICE_VERSION=${ver} --build-arg BUILDKIT_INLINE_CACHE=1 --cache-from ${latestImage} -t ${versionedImage} -t ${latestImage} .
|
||||
if %errorlevel% neq 0 exit /b 1
|
||||
docker push ${versionedImage}
|
||||
if %errorlevel% neq 0 exit /b 1
|
||||
@ -90,11 +97,9 @@ pipeline {
|
||||
lock('prod-deploy') {
|
||||
withCredentials([sshUserPrivateKey(credentialsId: 'PROD_SSH_KEY', keyFileVariable: 'SSH_KEY')]) {
|
||||
script {
|
||||
def services = params.SERVICE == 'all'
|
||||
? ['tenant-service', 'im-service', 'push-service', 'update-service', 'file-service', 'license-service', 'demo-service']
|
||||
: [params.SERVICE]
|
||||
|
||||
for (svc in services) {
|
||||
def serviceVersions = new groovy.json.JsonSlurper().parseText(env.SERVICE_VERSIONS_JSON)
|
||||
for (entry in serviceVersions) {
|
||||
def svc = entry.key
|
||||
def latestImage = "${ACR_REGISTRY}/${ACR_NAMESPACE}/${svc}:latest"
|
||||
echo "Deploying ${svc}..."
|
||||
retry(2) {
|
||||
@ -104,23 +109,19 @@ pipeline {
|
||||
}
|
||||
}
|
||||
|
||||
// Update versions.json on server for deployed services
|
||||
def deployedServices = params.SERVICE == 'all'
|
||||
? ['tenant-service', 'im-service', 'push-service', 'update-service', 'file-service', 'license-service', 'demo-service']
|
||||
: [params.SERVICE]
|
||||
// 合并更新 versions.json(只改动本次构建涉及的服务,不覆盖其它服务或 web 条目)
|
||||
def releasedAt = new Date().format("yyyy-MM-dd'T'HH:mm:ss'Z'", TimeZone.getTimeZone('UTC'))
|
||||
def serviceEntries = deployedServices.collect { svc ->
|
||||
"d.setdefault('services', {})['${svc}'] = {'version': '${env.SERVICE_VERSION}', 'changed': True}"
|
||||
def serviceEntries = serviceVersions.collect { svc, ver ->
|
||||
"d.setdefault('services', {})['${svc}'] = {'version': '${ver}'}"
|
||||
}.join('\n')
|
||||
def updateScript = """\
|
||||
import json, os
|
||||
path = '${env.VERSIONS_FILE}'
|
||||
d = json.load(open(path)) if os.path.exists(path) else {}
|
||||
${serviceEntries}
|
||||
d['serviceVersion'] = '${env.SERVICE_VERSION}'
|
||||
d['releasedAt'] = '${releasedAt}'
|
||||
json.dump(d, open(path, 'w'), indent=2, ensure_ascii=False)
|
||||
print('versions.json updated: ${params.SERVICE} = ${env.SERVICE_VERSION}')
|
||||
print('versions.json updated')
|
||||
""".stripIndent()
|
||||
writeFile file: 'update_versions.py', text: updateScript
|
||||
bat """
|
||||
@ -133,14 +134,17 @@ print('versions.json updated: ${params.SERVICE} = ${env.SERVICE_VERSION}')
|
||||
}
|
||||
}
|
||||
|
||||
stage('Commit Version') {
|
||||
stage('Commit Versions') {
|
||||
steps {
|
||||
script {
|
||||
def serviceVersions = new groovy.json.JsonSlurper().parseText(env.SERVICE_VERSIONS_JSON)
|
||||
def versionFiles = serviceVersions.keySet().collect { "VERSION.${it}" }.join(' ')
|
||||
def summary = serviceVersions.collect { svc, ver -> "${svc}=${ver}" }.join(', ')
|
||||
bat """
|
||||
git config user.email "jenkins@xuqm.com"
|
||||
git config user.name "Jenkins CI"
|
||||
git add ${env.VERSION_FILE}
|
||||
git diff --cached --quiet || git commit -m "ci: bump server to ${env.SERVICE_VERSION} [skip ci]"
|
||||
git add ${versionFiles}
|
||||
git diff --cached --quiet || git commit -m "ci: bump versions [${summary}] [skip ci]"
|
||||
git push origin HEAD:main
|
||||
"""
|
||||
}
|
||||
@ -149,7 +153,13 @@ print('versions.json updated: ${params.SERVICE} = ${env.SERVICE_VERSION}')
|
||||
}
|
||||
|
||||
post {
|
||||
success { echo "✅ ${params.SERVICE} v${env.SERVICE_VERSION} 构建部署成功" }
|
||||
success {
|
||||
script {
|
||||
def serviceVersions = new groovy.json.JsonSlurper().parseText(env.SERVICE_VERSIONS_JSON ?: '{}')
|
||||
def summary = serviceVersions.collect { svc, ver -> "${svc}:${ver}" }.join(', ')
|
||||
echo "✅ 构建部署成功 — ${summary}"
|
||||
}
|
||||
}
|
||||
failure { echo "❌ 构建失败,请检查日志" }
|
||||
}
|
||||
}
|
||||
|
||||
1
VERSION.demo-service
普通文件
1
VERSION.demo-service
普通文件
@ -0,0 +1 @@
|
||||
1.0.0
|
||||
1
VERSION.file-service
普通文件
1
VERSION.file-service
普通文件
@ -0,0 +1 @@
|
||||
1.0.0
|
||||
1
VERSION.im-service
普通文件
1
VERSION.im-service
普通文件
@ -0,0 +1 @@
|
||||
1.0.0
|
||||
1
VERSION.license-service
普通文件
1
VERSION.license-service
普通文件
@ -0,0 +1 @@
|
||||
1.0.0
|
||||
1
VERSION.push-service
普通文件
1
VERSION.push-service
普通文件
@ -0,0 +1 @@
|
||||
1.0.0
|
||||
1
VERSION.tenant-service
普通文件
1
VERSION.tenant-service
普通文件
@ -0,0 +1 @@
|
||||
1.0.0
|
||||
1
VERSION.update-service
普通文件
1
VERSION.update-service
普通文件
@ -0,0 +1 @@
|
||||
1.0.0
|
||||
正在加载...
在新工单中引用
屏蔽一个用户