Files
Genarrative/jenkins/Jenkinsfile.build-and-deploy

203 lines
10 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
pipeline {
agent none
options {
disableConcurrentBuilds()
timestamps()
}
environment {
GENARRATIVE_TOOLS_PATH = "/var/lib/jenkins/.nvm/versions/node/v22.22.2/bin:/var/lib/jenkins/.cargo/bin:/var/lib/jenkins/.local/bin:/var/lib/jenkins/bin"
PATH = "${GENARRATIVE_TOOLS_PATH}:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin"
}
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
# 每条流水线开头先强制回到 SCM 检出的干净提交,避免固定源码目录残留改动影响本次执行。
git reset --hard HEAD
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}"
}
}
}