下游触发分支规则(FANOUT_BRANCH 单原则)
-
下游触发分支规则(FANOUT_BRANCH 单原则)
1. 背景
在多仓库、多级触发(A -> B -> C)的场景中,如果每一跳都“按当前项目的运行分支”去触发下游,很容易发生分支漂移。
本规则的目标:
- 200+ 项目尽量不改各自的
.gitlab-ci.yml - 不使用 GitLab API Token
- 触发链路稳定、可预测
2. 唯一原则:FANOUT_BRANCH 贯穿
我们定义一个变量:
FANOUT_BRANCH,表示“本次触发链路从最上游开始的分支”。生成/继承规则:
- 若上游已传入
FANOUT_BRANCH,则下游沿用该值 - 否则默认
FANOUT_BRANCH = CI_COMMIT_BRANCH
每次触发下游时,都必须继续传递
FANOUT_BRANCH,从而保证多级触发不会漂移。3. 下游触发分支的决定规则
3.1 显式指定优先(最重要)
如果项目在
variables:中显式写了DOWNSTREAM_BRANCH,例如:variables: DOWNSTREAM_PROJECT: platform/deploy/blazor-app DOWNSTREAM_BRANCH: master则表现为:
- 永远触发下游的
master分支 - 即使当前上游在
feature/LT-49、bugfix/*等分支上运行,也不会触发同名分支 FANOUT_BRANCH仍会被传递到下游,但仅用于链路追踪/后续再触发,不参与本次分支选择
3.2 未显式指定时的默认行为
如果未显式设置
DOWNSTREAM_BRANCH,则默认:DOWNSTREAM_BRANCH = FANOUT_BRANCH
即:上游在
feature/LT-49运行时触发下游feature/LT-49;上游在master运行时触发下游master。4. 关于 master/main
在“不使用 Token、不调用 API”的前提下,我们无法在触发前自动判断下游默认分支是
master还是main。治理建议:
- 公司内尽量统一主干命名为
master - 若少数仓库历史上使用
main,建议一次性调整为master,之后无需在 200+ 项目侧维护差异
5. 适用范围
- NuGet / IIS / Docker 等流水线,只要通过 common-ci 的 downstream trigger 模板触发下游,本规则均适用。
6. 常见问题(FAQ)
6.1 上游是 develop(或 feature/*),下游只有 master,会怎么样?
在未显式配置
DOWNSTREAM_BRANCH的情况下,默认行为是:DOWNSTREAM_BRANCH = FANOUT_BRANCHFANOUT_BRANCH默认等于当前上游的CI_COMMIT_BRANCH
因此:
- 上游在
develop跑时,会尝试触发下游的develop - 若下游仓库没有
develop分支(只有master),则trigger:作业会失败 - 因为我们使用
strategy: depend,上游流水线会在“触发下游”阶段失败
治理方式(二选一):
- 方式 A(推荐,稳定)
- 对“只有主干分支”的下游仓库,在上游项目显式配置
DOWNSTREAM_BRANCH: master - 这样不管上游在哪个分支跑,下游都只触发
master
- 对“只有主干分支”的下游仓库,在上游项目显式配置
- 方式 B(统一分支体系)
- 在下游仓库创建对应分支(例如创建
develop),保证分支存在 - 适用于需要严格“同分支联动”的仓库
- 在下游仓库创建对应分支(例如创建
- 200+ 项目尽量不改各自的
-
下游触发分支选择:现象、根因与最终方案(仅 develop/master)
背景与目标
我们在 NuGet 组件发布流水线中使用下游触发(trigger downstream pipeline)来联动发布/同步多个项目。
核心目标是:
- 让下游触发分支选择可预测、可维护、可审计。
- 避免因为 GitLab Token 权限差异导致的“误判分支不存在”“偶发失败”等不稳定行为。
- 适配跨项目、跨实例同步(common-ci 同步到多个 GitLab 实例)的场景。
现象(问题表现)
在
trigger_downstream_pipeline作业中出现以下典型问题:-
现象 A:作业过早触发
- 流水线还未完成 push/update 等步骤,下游就被触发。
-
现象 B:下游明明存在分支,却提示分支不存在
- 例如下游有
develop,但 job 日志显示“分支不存在”。
- 例如下游有
-
现象 C:HTTP 401 Unauthorized
- 使用
CI_JOB_TOKEN调用 GitLab API 触发下游 pipeline 时返回 401。 - 说明默认情况下
CI_JOB_TOKEN不具备跨项目触发的权限。
- 使用
-
现象 D:Reference not found / downstream pipeline can not be created
- trigger 使用的
branch变量为空或未按预期注入,导致 GitLab 认为 ref 不存在。
- trigger 使用的
根因分析
1. GitLab 原生
trigger:的能力边界- 原生
trigger:只能触发一个确定的project + branch。 - 它不会做“分支不存在则 fallback”这种动态选择。
因此,如果要做“某项目没有分支就改触发另一个分支、后续再恢复原分支”的行为,必须:
- 预先知道下游是否存在该分支(需要 API),或
- 直接尝试触发并解析失败原因(需要 API),或
- 人工/静态配置规则(每项目一个开关),或
- 制定组织规范(所有项目都具备统一分支)。
2. 跨项目权限与 Token 复杂度
如果使用 API 触发下游(
/api/v4/projects/:id/pipeline):CI_JOB_TOKEN在多数场景下对跨项目 API 权限不足(401)。- 使用
GITLAB_API_TOKEN(Project/Group/Personal Access Token)可以解决,但引入:- Token 分发与轮换成本
- 安全风险与合规审计成本
- 多实例同步时的配置复杂度
3. 变量注入顺序与 artifacts 依赖
我们使用
dotenvartifacts 在 setup 阶段写入变量(如FANOUT_BRANCH、DOWNSTREAM_BRANCH)。如果 trigger job 没有正确
needs对应的 setup job(且artifacts: true),就可能拿不到变量,导致:$DOWNSTREAM_BRANCH为空- trigger 触发时报
Reference not found
方案对比(做过的选择)
方案 1:智能分支(API + fallback)
- 思路:优先触发“根分支”,失败再 fallback 到
develop/master。 - 优点:理论上能实现更复杂的链路行为。
- 缺点:
- 需要跨项目 API 权限,
CI_JOB_TOKEN往往不够(401) - Token 配置与安全治理成本高
- 失败原因多样(401/404/网络/权限策略),可维护性差
- 需要跨项目 API 权限,
方案 2:自建服务(分支查询/代理触发)
- 思路:将 GitLab token 收拢到内部服务,CI 只调用内部服务。
- 优点:可控、安全面可收敛。
- 缺点:引入额外系统:
- 部署与运维成本
- 高可用/限流/审计等需求
- 增加链路复杂度
方案 3(最终采用):最简单方案(无 API,仅 develop/master)
结论:采用方案 3。
- 只在
develop/master两者之间选择分支。 - 在 setup 阶段一次性确定“触发链根分支”,并通过
dotenv跨项目传递。 - trigger 使用 GitLab 原生
trigger:,不使用 API,不依赖 token。
该方案的关键前提:所有参与链路的项目必须同时存在
develop和master分支。这项前提换来的是:
- 不需要任何额外权限与 token
- 行为稳定、可预测
- 维护成本最低
最终方案设计
1. 根分支归一化(setup 阶段)
在
setup_version(.version_setup)阶段:- 读取触发源:
FANOUT_BRANCH(上游传递)或CI_COMMIT_BRANCH(当前) - 归一化为:
master/main/hotfix/*→master- 其他 →
develop
- 写入 dotenv:
FANOUT_BRANCH=<develop|master>DOWNSTREAM_BRANCH=<develop|master>(如果用户未显式指定)
2. 下游触发(trigger 阶段)
在
.trigger-downstream中:- 使用 GitLab 原生:
trigger.project: $DOWNSTREAM_PROJECTtrigger.branch: $DOWNSTREAM_BRANCH
- 同时向下游继续传递:
FANOUT_BRANCHFANOUT_FROMSTOP_FANOUT
并通过
needs确保拿到 setup 阶段的 dotenv:needs: setup_global_var (artifacts: true)
代码改动点(common-ci)
-
shared/version-setup.yml- 在
version.env中写入归一化后的FANOUT_BRANCH,并默认填充DOWNSTREAM_BRANCH。
- 在
-
nuget/7-trigger-downstream.yml- 恢复为原生
trigger: needs增加setup_global_var(带artifacts: true)以确保DOWNSTREAM_BRANCH可用
- 恢复为原生
使用与约束
-
组织规范(必须执行)
- 所有参与链路的仓库必须同时存在
develop与master分支。
- 所有参与链路的仓库必须同时存在
-
项目侧配置(可选)
- 在
.gitlab-ci.yml中配置:DOWNSTREAM_PROJECT:下游项目路径
- 一般无需配置
DOWNSTREAM_BRANCH,默认由上游归一化规则决定。
- 在
常见问题(FAQ)
Q1:为什么不继续做“智能分支 + fallback”?
因为需要 API 触发或分支判断,而跨项目 token 权限与安全治理成本过高,且多实例同步场景下不可控因素更多。
Q2:为什么会出现 Reference not found?
多数情况下是 trigger job 没有拿到 setup 阶段的 dotenv(变量未注入),导致
branch为空/错误。Q3:这个方案还能实现“某项目只有 master,但后续又想回到 develop”吗?
不能。
不使用 API 的前提下无法判断下游是否存在某分支,因此无法对“每一跳”做动态选择。
要支持这种行为,必须回到 API 方案或引入项目级配置。