pipeline { agent none options { disableConcurrentBuilds() timestamps() } parameters { string(name: 'AGENT_LABEL', defaultValue: 'built-in', description: '执行节点标签') string(name: 'GENARRATIVE_WORKSPACE_ROOT', defaultValue: '', description: '源码根目录,留空则使用当前 Jenkins 工作区') 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: '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: '可选,逗号分隔的表名白名单') string(name: 'CHUNK_SIZE', defaultValue: '524288', description: '迁移 JSON 分片大小,默认 512KiB,用于规避 SpacetimeDB HTTP 413') booleanParam(name: 'DRY_RUN', defaultValue: true, description: '仅校验导入,不写入数据') booleanParam(name: 'INCREMENTAL', defaultValue: true, description: '增量导入,跳过已存在或冲突的行') booleanParam(name: 'REPLACE_EXISTING', defaultValue: false, description: '覆盖本次文件内涉及的表,不可与 INCREMENTAL 同时启用') string(name: 'BOOTSTRAP_SECRET', defaultValue: '', description: '可选,授权临时导入 identity 的迁移引导密钥') string(name: 'TOKEN', defaultValue: '', description: '可选,SpacetimeDB 客户端连接 token;留空则自动创建临时 identity') string(name: 'NOTE', defaultValue: 'jenkins database import', description: '迁移授权备注') } stages { stage('校验参数') { agent { label 'built-in' } steps { script { if (!params.INPUT_FILE?.trim()) { error('INPUT_FILE 不能为空。') } 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" } } } stage('导入数据库') { agent { label "${params.AGENT_LABEL}" } steps { script { // 固定源码目录可复用 Jenkins Agent 上的脚本和依赖,未指定时回退到当前工作区。 env.WORKSPACE_ROOT = params.GENARRATIVE_WORKSPACE_ROOT?.trim() ? params.GENARRATIVE_WORKSPACE_ROOT.trim() : pwd() } dir("${env.WORKSPACE_ROOT}") { checkout scm sh """ bash -lc ' set -euo pipefail args=(scripts/spacetime-import-migration-json.mjs --in "${params.INPUT_FILE}") if [[ -n "${params.DATABASE}" ]]; then args+=(--database "${params.DATABASE}") fi if [[ -n "${params.SERVER}" ]]; then args+=(--server "${params.SERVER}") fi if [[ -n "${params.SERVER_URL}" ]]; then args+=(--server-url "${params.SERVER_URL}") fi 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}") fi if [[ -n "${params.CHUNK_SIZE}" ]]; then args+=(--chunk-size "${params.CHUNK_SIZE}") fi if [[ "${params.DRY_RUN}" == "true" ]]; then args+=(--dry-run) fi if [[ "${params.INCREMENTAL}" == "true" ]]; then args+=(--incremental) fi if [[ "${params.REPLACE_EXISTING}" == "true" ]]; then args+=(--replace-existing) fi if [[ -n "${params.BOOTSTRAP_SECRET}" ]]; then args+=(--bootstrap-secret "${params.BOOTSTRAP_SECRET}") fi if [[ -n "${params.TOKEN}" ]]; then args+=(--token "${params.TOKEN}") fi if [[ -n "${params.NOTE}" ]]; then args+=(--note "${params.NOTE}") fi # 导入默认 dry-run,并通过迁移 procedure 写入,保持权限校验和表级统计一致。 node "\${args[@]}" ' """ } } } } post { success { echo "数据库导入流水线完成,dry-run: ${params.DRY_RUN}" } } }