打了个 nightly 标签,结果发布了版本
目标
通过改变各种情况,确认根据分支、tag、文件是否存在、与远程的改动,作业会进入或退出列表,并把规则的顺序和规则所决定的变量,作为部署策略写进配置。
为什么重要
rules 不是作业开始时询问的条件,而是流水线创建时决定列表的规则。因为只应用第一个匹配项,所以条目的顺序就是策略,顺序放错,就会在没有错误的情况下让部署消失,或者让发布发往错误的 tag。changes 会因为与什么比较而结果不同,如果把条件和值(部署目标)分开放置,总有一天两者会对不上。这类差别光读配置很难看出来,必须改变情况去运行才行。
步骤
- 把
/root/glci-rules创建为 Git 仓库,并在.gitignore中放入.gitlab-ci-local/。在.gitlab-ci.yml中放入 stages[build, deploy]、没有条件的作业unit(build,echo "unit log=$LOG_LEVEL"),以及只在$CI_COMMIT_BRANCH == "main"时才创建的deploy-staging(deploy,echo staging),并提交到 main 分支。评分器会在副本中切换到feature/login分支,查看 deploy-staging 是否从列表中消失。 - 添加作业
release-notes(deploy,echo notes),使其只在$CI_COMMIT_TAG =~ /^v\d+\.\d+\.\d+$/时才创建,并提交。gitlab-ci-local 不读取 Git tag,所以用--variable CI_COMMIT_TAG=v1.2.0来模拟 tag 流水线。如果是 v1.2.0,它必须在列表中,如果是nightly或没有 tag,则必须不在列表中。 - 给作业
publish(deploy,echo publish)的 rules 设置三个条目:有 tag 时when: on_success,是 main 分支时when: manual和allow_failure: false,其余情况when: never。提交。在列表中,如果是 main 必须是 manual(allowFailure false),如果有 tag 变量必须是 on_success,如果是 feature 分支则必须没有。 - 用
rules: - exists: [Dockerfile]添加作业docker-build(build,echo docker)并提交。这个仓库里暂时不创建 Dockerfile。评分器会检查,只有在副本中放入 Dockerfile 时才会生成该作业。 - 在
/root/glci-rules-origin.git中创建 bare 仓库并注册为origin,push main 之后,运行git remote set-head origin main。然后用rules: - changes: ["docs/**/*"]添加作业docs-build(build,echo docs),提交并再次 push。评分器会在副本中分别创建修改了 docs 之下内容的分支和只修改了其他文件的分支,检查是否只有前一种情况会生成 docs-build。 - 添加作业
deploy(deploy,echo "target=$DEPLOY_TARGET")。rules 是:tag 时variables: {DEPLOY_TARGET: production},main 时variables: {DEPLOY_TARGET: staging}。提交并 push。在 main 上运行必须打印target=staging,给出 tag 变量时必须打印target=production。 - 在
.gitlab-ci.yml最上面放入workflow: rules:。main 时是variables: {LOG_LEVEL: warn},其余情况(when: always)是variables: {LOG_LEVEL: debug}。提交并 push。在 main 上,unit 日志必须是unit log=warn,在 feature 分支上必须是unit log=debug。
参考
- 这台 VM 上没有 GitLab 服务器和 Runner,由 gitlab-ci-local 4.75.1 按与 GitLab 相同的规则解析 .gitlab-ci.yml,并用 shell 执行作业。写了
image:就会试图用 Docker 运行,所以不要使用。受保护变量、掩码、CI_JOB_TOKEN、Runner tag、合并请求流水线的生成是服务器功能,在这里不会重现。 - 运行:在仓库根目录用
gitlab-ci-local --shell-isolation --no-artifacts-to-source(每个作业使用单独的工作目录,不把产物写回仓库),作业列表:gitlab-ci-local --list-csv-all,解析后的配置:gitlab-ci-local --preview。gitlab-ci-local 只把 Git 跟踪的文件传给作业,所以创建文件后请git add。评分器会把仓库复制一份,提交所有文件后,再用同一个工具重新运行。 - gitlab-ci-local 的差异(实测):它不会把 Git tag 读作 CI_COMMIT_TAG,所以用
--variable CI_COMMIT_TAG=...来模拟。rules:changes 与 origin 的默认分支比较,并忽略 compare_to。它不会重现由 workflow:rules 决定不创建流水线的行为,以及 manual 作业阻挡后面 stage 的行为。 - 要尝试切换分支时,请在副本或新分支上进行,评分之前请回到 main 并提交。
- Specify when jobs run with rules · workflow · Predefined CI/CD variables · CI/CD YAML syntax reference
只在 main 上创建预发布部署
把 /root/glci-rules 创建为 Git 仓库,并在 .gitignore 中放入 .gitlab-ci-local/。在 .gitlab-ci.yml 中放入 stages [build, deploy]、没有条件的作业 unit(build,echo "unit log=$LOG_LEVEL"),以及只在 $CI_COMMIT_BRANCH == "main" 时才创建的 deploy-staging(deploy,echo staging),并提交到 main 分支。评分器会在副本中切换到 feature/login 分支,查看 deploy-staging 是否从列表中消失。
rules 是在创建流水线时评估的。如果没有任何条件匹配,该作业会作为 'never' 从列表中排除。切换分支后,用 gitlab-ci-local --list-csv-all 比较 when 列。
连 tag 名称的形状都要看,才创建发布说明
添加作业 release-notes(deploy,echo notes),使其只在 $CI_COMMIT_TAG =~ /^v\d+\.\d+\.\d+$/ 时才创建,并提交。gitlab-ci-local 不读取 Git tag,所以用 --variable CI_COMMIT_TAG=v1.2.0 来模拟 tag 流水线。如果是 v1.2.0,它必须在列表中,如果是 nightly 或没有 tag,则必须不在列表中。
匹配运算符(等号后接波浪号)是正则表达式比较。tag 变量只在 tag 流水线中才会被填充,所以如果只看它是否存在,连 nightly 这样的临时 tag 也会发出发布。正则表达式要用斜杠括起来。
只应用第一个匹配项——顺序就是策略
给作业 publish(deploy,echo publish)的 rules 设置三个条目:有 tag 时 when: on_success,是 main 分支时 when: manual 和 allow_failure: false,其余情况 when: never。提交。在列表中,如果是 main 必须是 manual(allowFailure false),如果有 tag 变量必须是 on_success,如果是 feature 分支则必须没有。
从上往下读,只使用第一个匹配的条目。如果把没有条件的 when: never 放在中间,它下面的条目就永远不会被读到,而且不会出现任何错误。要把 manual 作业真正用作审批门禁,请同时设置 allow_failure: false。
只在有 Dockerfile 的仓库中才构建镜像
用 rules: - exists: [Dockerfile] 添加作业 docker-build(build,echo docker)并提交。这个仓库里暂时不创建 Dockerfile。评分器会检查,只有在副本中放入 Dockerfile 时才会生成该作业。
exists 查看的是仓库中是否有该路径的文件。当多个仓库 include 同一个模板时,用它可以在不修改每个仓库配置的情况下,只开启相应的作业。
只在文档有改动的分支上构建文档
在 /root/glci-rules-origin.git 中创建 bare 仓库并注册为 origin,push main 之后,运行 git remote set-head origin main。然后用 rules: - changes: ["docs/**/*"] 添加作业 docs-build(build,echo docs),提交并再次 push。评分器会在副本中分别创建修改了 docs 之下内容的分支和只修改了其他文件的分支,检查是否只有前一种情况会生成 docs-build。
changes 的关键是“与什么相比的改动”。gitlab-ci-local 与远程默认分支(origin/main)比较,所以必须有远程。GitLab 的分支流水线与上一次 push 比较,合并请求流水线则与目标分支比较。
命中了哪条规则,决定部署目标
添加作业 deploy(deploy,echo "target=$DEPLOY_TARGET")。rules 是:tag 时 variables: {DEPLOY_TARGET: production},main 时 variables: {DEPLOY_TARGET: staging}。提交并 push。在 main 上运行必须打印 target=staging,给出 tag 变量时必须打印 target=production。
rules 条目中的 variables,只有在命中该条目时才会进入作业。把条件和值放在同一个地方,“明明是 tag 却发往了 staging”这类不一致,在结构上就消失了。
整条流水线的值在 workflow 中决定
在 .gitlab-ci.yml 最上面放入 workflow: rules:。main 时是 variables: {LOG_LEVEL: warn},其余情况(when: always)是 variables: {LOG_LEVEL: debug}。提交并 push。在 main 上,unit 日志必须是 unit log=warn,在 feature 分支上必须是 unit log=debug。
workflow:rules 决定是否创建流水线,以及要放入整条流水线的变量。不必在每个作业中重复同样的条件。防止合并请求流水线与分支流水线重复的地方也在这里,但这个行为只能在 GitLab 服务器上确认。