From f63331a1b9f6778a9938076236d2d28861965620 Mon Sep 17 00:00:00 2001 From: kdletters Date: Fri, 24 Apr 2026 11:59:42 +0800 Subject: [PATCH] chore: update Jenkins deploy pipeline controls --- ..._RUST_BUILD_DEPLOY_PIPELINES_2026-04-23.md | 39 ++++++++++--------- jenkins/Jenkinsfile.build | 10 +++++ jenkins/Jenkinsfile.build-and-deploy | 10 +++++ jenkins/Jenkinsfile.deploy | 13 +++---- 4 files changed, 46 insertions(+), 26 deletions(-) diff --git a/docs/technical/JENKINS_RUST_BUILD_DEPLOY_PIPELINES_2026-04-23.md b/docs/technical/JENKINS_RUST_BUILD_DEPLOY_PIPELINES_2026-04-23.md index c2668461..0a0c8f75 100644 --- a/docs/technical/JENKINS_RUST_BUILD_DEPLOY_PIPELINES_2026-04-23.md +++ b/docs/technical/JENKINS_RUST_BUILD_DEPLOY_PIPELINES_2026-04-23.md @@ -7,7 +7,7 @@ 本方案为当前仓库补齐 3 条 Jenkins 流水线: 1. `构建`:只负责在仓库根目录执行 `npm run deploy:rust:remote -- --skip-upload`,生成发布包。 -2. `部署`:只负责把指定发布版本部署到 `/var/lib/jenkins/deploy/Genarrative/`,禁止人工直接点击执行,并支持按参数决定是否清空 SpacetimeDB 数据。 +2. `部署`:只负责把指定发布版本部署到 `/var/lib/jenkins/deploy/Genarrative/`,允许人工按参数启动,并支持按参数决定是否清空 SpacetimeDB 数据。 3. `构建并部署`:先构建,再把构建出的版本号传给 `部署` 流水线并等待部署完成;同时暴露 `WEB_PORT` 参数,默认把发布包 Web 端口写成 `80`,并透传是否清库。 本次只补 Jenkins 编排与本地部署脚本,不改现有 Rust 发布包构建逻辑,不恢复旧 `server-node` 部署链。 @@ -16,13 +16,14 @@ 1. 构建产物目录统一使用 `build/<版本号>/`。 2. 默认使用 Jenkins `BUILD_NUMBER` 作为版本号,避免依赖时间戳;如有需要也允许显式传 `BUILD_VERSION`。 -3. `部署` 流水线必须校验当前构建原因包含上游触发 cause,没有上游触发则直接失败。 -4. `部署` 流水线额外校验上游作业名与传入的 `EXPECTED_UPSTREAM_JOB` 一致;如配置了环境变量 `GENARRATIVE_ALLOWED_UPSTREAM_JOB`,还必须与该值一致。 -5. `构建并部署` 在触发 `部署` 前先释放自己的构建节点,避免单执行器节点出现死锁。 -6. `部署` 不重新构建,不重新上传,不从 Jenkins 插件仓库复制产物,直接使用上游构建节点的本地 `build/<版本号>/` 目录。 -7. `部署` 流水线读取触发原因时必须使用 `currentBuild.getBuildCauses(...)` 这类白名单方法,不能直接访问 `currentBuild.rawBuild`,否则会被 Jenkins Script Security 拦截。 -8. 由于 Jenkins Pipeline 的 `build` 步骤触发下游时,原因类型通常是 `org.jenkinsci.plugins.workflow.support.steps.build.BuildUpstreamCause`,实现上需要同时兼容它和经典的 `hudson.model.Cause$UpstreamCause`,否则会把真实的上游触发误判成人工执行。 -9. 如果线上进程的启停必须经过 `sudo`,只允许 `start.sh` / `stop.sh` 这两个 hook 使用 `sudo -n` 执行,部署目录清空与文件覆盖仍保持普通权限。 +3. `构建` 与 `构建并部署` 在 `checkout scm` 后、实际构建前必须执行 `git reset --hard HEAD` 与 `git clean -fd`,避免固定源码目录内的 Git 变更和未跟踪文件影响发布包;不使用 `-x`,避免删除 `node_modules/` 等忽略目录后与 `RUN_NPM_CI=false` 冲突。 +4. `部署` 流水线允许人工启动;没有上游触发 cause 时按人工部署处理,不再直接失败。 +5. `部署` 流水线仅在存在上游触发 cause 时校验上游作业名与传入的 `EXPECTED_UPSTREAM_JOB` 一致;如配置了环境变量 `GENARRATIVE_ALLOWED_UPSTREAM_JOB`,还必须与该值一致。 +6. `构建并部署` 在触发 `部署` 前先释放自己的构建节点,避免单执行器节点出现死锁。 +7. `部署` 不重新构建,不重新上传,不从 Jenkins 插件仓库复制产物,直接使用上游构建节点的本地 `build/<版本号>/` 目录。 +8. `部署` 流水线读取触发原因时必须使用 `currentBuild.getBuildCauses(...)` 这类白名单方法,不能直接访问 `currentBuild.rawBuild`,否则会被 Jenkins Script Security 拦截。 +9. 由于 Jenkins Pipeline 的 `build` 步骤触发下游时,原因类型通常是 `org.jenkinsci.plugins.workflow.support.steps.build.BuildUpstreamCause`,实现上需要同时兼容它和经典的 `hudson.model.Cause$UpstreamCause`,否则会把真实的上游触发误判成人工执行。 +10. 如果线上进程的启停必须经过 `sudo`,只允许 `start.sh` / `stop.sh` 这两个 hook 使用 `sudo -n` 执行,部署目录清空与文件覆盖仍保持普通权限。 ## 3. 节点与工作区要求 @@ -49,15 +50,16 @@ jenkins/Jenkinsfile.build 核心流程: -1. 可选执行 `npm ci`。 -2. 在源码根目录执行: +1. `checkout scm` 后执行 `git reset --hard HEAD` 与 `git clean -fd` 清理工作区。 +2. 可选执行 `npm ci`。 +3. 在源码根目录执行: ```bash npm run deploy:rust:remote -- --skip-upload --name ``` -3. 校验 `build//` 存在。 -4. 归档 `build//**` 作为 Jenkins 产物。 +4. 校验 `build//` 存在。 +5. 归档 `build//**` 作为 Jenkins 产物。 默认版本号: @@ -75,7 +77,7 @@ jenkins/Jenkinsfile.deploy 核心流程: -1. 校验触发原因必须是上游流水线,而不是人工点击;实现上同时兼容 `BuildUpstreamCause` 与经典 `UpstreamCause`。 +1. 读取触发原因;人工启动时跳过上游门禁,上游触发时同时兼容 `BuildUpstreamCause` 与经典 `UpstreamCause` 并继续校验上游作业名。 2. 校验 `BUILD_VERSION`、`SOURCE_WORKSPACE_ROOT`、`DEPLOY_DIRECTORY` 非空。 3. 执行: @@ -109,11 +111,12 @@ jenkins/Jenkinsfile.build-and-deploy 核心流程: -1. 复用与 `构建` 相同的构建命令生成 `build//`。 -2. 归档 `build//**`。 -3. 记录当前 `NODE_NAME`、源码根目录、版本号。 -4. 构建时额外透传 `--web-port `,默认生成监听 `80` 的发布包。 -5. 触发 `部署` 流水线,并传递: +1. `checkout scm` 后执行 `git reset --hard HEAD` 与 `git clean -fd` 清理工作区。 +2. 复用与 `构建` 相同的构建命令生成 `build//`。 +3. 归档 `build//**`。 +4. 记录当前 `NODE_NAME`、源码根目录、版本号。 +5. 构建时额外透传 `--web-port `,默认生成监听 `80` 的发布包。 +6. 触发 `部署` 流水线,并传递: - `BUILD_VERSION` - `SOURCE_WORKSPACE_ROOT` - `SOURCE_NODE_NAME` diff --git a/jenkins/Jenkinsfile.build b/jenkins/Jenkinsfile.build index f06f7ad9..db165ede 100644 --- a/jenkins/Jenkinsfile.build +++ b/jenkins/Jenkinsfile.build @@ -30,6 +30,16 @@ pipeline { dir("${env.WORKSPACE_ROOT}") { checkout scm + sh ''' + bash -lc ' + set -euo pipefail + # 构建前清理工作区内的 Git 变更和未跟踪文件,避免复用固定源码目录时受到上次构建残留影响。 + # 这里不使用 -x,避免删除 node_modules 等忽略目录后与 RUN_NPM_CI=false 的配置冲突。 + git reset --hard HEAD + git clean -fd + ' + ''' + script { // 是否重装依赖交给流水线参数决定,避免每次构建都重复执行 npm ci。 if (params.RUN_NPM_CI) { diff --git a/jenkins/Jenkinsfile.build-and-deploy b/jenkins/Jenkinsfile.build-and-deploy index a9607900..7fb541d2 100644 --- a/jenkins/Jenkinsfile.build-and-deploy +++ b/jenkins/Jenkinsfile.build-and-deploy @@ -37,6 +37,16 @@ pipeline { dir("${env.WORKSPACE_ROOT}") { checkout scm + sh ''' + bash -lc ' + set -euo pipefail + # 构建前清理工作区内的 Git 变更和未跟踪文件,避免复用固定源码目录时受到上次构建残留影响。 + # 这里不使用 -x,避免删除 node_modules 等忽略目录后与 RUN_NPM_CI=false 的配置冲突。 + git reset --hard HEAD + git clean -fd + ' + ''' + script { // 是否重装依赖交给流水线参数决定,避免每次构建都重复执行 npm ci。 if (params.RUN_NPM_CI) { diff --git a/jenkins/Jenkinsfile.deploy b/jenkins/Jenkinsfile.deploy index 523da57f..6dfdafe0 100644 --- a/jenkins/Jenkinsfile.deploy +++ b/jenkins/Jenkinsfile.deploy @@ -24,6 +24,7 @@ pipeline { steps { script { + // 部署流水线允许手动启动;如存在上游触发原因,则继续执行上游作业名门禁。 // Pipeline 的 build 步骤通常会把下游触发原因记录成 BuildUpstreamCause, // 直接只查经典 UpstreamCause 会把真实的上游触发误判成“人工执行”。 def pipelineUpstreamCauses = currentBuild.getBuildCauses('org.jenkinsci.plugins.workflow.support.steps.build.BuildUpstreamCause') @@ -36,10 +37,6 @@ pipeline { upstreamCause = classicUpstreamCauses[0] } - if (!upstreamCause) { - error('部署流水线禁止人工直接执行,只允许由上游构建并部署流水线触发。') - } - def actualUpstreamJob = upstreamCause?.upstreamProject ?: '' def expectedUpstreamJob = params.EXPECTED_UPSTREAM_JOB?.trim() def allowedUpstreamJob = env.GENARRATIVE_ALLOWED_UPSTREAM_JOB?.trim() @@ -56,19 +53,19 @@ pipeline { error('SOURCE_NODE_NAME 不能为空。') } - if (!actualUpstreamJob?.trim()) { + if (upstreamCause && !actualUpstreamJob?.trim()) { error('无法从上游触发原因中解析作业名,请检查 Jenkins Pipeline Build Step 插件版本与触发链。') } - if (expectedUpstreamJob && actualUpstreamJob != expectedUpstreamJob) { + if (actualUpstreamJob && expectedUpstreamJob && actualUpstreamJob != expectedUpstreamJob) { error("上游作业校验失败,期望 ${expectedUpstreamJob},实际 ${actualUpstreamJob}") } - if (allowedUpstreamJob && actualUpstreamJob != allowedUpstreamJob) { + if (actualUpstreamJob && allowedUpstreamJob && actualUpstreamJob != allowedUpstreamJob) { error("环境门禁校验失败,仅允许 ${allowedUpstreamJob} 触发,实际 ${actualUpstreamJob}") } - env.UPSTREAM_JOB_NAME = actualUpstreamJob + env.UPSTREAM_JOB_NAME = actualUpstreamJob ?: 'MANUAL' } } }