b49bf9c984
将旧布局上限统一提升至2MiB并接入revision CAS保存 新增画布图层、生成对话框和迁移状态结构化存储 新增migration operator保护的回填、激活和回滚脚本 新增历史图片映射及音频资源恢复的定向dry-run/apply流程 补齐前端保存队列、OpenAPI、Bindings、发布归档与回归测试
193 lines
12 KiB
Plaintext
193 lines
12 KiB
Plaintext
pipeline {
|
||
agent none
|
||
|
||
options {
|
||
disableConcurrentBuilds()
|
||
skipDefaultCheckout(true)
|
||
buildDiscarder(logRotator(numToKeepStr: '20', artifactNumToKeepStr: '20'))
|
||
}
|
||
|
||
parameters {
|
||
choice(name: 'DEPLOY_TARGET', choices: ['development', 'release'], description: '逻辑部署目标;development 使用当前 Linux 开发/构建/开发部署 agent')
|
||
booleanParam(name: 'CONFIRM_RELEASE_DEPLOY_AGENT', defaultValue: false, description: '确认 release 目标已有独立 release 部署 agent;当前 Linux 开发/构建/开发部署 agent 不可冒充 release 部署机')
|
||
string(name: 'SOURCE_BRANCH', defaultValue: 'master', description: '上游构建源码分支')
|
||
string(name: 'COMMIT_HASH', defaultValue: '', description: '上游构建源码 commit')
|
||
string(name: 'NOTIFICATION_EMAILS', defaultValue: '', description: '本次运行追加通知邮箱;会与 Jenkins Secret Text 凭据 genarrative-notification-emails 合并发送')
|
||
string(name: 'MIGRATION_BOOTSTRAP_SECRET_CREDENTIAL_ID', defaultValue: 'genarrative-spacetime-bootstrap-secret-dev-file', description: '必填:与目标 wasm 一致的 Jenkins Secret File 凭据 ID;仅在发布时受保护挂载')
|
||
string(name: 'BUILD_VERSION', defaultValue: '', description: '待发布版本号')
|
||
string(name: 'BUILD_JOB_NAME', defaultValue: 'Genarrative-Stdb-Module-Build', description: 'Stdb module 构建流水线作业名')
|
||
string(name: 'BUILD_NUMBER_TO_DEPLOY', defaultValue: '', description: '要复制归档产物的上游构建号')
|
||
string(name: 'DATABASE', defaultValue: 'genarrative-prod', description: '生产 SpacetimeDB database')
|
||
string(name: 'SPACETIME_SERVER', defaultValue: 'local', description: 'SpacetimeDB server alias')
|
||
string(name: 'SPACETIME_SERVER_URL', defaultValue: 'http://127.0.0.1:3101', description: '显式 SpacetimeDB server URL,填写后优先于 SPACETIME_SERVER')
|
||
string(name: 'SPACETIME_ROOT_DIR', defaultValue: '/stdb', description: 'spacetime CLI root-dir;需与自托管 spacetimedb.service 一致')
|
||
string(name: 'SPACETIME_RUN_AS_USER', defaultValue: 'spacetimedb', description: '执行 spacetime publish 的本机用户,默认使用自托管服务用户')
|
||
string(name: 'API_ENV_FILE', defaultValue: '/etc/genarrative/api-server.env', description: '需补齐 runtime bootstrap secret FILE 的 api-server 环境文件')
|
||
string(name: 'WORKER_ENV_FILE', defaultValue: '/etc/genarrative/external-generation-worker.env', description: '需补齐 runtime bootstrap secret FILE 的 worker 环境文件;文件不存在时跳过')
|
||
booleanParam(name: 'KEEP_MAINTENANCE_MODE', defaultValue: false, description: '发布 module 后保持维护模式并停止旧 API/controller/worker,等待受控维护和后续 API deploy')
|
||
choice(name: 'DATABASE_BACKUP_MODE', choices: ['async', 'sync', 'skip'], description: '数据库备份策略:async 在 publish 前生成本地冷备份、后台上传 OSS;sync 在 publish 前等待上传完成且失败阻断;skip 跳过')
|
||
}
|
||
|
||
stages {
|
||
stage('Prepare') {
|
||
agent {
|
||
label 'linux && genarrative-build'
|
||
}
|
||
steps {
|
||
script {
|
||
if (params.DEPLOY_TARGET == 'release' && !params.CONFIRM_RELEASE_DEPLOY_AGENT) {
|
||
error('release 部署需要先配置独立 release 部署 agent,并勾选 CONFIRM_RELEASE_DEPLOY_AGENT。当前 Linux 开发/构建/开发部署 agent 不能执行 release 部署。')
|
||
}
|
||
if (!params.BUILD_VERSION?.trim()) {
|
||
error('BUILD_VERSION 不能为空。')
|
||
}
|
||
if (params.BUILD_VERSION != params.BUILD_VERSION.trim() || !(params.BUILD_VERSION ==~ /^[A-Za-z0-9][A-Za-z0-9._-]*$/)) {
|
||
error("BUILD_VERSION 只能包含字母、数字、点、下划线和短横线,且必须以字母或数字开头: ${params.BUILD_VERSION}")
|
||
}
|
||
if (!params.BUILD_JOB_NAME?.trim()) {
|
||
error('BUILD_JOB_NAME 不能为空。')
|
||
}
|
||
if (!params.BUILD_NUMBER_TO_DEPLOY?.trim()) {
|
||
error('BUILD_NUMBER_TO_DEPLOY 不能为空。')
|
||
}
|
||
if (!params.MIGRATION_BOOTSTRAP_SECRET_CREDENTIAL_ID?.trim()) {
|
||
error('MIGRATION_BOOTSTRAP_SECRET_CREDENTIAL_ID 必须引用受保护的 Jenkins Secret File 凭据。')
|
||
}
|
||
if (!params.DATABASE?.trim()) {
|
||
error('DATABASE 不能为空。')
|
||
}
|
||
if (!params.SPACETIME_SERVER?.trim() && !params.SPACETIME_SERVER_URL?.trim()) {
|
||
error('SPACETIME_SERVER 与 SPACETIME_SERVER_URL 不能同时为空。')
|
||
}
|
||
def spacetimeRootDir = params.SPACETIME_ROOT_DIR?.trim() ? params.SPACETIME_ROOT_DIR.trim() : '/stdb'
|
||
if (!(spacetimeRootDir ==~ /^\/(?!.*\.\.)[A-Za-z0-9._\/-]+$/)) {
|
||
error("SPACETIME_ROOT_DIR 必须是 Linux 绝对路径且不能包含 ..: ${spacetimeRootDir}")
|
||
}
|
||
def spacetimeRunAsUser = params.SPACETIME_RUN_AS_USER?.trim()
|
||
if (spacetimeRunAsUser && !(spacetimeRunAsUser ==~ /^[A-Za-z_][A-Za-z0-9_-]*$/)) {
|
||
error("SPACETIME_RUN_AS_USER 只能是本机用户名: ${spacetimeRunAsUser}")
|
||
}
|
||
[API_ENV_FILE: params.API_ENV_FILE, WORKER_ENV_FILE: params.WORKER_ENV_FILE].each { name, value ->
|
||
def envFile = value?.trim()
|
||
if (!envFile || envFile == '/' || !(envFile ==~ /^\/(?!.*\.\.)[A-Za-z0-9._\/-]+$/)) {
|
||
error("${name} 必须是 Linux 绝对路径且不能包含 ..: ${envFile}")
|
||
}
|
||
}
|
||
def spacetimeServerUrl = params.SPACETIME_SERVER_URL?.trim()
|
||
if (spacetimeServerUrl && !(spacetimeServerUrl ==~ /^https?:\/\/[A-Za-z0-9._:-]+$/)) {
|
||
error("SPACETIME_SERVER_URL 只能是 http(s) URL,且不能包含路径或 shell 特殊字符: ${spacetimeServerUrl}")
|
||
}
|
||
def spacetimeServer = params.SPACETIME_SERVER?.trim()
|
||
if (!spacetimeServerUrl && spacetimeServer && !(spacetimeServer ==~ /^[A-Za-z0-9._:-]+$/)) {
|
||
error("SPACETIME_SERVER 只能包含字母、数字、点、下划线、冒号和短横线: ${spacetimeServer}")
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
stage('Fetch Artifact') {
|
||
agent {
|
||
label "${params.DEPLOY_TARGET == 'development' ? 'linux && genarrative-dev-deploy' : 'linux && genarrative-release-deploy'}"
|
||
}
|
||
steps {
|
||
copyArtifacts(
|
||
projectName: params.BUILD_JOB_NAME,
|
||
selector: specific(params.BUILD_NUMBER_TO_DEPLOY),
|
||
filter: "build/${params.BUILD_VERSION}/spacetime_module.wasm,build/${params.BUILD_VERSION}/spacetime_module.wasm.sha256,build/${params.BUILD_VERSION}/release-manifest.json,scripts/deploy/production-stdb-publish.sh,scripts/deploy/production-runtime-writer-identity-rotate.mjs,scripts/deploy/maintenance-on.sh,scripts/deploy/maintenance-off.sh,scripts/spacetime-migration-common.mjs,scripts/spacetime-maintain-external-generation-jobs.mjs,scripts/spacetime-migrate-editor-canvas-layout.mjs,scripts/spacetime-repair-editor-canvas-resources.mjs,scripts/database-backup-to-oss.mjs",
|
||
target: '.',
|
||
fingerprintArtifacts: true
|
||
)
|
||
}
|
||
}
|
||
|
||
stage('Publish Stdb Module') {
|
||
agent {
|
||
label "${params.DEPLOY_TARGET == 'development' ? 'linux && genarrative-dev-deploy' : 'linux && genarrative-release-deploy'}"
|
||
}
|
||
steps {
|
||
script {
|
||
def keepMaintenanceArg = params.KEEP_MAINTENANCE_MODE ? '--keep-maintenance-mode' : ''
|
||
def backupMode = params.DATABASE_BACKUP_MODE?.trim() ? params.DATABASE_BACKUP_MODE.trim() : 'async'
|
||
if (!(backupMode in ['async', 'sync', 'skip'])) {
|
||
error("DATABASE_BACKUP_MODE 只能是 async、sync 或 skip: ${backupMode}")
|
||
}
|
||
def publishScriptPath = 'scripts/deploy/production-stdb-publish.sh'
|
||
def publishScriptText = readFile(file: publishScriptPath, encoding: 'UTF-8')
|
||
def publishScriptSupportsBackupMode = publishScriptText.contains('--backup-mode')
|
||
def backupArg = ''
|
||
if (publishScriptSupportsBackupMode) {
|
||
backupArg = "--backup-mode \"${backupMode}\""
|
||
} else if (backupMode == 'sync') {
|
||
error('当前工作区的 scripts/deploy/production-stdb-publish.sh 还不支持 --backup-mode,无法保证 sync 语义。请先更新工作区脚本后再运行。')
|
||
} else {
|
||
echo "[Jenkins] 当前工作区脚本还不支持 --backup-mode,async/skip 自动降级为 --skip-backup,避免参数不兼容导致发布失败。"
|
||
backupArg = '--skip-backup'
|
||
}
|
||
def rootArg = "--root-dir \"${params.SPACETIME_ROOT_DIR?.trim() ? params.SPACETIME_ROOT_DIR.trim() : '/stdb'}\""
|
||
def runAsArg = params.SPACETIME_RUN_AS_USER?.trim()
|
||
? "--run-as-user \"${params.SPACETIME_RUN_AS_USER.trim()}\""
|
||
: ''
|
||
def serverArg = params.SPACETIME_SERVER_URL?.trim()
|
||
? "--server-url \"${params.SPACETIME_SERVER_URL.trim()}\""
|
||
: "--server \"${params.SPACETIME_SERVER}\""
|
||
withCredentials([
|
||
file(credentialsId: params.MIGRATION_BOOTSTRAP_SECRET_CREDENTIAL_ID.trim(), variable: 'MIGRATION_BOOTSTRAP_SECRET_FILE')
|
||
]) {
|
||
sh """
|
||
bash -lc '
|
||
set -euo pipefail
|
||
chmod +x scripts/deploy/production-stdb-publish.sh scripts/deploy/production-runtime-writer-identity-rotate.mjs scripts/deploy/maintenance-on.sh scripts/deploy/maintenance-off.sh
|
||
scripts/deploy/production-stdb-publish.sh \\
|
||
--source-dir "build/${params.BUILD_VERSION}" \\
|
||
--database "${params.DATABASE}" \\
|
||
${rootArg} \\
|
||
${runAsArg} \\
|
||
${serverArg} \\
|
||
--migration-bootstrap-secret-file "\${MIGRATION_BOOTSTRAP_SECRET_FILE:?MIGRATION_BOOTSTRAP_SECRET_FILE 不能为空}" \\
|
||
--api-env-file "${params.API_ENV_FILE}" \\
|
||
--worker-env-file "${params.WORKER_ENV_FILE}" \\
|
||
${keepMaintenanceArg} \\
|
||
${backupArg}
|
||
'
|
||
"""
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
post {
|
||
always {
|
||
script {
|
||
def notificationParameters = [
|
||
string(name: 'SOURCE_JOB_NAME', value: env.JOB_NAME),
|
||
string(name: 'SOURCE_BUILD_NUMBER', value: env.BUILD_NUMBER),
|
||
string(name: 'SOURCE_BUILD_URL', value: env.BUILD_URL ?: ''),
|
||
string(name: 'SOURCE_RESULT', value: currentBuild.currentResult ?: 'UNKNOWN'),
|
||
string(name: 'SOURCE_BRANCH', value: params.SOURCE_BRANCH ?: ''),
|
||
string(name: 'SOURCE_COMMIT', value: env.SOURCE_COMMIT ?: (params.COMMIT_HASH ?: '')),
|
||
string(name: 'BUILD_VERSION', value: env.EFFECTIVE_BUILD_VERSION ?: (params.BUILD_VERSION ?: '')),
|
||
string(name: 'DEPLOY_TARGET', value: params.DEPLOY_TARGET ?: ''),
|
||
string(name: 'DATABASE', value: params.DATABASE ?: ''),
|
||
string(name: 'SUMMARY', value: 'Stdb module 发布流水线结束'),
|
||
]
|
||
def notificationRecipients = params.NOTIFICATION_EMAILS?.trim()
|
||
if (notificationRecipients) {
|
||
notificationParameters.add(string(name: 'EMAIL_RECIPIENTS', value: notificationRecipients))
|
||
}
|
||
try {
|
||
build job: 'Genarrative-Notify-Email',
|
||
wait: false,
|
||
propagate: false,
|
||
parameters: notificationParameters
|
||
} catch (error) {
|
||
echo "邮件通知触发失败: ${error.message}"
|
||
}
|
||
}
|
||
}
|
||
success {
|
||
echo "Stdb module 发布完成: version=${params.BUILD_VERSION}, database=${params.DATABASE}"
|
||
}
|
||
}
|
||
}
|