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
96
Jenkinsfile
vendored
96
Jenkinsfile
vendored
@ -5,20 +5,19 @@ pipeline {
|
|||||||
choice(
|
choice(
|
||||||
name: 'SERVICE',
|
name: 'SERVICE',
|
||||||
choices: ['all', 'tenant-service', 'im-service', 'push-service', 'update-service', 'file-service', 'license-service', 'demo-service'],
|
choices: ['all', 'tenant-service', 'im-service', 'push-service', 'update-service', 'file-service', 'license-service', 'demo-service'],
|
||||||
description: '要构建的服务模块(all = 全部,包含所有微服务)'
|
description: '要构建的服务模块(all = 全部,每个服务独立版本号)'
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
environment {
|
environment {
|
||||||
ACR_REGISTRY = 'crpi-n44qjpuucgjt8e8c.cn-beijing.personal.cr.aliyuncs.com'
|
ACR_REGISTRY = 'crpi-n44qjpuucgjt8e8c.cn-beijing.personal.cr.aliyuncs.com'
|
||||||
ACR_NAMESPACE = 'xuqmgroup'
|
ACR_NAMESPACE = 'xuqmgroup'
|
||||||
ACR_USERNAME = 'xuqinmin12'
|
ACR_USERNAME = 'xuqinmin12'
|
||||||
PROD_HOST = '106.54.23.149'
|
PROD_HOST = '106.54.23.149'
|
||||||
PROD_USER = 'ubuntu'
|
PROD_USER = 'ubuntu'
|
||||||
COMPOSE_FILE = '/opt/xuqm/deploy/compose.production.yaml'
|
COMPOSE_FILE = '/opt/xuqm/deploy/compose.production.yaml'
|
||||||
VERSIONS_FILE = '/opt/xuqm/deploy/versions.json'
|
VERSIONS_FILE = '/opt/xuqm/deploy/versions.json'
|
||||||
DOCKER_BUILDKIT = '1'
|
DOCKER_BUILDKIT = '1'
|
||||||
VERSION_FILE = 'VERSION'
|
|
||||||
}
|
}
|
||||||
|
|
||||||
options {
|
options {
|
||||||
@ -39,16 +38,25 @@ pipeline {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
stage('Resolve Version') {
|
stage('Resolve Versions') {
|
||||||
steps {
|
steps {
|
||||||
script {
|
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 parts = current.tokenize('.')
|
def targets = params.SERVICE == 'all' ? allServices : [params.SERVICE]
|
||||||
while (parts.size() < 3) parts.add('0')
|
|
||||||
def newVer = "${parts[0]}.${parts[1]}.${parts[2].toInteger() + 1}"
|
// serviceVersions: Map<svcName, newVersion>
|
||||||
env.SERVICE_VERSION = newVer
|
def serviceVersions = [:]
|
||||||
writeFile file: env.VERSION_FILE, text: newVer
|
for (svc in targets) {
|
||||||
echo "Server: ${current} → ${newVer}"
|
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}"
|
||||||
|
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 {
|
steps {
|
||||||
withCredentials([string(credentialsId: 'ACR_PASSWORD', variable: 'ACR_PASS')]) {
|
withCredentials([string(credentialsId: 'ACR_PASSWORD', variable: 'ACR_PASS')]) {
|
||||||
script {
|
script {
|
||||||
def services = params.SERVICE == 'all'
|
def serviceVersions = new groovy.json.JsonSlurper().parseText(env.SERVICE_VERSIONS_JSON)
|
||||||
? ['tenant-service', 'im-service', 'push-service', 'update-service', 'file-service', 'license-service', 'demo-service']
|
for (entry in serviceVersions) {
|
||||||
: [params.SERVICE]
|
def svc = entry.key
|
||||||
|
def ver = entry.value
|
||||||
for (svc in services) {
|
|
||||||
def base = "${ACR_REGISTRY}/${ACR_NAMESPACE}/${svc}"
|
def base = "${ACR_REGISTRY}/${ACR_NAMESPACE}/${svc}"
|
||||||
def versionedImage = "${base}:${env.SERVICE_VERSION}"
|
def versionedImage = "${base}:${ver}"
|
||||||
def latestImage = "${base}:latest"
|
def latestImage = "${base}:latest"
|
||||||
echo "Building ${svc} ${env.SERVICE_VERSION}..."
|
echo "Building ${svc}:${ver}..."
|
||||||
bat """
|
bat """
|
||||||
docker login ${ACR_REGISTRY} -u ${ACR_USERNAME} -p %ACR_PASS%
|
docker login ${ACR_REGISTRY} -u ${ACR_USERNAME} -p %ACR_PASS%
|
||||||
if %errorlevel% neq 0 exit /b 1
|
if %errorlevel% neq 0 exit /b 1
|
||||||
docker pull --platform=linux/amd64 ${latestImage} || echo Pull failed, will build fresh
|
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
|
if %errorlevel% neq 0 exit /b 1
|
||||||
docker push ${versionedImage}
|
docker push ${versionedImage}
|
||||||
if %errorlevel% neq 0 exit /b 1
|
if %errorlevel% neq 0 exit /b 1
|
||||||
@ -90,11 +97,9 @@ pipeline {
|
|||||||
lock('prod-deploy') {
|
lock('prod-deploy') {
|
||||||
withCredentials([sshUserPrivateKey(credentialsId: 'PROD_SSH_KEY', keyFileVariable: 'SSH_KEY')]) {
|
withCredentials([sshUserPrivateKey(credentialsId: 'PROD_SSH_KEY', keyFileVariable: 'SSH_KEY')]) {
|
||||||
script {
|
script {
|
||||||
def services = params.SERVICE == 'all'
|
def serviceVersions = new groovy.json.JsonSlurper().parseText(env.SERVICE_VERSIONS_JSON)
|
||||||
? ['tenant-service', 'im-service', 'push-service', 'update-service', 'file-service', 'license-service', 'demo-service']
|
for (entry in serviceVersions) {
|
||||||
: [params.SERVICE]
|
def svc = entry.key
|
||||||
|
|
||||||
for (svc in services) {
|
|
||||||
def latestImage = "${ACR_REGISTRY}/${ACR_NAMESPACE}/${svc}:latest"
|
def latestImage = "${ACR_REGISTRY}/${ACR_NAMESPACE}/${svc}:latest"
|
||||||
echo "Deploying ${svc}..."
|
echo "Deploying ${svc}..."
|
||||||
retry(2) {
|
retry(2) {
|
||||||
@ -104,23 +109,19 @@ pipeline {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update versions.json on server for deployed services
|
// 合并更新 versions.json(只改动本次构建涉及的服务,不覆盖其它服务或 web 条目)
|
||||||
def deployedServices = params.SERVICE == 'all'
|
|
||||||
? ['tenant-service', 'im-service', 'push-service', 'update-service', 'file-service', 'license-service', 'demo-service']
|
|
||||||
: [params.SERVICE]
|
|
||||||
def releasedAt = new Date().format("yyyy-MM-dd'T'HH:mm:ss'Z'", TimeZone.getTimeZone('UTC'))
|
def releasedAt = new Date().format("yyyy-MM-dd'T'HH:mm:ss'Z'", TimeZone.getTimeZone('UTC'))
|
||||||
def serviceEntries = deployedServices.collect { svc ->
|
def serviceEntries = serviceVersions.collect { svc, ver ->
|
||||||
"d.setdefault('services', {})['${svc}'] = {'version': '${env.SERVICE_VERSION}', 'changed': True}"
|
"d.setdefault('services', {})['${svc}'] = {'version': '${ver}'}"
|
||||||
}.join('\n')
|
}.join('\n')
|
||||||
def updateScript = """\
|
def updateScript = """\
|
||||||
import json, os
|
import json, os
|
||||||
path = '${env.VERSIONS_FILE}'
|
path = '${env.VERSIONS_FILE}'
|
||||||
d = json.load(open(path)) if os.path.exists(path) else {}
|
d = json.load(open(path)) if os.path.exists(path) else {}
|
||||||
${serviceEntries}
|
${serviceEntries}
|
||||||
d['serviceVersion'] = '${env.SERVICE_VERSION}'
|
|
||||||
d['releasedAt'] = '${releasedAt}'
|
d['releasedAt'] = '${releasedAt}'
|
||||||
json.dump(d, open(path, 'w'), indent=2, ensure_ascii=False)
|
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()
|
""".stripIndent()
|
||||||
writeFile file: 'update_versions.py', text: updateScript
|
writeFile file: 'update_versions.py', text: updateScript
|
||||||
bat """
|
bat """
|
||||||
@ -133,14 +134,17 @@ print('versions.json updated: ${params.SERVICE} = ${env.SERVICE_VERSION}')
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
stage('Commit Version') {
|
stage('Commit Versions') {
|
||||||
steps {
|
steps {
|
||||||
script {
|
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 """
|
bat """
|
||||||
git config user.email "jenkins@xuqm.com"
|
git config user.email "jenkins@xuqm.com"
|
||||||
git config user.name "Jenkins CI"
|
git config user.name "Jenkins CI"
|
||||||
git add ${env.VERSION_FILE}
|
git add ${versionFiles}
|
||||||
git diff --cached --quiet || git commit -m "ci: bump server to ${env.SERVICE_VERSION} [skip ci]"
|
git diff --cached --quiet || git commit -m "ci: bump versions [${summary}] [skip ci]"
|
||||||
git push origin HEAD:main
|
git push origin HEAD:main
|
||||||
"""
|
"""
|
||||||
}
|
}
|
||||||
@ -149,7 +153,13 @@ print('versions.json updated: ${params.SERVICE} = ${env.SERVICE_VERSION}')
|
|||||||
}
|
}
|
||||||
|
|
||||||
post {
|
post {
|
||||||
success { echo "✅ ${params.SERVICE} v${env.SERVICE_VERSION} 构建部署成功" }
|
success {
|
||||||
failure { echo "❌ 构建失败,请检查日志" }
|
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
|
||||||
正在加载...
在新工单中引用
屏蔽一个用户