From d948889f24d5d63a8b42e1f479a68c0595371ca6 Mon Sep 17 00:00:00 2001 From: kdletters Date: Thu, 23 Apr 2026 04:08:30 +0800 Subject: [PATCH] fix(jenkins): support pipeline upstream cause gate --- ..._RUST_BUILD_DEPLOY_PIPELINES_2026-04-23.md | 7 ++++--- jenkins/Jenkinsfile.deploy | 21 +++++++++++++++---- 2 files changed, 21 insertions(+), 7 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 99792d15..ce171cc3 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 @@ -16,11 +16,12 @@ 1. 构建产物目录统一使用 `build/<版本号>/`。 2. 默认使用 Jenkins `BUILD_NUMBER` 作为版本号,避免依赖时间戳;如有需要也允许显式传 `BUILD_VERSION`。 -3. `部署` 流水线必须校验当前构建原因包含 `UpstreamCause`,没有上游触发则直接失败。 +3. `部署` 流水线必须校验当前构建原因包含上游触发 cause,没有上游触发则直接失败。 4. `部署` 流水线额外校验上游作业名与传入的 `EXPECTED_UPSTREAM_JOB` 一致;如配置了环境变量 `GENARRATIVE_ALLOWED_UPSTREAM_JOB`,还必须与该值一致。 5. `构建并部署` 在触发 `部署` 前先释放自己的构建节点,避免单执行器节点出现死锁。 6. `部署` 不重新构建,不重新上传,不从 Jenkins 插件仓库复制产物,直接使用上游构建节点的本地 `build/<版本号>/` 目录。 -7. `部署` 流水线读取触发原因时必须使用 `currentBuild.getBuildCauses('hudson.model.Cause$UpstreamCause')` 这类白名单方法,不能直接访问 `currentBuild.rawBuild`,否则会被 Jenkins Script Security 拦截。 +7. `部署` 流水线读取触发原因时必须使用 `currentBuild.getBuildCauses(...)` 这类白名单方法,不能直接访问 `currentBuild.rawBuild`,否则会被 Jenkins Script Security 拦截。 +8. 由于 Jenkins Pipeline 的 `build` 步骤触发下游时,原因类型通常是 `org.jenkinsci.plugins.workflow.support.steps.build.BuildUpstreamCause`,实现上需要同时兼容它和经典的 `hudson.model.Cause$UpstreamCause`,否则会把真实的上游触发误判成人工执行。 ## 3. 节点与工作区要求 @@ -73,7 +74,7 @@ jenkins/Jenkinsfile.deploy 核心流程: -1. 校验触发原因必须是上游流水线,而不是人工点击。 +1. 校验触发原因必须是上游流水线,而不是人工点击;实现上同时兼容 `BuildUpstreamCause` 与经典 `UpstreamCause`。 2. 校验 `BUILD_VERSION`、`SOURCE_WORKSPACE_ROOT`、`DEPLOY_DIRECTORY` 非空。 3. 执行: diff --git a/jenkins/Jenkinsfile.deploy b/jenkins/Jenkinsfile.deploy index be94c4f6..15201604 100644 --- a/jenkins/Jenkinsfile.deploy +++ b/jenkins/Jenkinsfile.deploy @@ -22,13 +22,22 @@ pipeline { steps { script { - // 使用 RunWrapper 白名单方法读取触发原因,避免触发 Jenkins Script Security 审批。 - def upstreamCauses = currentBuild.getBuildCauses('hudson.model.Cause$UpstreamCause') - if (!upstreamCauses || upstreamCauses.isEmpty()) { + // 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] + } + + if (!upstreamCause) { error('部署流水线禁止人工直接执行,只允许由上游构建并部署流水线触发。') } - def upstreamCause = upstreamCauses[0] def actualUpstreamJob = upstreamCause?.upstreamProject ?: '' def expectedUpstreamJob = params.EXPECTED_UPSTREAM_JOB?.trim() def allowedUpstreamJob = env.GENARRATIVE_ALLOWED_UPSTREAM_JOB?.trim() @@ -45,6 +54,10 @@ pipeline { error('SOURCE_NODE_NAME 不能为空。') } + if (!actualUpstreamJob?.trim()) { + error('无法从上游触发原因中解析作业名,请检查 Jenkins Pipeline Build Step 插件版本与触发链。') + } + if (expectedUpstreamJob && actualUpstreamJob != expectedUpstreamJob) { error("上游作业校验失败,期望 ${expectedUpstreamJob},实际 ${actualUpstreamJob}") }