From 1dee5f72d7498437d25ffddade93d90ae03da57d Mon Sep 17 00:00:00 2001 From: Codex Date: Wed, 29 Apr 2026 12:03:22 +0800 Subject: [PATCH] Add Jenkins local deployment test parameters --- ...DATABASE_MIGRATION_PIPELINES_2026-04-29.md | 33 +++++++++--- jenkins/Jenkinsfile.build-and-deploy | 50 ++++++++++++++++++- jenkins/Jenkinsfile.database-export | 12 +++-- jenkins/Jenkinsfile.database-import | 12 +++-- 4 files changed, 93 insertions(+), 14 deletions(-) diff --git a/docs/technical/JENKINS_SPACETIMEDB_DATABASE_MIGRATION_PIPELINES_2026-04-29.md b/docs/technical/JENKINS_SPACETIMEDB_DATABASE_MIGRATION_PIPELINES_2026-04-29.md index 05d33663..d28faee3 100644 --- a/docs/technical/JENKINS_SPACETIMEDB_DATABASE_MIGRATION_PIPELINES_2026-04-29.md +++ b/docs/technical/JENKINS_SPACETIMEDB_DATABASE_MIGRATION_PIPELINES_2026-04-29.md @@ -41,10 +41,11 @@ Genarrative-Database-Export 1. `DATABASE`:目标 SpacetimeDB 数据库名;留空时读取仓库环境变量。 2. `SERVER`:SpacetimeDB server 别名,默认 `maincloud`。 3. `SERVER_URL`:显式服务地址;填写后优先于 `SERVER`。 -4. `ROOT_DIR`:可选,透传给 `spacetime --root-dir`。 -5. `INCLUDE_TABLES`:可选,逗号分隔的表名白名单。 -6. `OUTPUT_DIRECTORY`:导出文件目录,默认 `database-exports`。 -7. `EXPORT_NAME`:导出文件名;留空时使用 `spacetime-migration-.json`。 +4. `DEPLOY_DIRECTORY`:固定部署目录,默认 `/var/lib/jenkins/deploy/Genarrative`。 +5. `ROOT_DIR`:可选,透传给 `spacetime --root-dir`;为空时使用 `/.spacetimedb`。 +6. `INCLUDE_TABLES`:可选,逗号分隔的表名白名单。 +7. `OUTPUT_DIRECTORY`:导出文件目录,默认 `database-exports`。 +8. `EXPORT_NAME`:导出文件名;留空时使用 `spacetime-migration-.json`。 导出成功后,Jenkins 归档: @@ -69,7 +70,7 @@ Genarrative-Database-Import 关键参数: 1. `INPUT_FILE`:必填,迁移 JSON 文件路径。 -2. `DATABASE`、`SERVER`、`SERVER_URL`、`ROOT_DIR`:与导出流水线一致。 +2. `DATABASE`、`SERVER`、`SERVER_URL`、`DEPLOY_DIRECTORY`、`ROOT_DIR`:与导出流水线一致。 3. `INCLUDE_TABLES`:可选,只导入指定表。 4. `DRY_RUN`:默认 `true`,只校验不写入。 5. `INCREMENTAL`:默认 `true`,跳过已存在或冲突的行。 @@ -85,7 +86,27 @@ Genarrative-Database-Import 3. Jenkinsfile 不打印 token;生产环境应通过 Jenkins 凭据或目标机器环境变量传入敏感值。 4. 如果不传 `TOKEN`,导入脚本会创建临时 Web API identity,并调用迁移授权/撤销 procedure 收敛权限窗口。 -## 5. 文件清单 +## 5. 本地部署测试参数 + +`Genarrative-Build-And-Deploy` 增加以下本地发布包参数,便于在 Jenkins 中测试本地 SpacetimeDB,不依赖 Maincloud: + +1. `DATABASE`:发布包默认数据库名,默认 `genarrative_pipeline_local_test`。 +2. `API_PORT`:发布包内 api-server 端口,默认 `8082`。 +3. `WEB_PORT`:发布包内静态网站端口,默认 `25001`。 +4. `SPACETIME_PORT`:发布包内本地 SpacetimeDB 端口,默认 `3101`。 +5. `DEPLOY_DIRECTORY`:固定部署目录,继续透传给 `Genarrative-Deploy`。 + +数据库导入导出流水线在本地测试时应显式填写: + +```text +DATABASE=genarrative_pipeline_local_test +SERVER_URL=http://127.0.0.1:3101 +DEPLOY_DIRECTORY=/var/lib/jenkins/deploy/Genarrative +``` + +这样脚本会自动使用 `/var/lib/jenkins/deploy/Genarrative/.spacetimedb` 作为 `spacetime --root-dir`,避免回退到 Jenkins 用户全局 CLI 登录态,也避免误连 Maincloud。 + +## 6. 文件清单 ```text jenkins/Jenkinsfile.database-export diff --git a/jenkins/Jenkinsfile.build-and-deploy b/jenkins/Jenkinsfile.build-and-deploy index 62554c07..3338e8f1 100644 --- a/jenkins/Jenkinsfile.build-and-deploy +++ b/jenkins/Jenkinsfile.build-and-deploy @@ -10,7 +10,10 @@ pipeline { string(name: 'AGENT_LABEL', defaultValue: 'built-in', description: '构建节点标签') string(name: 'GENARRATIVE_WORKSPACE_ROOT', defaultValue: '', description: '源码根目录,留空则使用当前 Jenkins 工作区') string(name: 'BUILD_VERSION', defaultValue: '', description: '发布版本号,留空则使用 Jenkins BUILD_NUMBER') + string(name: 'DATABASE', defaultValue: 'genarrative_pipeline_local_test', description: '发布包默认 SpacetimeDB database') + string(name: 'API_PORT', defaultValue: '8082', description: '发布包内 api-server 端口') string(name: 'WEB_PORT', defaultValue: '25001', description: '发布包内静态网站端口,默认 25001') + string(name: 'SPACETIME_PORT', defaultValue: '3101', description: '发布包内本地 SpacetimeDB 端口') booleanParam(name: 'CLEAR_DATABASE', defaultValue: false, description: '部署时是否清空 SpacetimeDB 数据后再发布 wasm') booleanParam(name: 'RUN_NPM_CI', defaultValue: false, description: '构建前是否执行 npm ci') string(name: 'DEPLOY_JOB_NAME', defaultValue: 'Genarrative-Deploy', description: '部署流水线作业名') @@ -30,6 +33,29 @@ pipeline { env.EFFECTIVE_BUILD_VERSION = params.BUILD_VERSION?.trim() ? params.BUILD_VERSION.trim() : env.BUILD_NUMBER // 允许 Jenkins Job 直接指定固定源码目录,未指定时回退到当前工作区。 env.WORKSPACE_ROOT = params.GENARRATIVE_WORKSPACE_ROOT?.trim() ? params.GENARRATIVE_WORKSPACE_ROOT.trim() : pwd() + def database = params.DATABASE?.trim() + if (!database) { + error('DATABASE 不能为空。') + } + if (!(database ==~ /^[0-9A-Za-z._-]+$/)) { + error("DATABASE 只能包含数字、字母、点、下划线和短横线,当前值: ${database}") + } + env.EFFECTIVE_DATABASE = database + def apiPort = params.API_PORT?.trim() + if (!apiPort) { + error('API_PORT 不能为空。') + } + if (!(apiPort ==~ /^[0-9]+$/)) { + error("API_PORT 必须是数字端口,当前值: ${apiPort}") + } + if (apiPort.length() > 5) { + error("API_PORT 必须在 1-65535 之间,当前值: ${apiPort}") + } + def parsedApiPort = apiPort.toInteger() + if (parsedApiPort < 1 || parsedApiPort > 65535) { + error("API_PORT 必须在 1-65535 之间,当前值: ${apiPort}") + } + env.EFFECTIVE_API_PORT = apiPort def webPort = params.WEB_PORT?.trim() if (!webPort) { error('WEB_PORT 不能为空。') @@ -46,6 +72,21 @@ pipeline { } // 后续构建与下游部署都使用校验后的同一端口值,避免参数空格导致上下游不一致。 env.EFFECTIVE_WEB_PORT = webPort + def spacetimePort = params.SPACETIME_PORT?.trim() + if (!spacetimePort) { + error('SPACETIME_PORT 不能为空。') + } + if (!(spacetimePort ==~ /^[0-9]+$/)) { + error("SPACETIME_PORT 必须是数字端口,当前值: ${spacetimePort}") + } + if (spacetimePort.length() > 5) { + error("SPACETIME_PORT 必须在 1-65535 之间,当前值: ${spacetimePort}") + } + def parsedSpacetimePort = spacetimePort.toInteger() + if (parsedSpacetimePort < 1 || parsedSpacetimePort > 65535) { + error("SPACETIME_PORT 必须在 1-65535 之间,当前值: ${spacetimePort}") + } + env.EFFECTIVE_SPACETIME_PORT = spacetimePort // 记录当前构建节点名,部署阶段必须回到同一节点读取本地 build 目录。 env.SOURCE_NODE_NAME = env.NODE_NAME } @@ -73,8 +114,13 @@ pipeline { sh """ bash -lc ' set -euo pipefail - # 构建并部署流水线显式透传 Web 端口,确保部署包默认监听 25001,同时允许 Jenkins 参数覆盖。 - npm run deploy:rust:remote -- --skip-upload --name "${env.EFFECTIVE_BUILD_VERSION}" --web-port "${env.EFFECTIVE_WEB_PORT}" + # 构建并部署流水线显式透传本地测试参数,避免发布包回退到默认库名或端口。 + npm run deploy:rust:remote -- --skip-upload \ + --name "${env.EFFECTIVE_BUILD_VERSION}" \ + --database "${env.EFFECTIVE_DATABASE}" \ + --api-port "${env.EFFECTIVE_API_PORT}" \ + --web-port "${env.EFFECTIVE_WEB_PORT}" \ + --spacetime-port "${env.EFFECTIVE_SPACETIME_PORT}" test -d "build/${env.EFFECTIVE_BUILD_VERSION}" ' """ diff --git a/jenkins/Jenkinsfile.database-export b/jenkins/Jenkinsfile.database-export index ee3ebc48..6f5830e3 100644 --- a/jenkins/Jenkinsfile.database-export +++ b/jenkins/Jenkinsfile.database-export @@ -12,7 +12,8 @@ pipeline { string(name: 'DATABASE', defaultValue: '', description: 'SpacetimeDB 数据库名,留空则读取环境变量') string(name: 'SERVER', defaultValue: 'maincloud', description: 'SpacetimeDB server 别名,例如 maincloud/local/dev') string(name: 'SERVER_URL', defaultValue: '', description: 'SpacetimeDB server URL,填写后优先于 SERVER') - string(name: 'ROOT_DIR', defaultValue: '', description: 'spacetime CLI root-dir,可选') + string(name: 'DEPLOY_DIRECTORY', defaultValue: '/var/lib/jenkins/deploy/Genarrative', description: '固定部署目录,ROOT_DIR 为空时使用其 .spacetimedb') + string(name: 'ROOT_DIR', defaultValue: '', description: 'spacetime CLI root-dir,可选,优先于 DEPLOY_DIRECTORY') string(name: 'INCLUDE_TABLES', defaultValue: '', description: '可选,逗号分隔的表名白名单') string(name: 'OUTPUT_DIRECTORY', defaultValue: 'database-exports', description: '导出文件目录,相对源码根目录或绝对路径') string(name: 'EXPORT_NAME', defaultValue: '', description: '导出文件名,留空则自动使用构建号') @@ -28,6 +29,11 @@ pipeline { script { // 允许 Jenkins Job 指定固定源码目录;未指定时使用当前工作区,方便临时手工执行。 env.WORKSPACE_ROOT = params.GENARRATIVE_WORKSPACE_ROOT?.trim() ? params.GENARRATIVE_WORKSPACE_ROOT.trim() : pwd() + def deployDirectory = params.DEPLOY_DIRECTORY?.trim() + if (!deployDirectory) { + error('DEPLOY_DIRECTORY 不能为空。') + } + env.EFFECTIVE_ROOT_DIR = params.ROOT_DIR?.trim() ? params.ROOT_DIR.trim() : "${deployDirectory}/.spacetimedb" def exportName = params.EXPORT_NAME?.trim() if (!exportName) { exportName = "spacetime-migration-${env.BUILD_NUMBER}.json" @@ -60,8 +66,8 @@ pipeline { if [[ -n "${params.SERVER_URL}" ]]; then args+=(--server-url "${params.SERVER_URL}") fi - if [[ -n "${params.ROOT_DIR}" ]]; then - args+=(--root-dir "${params.ROOT_DIR}") + if [[ -n "${env.EFFECTIVE_ROOT_DIR}" ]]; then + args+=(--root-dir "${env.EFFECTIVE_ROOT_DIR}") fi if [[ -n "${params.INCLUDE_TABLES}" ]]; then args+=(--include "${params.INCLUDE_TABLES}") diff --git a/jenkins/Jenkinsfile.database-import b/jenkins/Jenkinsfile.database-import index 80347d88..df93bb96 100644 --- a/jenkins/Jenkinsfile.database-import +++ b/jenkins/Jenkinsfile.database-import @@ -12,7 +12,8 @@ pipeline { string(name: 'DATABASE', defaultValue: '', description: 'SpacetimeDB 数据库名,留空则读取环境变量') string(name: 'SERVER', defaultValue: 'maincloud', description: 'SpacetimeDB server 别名,例如 maincloud/local/dev') string(name: 'SERVER_URL', defaultValue: '', description: 'SpacetimeDB server URL,填写后优先于 SERVER') - string(name: 'ROOT_DIR', defaultValue: '', description: 'spacetime CLI root-dir,可选') + string(name: 'DEPLOY_DIRECTORY', defaultValue: '/var/lib/jenkins/deploy/Genarrative', description: '固定部署目录,ROOT_DIR 为空时使用其 .spacetimedb') + string(name: 'ROOT_DIR', defaultValue: '', description: 'spacetime CLI root-dir,可选,优先于 DEPLOY_DIRECTORY') string(name: 'INPUT_FILE', defaultValue: '', description: '必填,迁移 JSON 文件路径,相对源码根目录或绝对路径') string(name: 'INCLUDE_TABLES', defaultValue: '', description: '可选,逗号分隔的表名白名单') booleanParam(name: 'DRY_RUN', defaultValue: true, description: '仅校验导入,不写入数据') @@ -37,6 +38,11 @@ pipeline { if (params.INCREMENTAL && params.REPLACE_EXISTING) { error('INCREMENTAL 不能和 REPLACE_EXISTING 同时启用。') } + def deployDirectory = params.DEPLOY_DIRECTORY?.trim() + if (!deployDirectory) { + error('DEPLOY_DIRECTORY 不能为空。') + } + env.EFFECTIVE_ROOT_DIR = params.ROOT_DIR?.trim() ? params.ROOT_DIR.trim() : "${deployDirectory}/.spacetimedb" } } } @@ -68,8 +74,8 @@ pipeline { if [[ -n "${params.SERVER_URL}" ]]; then args+=(--server-url "${params.SERVER_URL}") fi - if [[ -n "${params.ROOT_DIR}" ]]; then - args+=(--root-dir "${params.ROOT_DIR}") + if [[ -n "${env.EFFECTIVE_ROOT_DIR}" ]]; then + args+=(--root-dir "${env.EFFECTIVE_ROOT_DIR}") fi if [[ -n "${params.INCLUDE_TABLES}" ]]; then args+=(--include "${params.INCLUDE_TABLES}")