161 lines
7.1 KiB
Plaintext
161 lines
7.1 KiB
Plaintext
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: 'SOURCE_NODE_NAME', defaultValue: '', description: '上游构建节点名')
|
||
string(name: 'SOURCE_WORKSPACE_ROOT', defaultValue: '', description: '上游源码根目录')
|
||
string(name: 'BUILD_VERSION', defaultValue: '', description: '待部署版本号')
|
||
string(name: 'DEPLOY_DIRECTORY', defaultValue: '/var/lib/jenkins/deploy/Genarrative', description: '固定部署目录')
|
||
string(name: 'WEB_PORT', defaultValue: '25001', description: '静态网站监听端口,默认 25001,上游构建并部署流水线会透传同名参数')
|
||
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_DEPLOY_HOOKS_WITH_SUDO', defaultValue: true, description: 'start.sh / stop.sh 是否通过 sudo -n 执行')
|
||
string(name: 'EXPECTED_UPSTREAM_JOB', defaultValue: '', description: '允许触发本作业的上游作业名')
|
||
}
|
||
|
||
stages {
|
||
stage('校验触发来源') {
|
||
agent {
|
||
label 'built-in'
|
||
}
|
||
|
||
steps {
|
||
script {
|
||
// 部署流水线允许手动启动;如存在上游触发原因,则继续执行上游作业名门禁。
|
||
// Pipeline 的 build 步骤通常会把下游触发原因记录成 BuildUpstreamCause,
|
||
// 直接只查经典 UpstreamCause 会把真实的上游触发误判成“人工执行”。
|
||
def pipelineUpstreamCauses = currentBuild.getBuildCauses('org.jenkinsci.plugins.workflow.support.steps.build.BuildUpstreamCause')
|
||
def classicUpstreamCauses = currentBuild.getBuildCauses('hudson.model.Cause$UpstreamCause')
|
||
def upstreamCause = null
|
||
|
||
if (pipelineUpstreamCauses && !pipelineUpstreamCauses.isEmpty()) {
|
||
upstreamCause = pipelineUpstreamCauses[0]
|
||
} else if (classicUpstreamCauses && !classicUpstreamCauses.isEmpty()) {
|
||
upstreamCause = classicUpstreamCauses[0]
|
||
}
|
||
|
||
def actualUpstreamJob = upstreamCause?.upstreamProject ?: ''
|
||
def expectedUpstreamJob = params.EXPECTED_UPSTREAM_JOB?.trim()
|
||
def allowedUpstreamJob = env.GENARRATIVE_ALLOWED_UPSTREAM_JOB?.trim()
|
||
|
||
if (!params.BUILD_VERSION?.trim()) {
|
||
error('BUILD_VERSION 不能为空。')
|
||
}
|
||
|
||
if (!params.SOURCE_WORKSPACE_ROOT?.trim()) {
|
||
error('SOURCE_WORKSPACE_ROOT 不能为空。')
|
||
}
|
||
|
||
if (!params.SOURCE_NODE_NAME?.trim()) {
|
||
error('SOURCE_NODE_NAME 不能为空。')
|
||
}
|
||
|
||
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}")
|
||
}
|
||
// 部署脚本只接收校验后的端口值,避免手工参数前后空格传到 Bash。
|
||
env.EFFECTIVE_WEB_PORT = webPort
|
||
|
||
if (upstreamCause && !actualUpstreamJob?.trim()) {
|
||
error('无法从上游触发原因中解析作业名,请检查 Jenkins Pipeline Build Step 插件版本与触发链。')
|
||
}
|
||
|
||
if (actualUpstreamJob && expectedUpstreamJob && actualUpstreamJob != expectedUpstreamJob) {
|
||
error("上游作业校验失败,期望 ${expectedUpstreamJob},实际 ${actualUpstreamJob}")
|
||
}
|
||
|
||
if (actualUpstreamJob && allowedUpstreamJob && actualUpstreamJob != allowedUpstreamJob) {
|
||
error("环境门禁校验失败,仅允许 ${allowedUpstreamJob} 触发,实际 ${actualUpstreamJob}")
|
||
}
|
||
|
||
env.UPSTREAM_JOB_NAME = actualUpstreamJob ?: 'MANUAL'
|
||
}
|
||
}
|
||
}
|
||
|
||
stage('部署指定版本') {
|
||
agent {
|
||
label "${params.SOURCE_NODE_NAME}"
|
||
}
|
||
|
||
steps {
|
||
script {
|
||
// 部署脚本使用当前 Deploy 作业 checkout 出来的版本,避免重放旧 build 目录时继续执行旧脚本。
|
||
env.DEPLOY_SCRIPT_PATH = "${pwd()}/scripts/jenkins-deploy-release.sh"
|
||
}
|
||
|
||
dir("${params.SOURCE_WORKSPACE_ROOT}") {
|
||
sh """
|
||
bash -lc '
|
||
set -euo pipefail
|
||
test -d "build/${params.BUILD_VERSION}"
|
||
deploy_script="${env.DEPLOY_SCRIPT_PATH}"
|
||
chmod +x "\${deploy_script}"
|
||
deploy_args=(
|
||
--source-dir "build/${params.BUILD_VERSION}"
|
||
--deploy-dir "${params.DEPLOY_DIRECTORY}"
|
||
--web-port "${env.EFFECTIVE_WEB_PORT}"
|
||
)
|
||
if [[ "${params.CLEAR_DATABASE}" == "true" ]]; then
|
||
deploy_args+=(--clear-database)
|
||
fi
|
||
if [[ "${params.MIGRATE_ON_CONFLICT}" == "true" ]]; then
|
||
deploy_args+=(--migrate-on-conflict)
|
||
else
|
||
deploy_args+=(--no-migrate-on-conflict)
|
||
fi
|
||
if [[ -n "${params.MIGRATION_DIRECTORY}" ]]; then
|
||
deploy_args+=(--migration-dir "${params.MIGRATION_DIRECTORY}")
|
||
fi
|
||
if [[ -n "${params.MIGRATION_EXPORT_TOKEN}" ]]; then
|
||
deploy_args+=(--migration-export-token "${params.MIGRATION_EXPORT_TOKEN}")
|
||
fi
|
||
if [[ -n "${params.MIGRATION_IMPORT_TOKEN}" ]]; then
|
||
deploy_args+=(--migration-import-token "${params.MIGRATION_IMPORT_TOKEN}")
|
||
fi
|
||
if [[ "${params.RUN_DEPLOY_HOOKS_WITH_SUDO}" == "true" ]]; then
|
||
deploy_args+=(--hook-with-sudo)
|
||
fi
|
||
# 只部署上游已构建好的版本目录,避免部署阶段再次构建产生漂移。
|
||
"\${deploy_script}" "\${deploy_args[@]}"
|
||
'
|
||
"""
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
post {
|
||
success {
|
||
echo "部署完成,版本号: ${params.BUILD_VERSION},上游作业: ${env.UPSTREAM_JOB_NAME}"
|
||
}
|
||
}
|
||
}
|