196 lines
10 KiB
Plaintext
196 lines
10 KiB
Plaintext
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: 'BUILD_VERSION', defaultValue: '', description: '发布版本号,留空则使用 Jenkins BUILD_NUMBER')
|
||
string(name: 'COMMIT_HASH', defaultValue: '', description: '可选,指定要构建的 Git commit hash;留空则使用 SCM 当前检出的提交')
|
||
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: 'MIGRATE_ON_CONFLICT', defaultValue: true, description: '普通发布遇到 SpacetimeDB schema 冲突时自动导出、清库发布并导入回灌')
|
||
string(name: 'MIGRATION_DIRECTORY', defaultValue: '', description: '自动迁移 JSON 输出目录,留空则使用部署目录内 database-migrations/<database>')
|
||
password(name: 'MIGRATION_EXPORT_TOKEN', defaultValue: '', description: '可选,旧库已授权迁移操作员 token,仅部署阶段用于 schema 冲突导出')
|
||
password(name: 'MIGRATION_IMPORT_TOKEN', defaultValue: '', description: '可选,新库已授权迁移操作员 token,仅部署阶段用于 schema 冲突导入')
|
||
booleanParam(name: 'RUN_NPM_CI', defaultValue: false, description: '构建前是否执行 npm ci')
|
||
string(name: 'DEPLOY_JOB_NAME', defaultValue: 'Genarrative-Deploy', description: '部署流水线作业名')
|
||
string(name: 'DEPLOY_DIRECTORY', defaultValue: '/var/lib/jenkins/deploy/Genarrative', description: '固定部署目录')
|
||
booleanParam(name: 'RUN_DEPLOY_HOOKS_WITH_SUDO', defaultValue: true, description: 'start.sh / stop.sh 是否通过 sudo -n 执行')
|
||
}
|
||
|
||
stages {
|
||
stage('构建发布包') {
|
||
agent {
|
||
label "${params.AGENT_LABEL}"
|
||
}
|
||
|
||
steps {
|
||
script {
|
||
// 统一在脚本块里计算版本号,避免 declarative environment 对表达式求值不一致。
|
||
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 commitHash = params.COMMIT_HASH?.trim()
|
||
if (commitHash && !(commitHash ==~ /^[0-9a-fA-F]{7,40}$/)) {
|
||
error('COMMIT_HASH 只能填写 7 到 40 位十六进制 Git commit hash,当前值: ' + commitHash)
|
||
}
|
||
env.COMMIT_HASH = commitHash ?: ''
|
||
def database = params.DATABASE?.trim()
|
||
if (!database) {
|
||
error('DATABASE 不能为空。')
|
||
}
|
||
if (!(database ==~ /^[a-z0-9]+(-[a-z0-9]+)*$/)) {
|
||
error('DATABASE 必须匹配 SpacetimeDB 数据库名规则 ^[a-z0-9]+(-[a-z0-9]+)*$,只能使用小写字母、数字,并用单个短横线分隔,当前值: ' + database)
|
||
}
|
||
env.EFFECTIVE_DATABASE = database
|
||
echo "SpacetimeDB 发布数据库: ${env.EFFECTIVE_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 不能为空。')
|
||
}
|
||
if (!(webPort ==~ /^[0-9]+$/)) {
|
||
error("WEB_PORT 必须是数字端口,当前值: ${webPort}")
|
||
}
|
||
if (webPort.length() > 5) {
|
||
error("WEB_PORT 必须在 1-65535 之间,当前值: ${webPort}")
|
||
}
|
||
def parsedWebPort = webPort.toInteger()
|
||
if (parsedWebPort < 1 || parsedWebPort > 65535) {
|
||
error("WEB_PORT 必须在 1-65535 之间,当前值: ${webPort}")
|
||
}
|
||
// 后续构建与下游部署都使用校验后的同一端口值,避免参数空格导致上下游不一致。
|
||
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
|
||
}
|
||
|
||
dir("${env.WORKSPACE_ROOT}") {
|
||
checkout scm
|
||
|
||
sh '''
|
||
bash -lc '
|
||
set -euo pipefail
|
||
requested_commit="${COMMIT_HASH:-}"
|
||
if [[ -n "${requested_commit}" ]]; then
|
||
# Jenkins 先按 SCM 配置完成 checkout;如指定 commit,再拉取远端引用并切到该提交构建。
|
||
git fetch --tags --prune origin "+refs/heads/*:refs/remotes/origin/*" || git fetch --all --tags --prune
|
||
if [[ "$(git rev-parse --is-shallow-repository 2>/dev/null || echo false)" == "true" ]]; then
|
||
git fetch --unshallow --tags || true
|
||
fi
|
||
git cat-file -e "${requested_commit}^{commit}"
|
||
resolved_commit="$(git rev-parse "${requested_commit}^{commit}")"
|
||
git checkout --detach "${resolved_commit}"
|
||
echo "[build-and-deploy] 使用指定 commit 构建: ${resolved_commit}"
|
||
else
|
||
resolved_commit="$(git rev-parse HEAD)"
|
||
echo "[build-and-deploy] 使用 SCM checkout commit 构建: ${resolved_commit}"
|
||
fi
|
||
# 构建前清理工作区内的 Git 变更和未跟踪文件,避免复用固定源码目录时受到上次构建残留影响。
|
||
# 这里不使用 -x,避免删除 node_modules 等忽略目录后与 RUN_NPM_CI=false 的配置冲突。
|
||
git reset --hard HEAD
|
||
git clean -fd
|
||
echo "${resolved_commit}" > "build-and-deploy-commit.txt"
|
||
rm -rf "build/${EFFECTIVE_BUILD_VERSION}"
|
||
'
|
||
'''
|
||
|
||
script {
|
||
env.EFFECTIVE_COMMIT_HASH = readFile('build-and-deploy-commit.txt').trim()
|
||
echo "构建 commit: ${env.EFFECTIVE_COMMIT_HASH}"
|
||
}
|
||
|
||
script {
|
||
// 是否重装依赖交给流水线参数决定,避免每次构建都重复执行 npm ci。
|
||
if (params.RUN_NPM_CI) {
|
||
sh 'bash -lc "npm ci"'
|
||
}
|
||
}
|
||
|
||
sh """
|
||
bash -lc '
|
||
set -euo pipefail
|
||
# 构建并部署流水线显式透传本地测试参数,避免发布包回退到默认库名或端口。
|
||
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}"
|
||
'
|
||
"""
|
||
|
||
archiveArtifacts artifacts: "build/${env.EFFECTIVE_BUILD_VERSION}/**", fingerprint: true
|
||
}
|
||
}
|
||
}
|
||
|
||
stage('触发部署') {
|
||
steps {
|
||
// 本阶段没有声明 agent,确保触发下游前已经释放构建节点,避免单执行器死锁。
|
||
build job: params.DEPLOY_JOB_NAME,
|
||
wait: true,
|
||
propagate: true,
|
||
parameters: [
|
||
string(name: 'SOURCE_NODE_NAME', value: env.SOURCE_NODE_NAME),
|
||
string(name: 'SOURCE_WORKSPACE_ROOT', value: env.WORKSPACE_ROOT),
|
||
string(name: 'BUILD_VERSION', value: env.EFFECTIVE_BUILD_VERSION),
|
||
string(name: 'DEPLOY_DIRECTORY', value: params.DEPLOY_DIRECTORY),
|
||
string(name: 'WEB_PORT', value: env.EFFECTIVE_WEB_PORT),
|
||
booleanParam(name: 'CLEAR_DATABASE', value: params.CLEAR_DATABASE),
|
||
booleanParam(name: 'MIGRATE_ON_CONFLICT', value: params.MIGRATE_ON_CONFLICT),
|
||
string(name: 'MIGRATION_DIRECTORY', value: params.MIGRATION_DIRECTORY),
|
||
password(name: 'MIGRATION_EXPORT_TOKEN', value: params.MIGRATION_EXPORT_TOKEN),
|
||
password(name: 'MIGRATION_IMPORT_TOKEN', value: params.MIGRATION_IMPORT_TOKEN),
|
||
booleanParam(name: 'RUN_DEPLOY_HOOKS_WITH_SUDO', value: params.RUN_DEPLOY_HOOKS_WITH_SUDO),
|
||
string(name: 'EXPECTED_UPSTREAM_JOB', value: env.JOB_NAME),
|
||
]
|
||
}
|
||
}
|
||
}
|
||
|
||
post {
|
||
success {
|
||
echo "构建并部署完成,版本号: ${env.EFFECTIVE_BUILD_VERSION}"
|
||
}
|
||
}
|
||
}
|