CI/CD 十關

LV.6/10

Rules 與 workflow

用 rules 與 workflow 控制什麼時候建立 pipeline、哪些 job 要跑、要不要手動按

進階約 25 分鐘rules、workflow、changes、exists、when: manual

學習目標

  • 用 workflow:rules 決定整條 pipeline 要不要建立。
  • 用 rules:if、rules:changes、rules:exists 決定 job 要不要跑。
  • 會用 when: manual、when: delayed、when: never。
  • 知道 only/except 已不建議使用,一律改寫成 rules。

觀念

rules 是一串條件,由上往下比對,第一條符合就停:

結果意思
符合,沒寫 when照常執行(on_success)
符合,when: never不執行
符合,when: manual出現按鈕,要有人按
符合,when: delayed等 start_in 後執行
全部不符合job 不會出現在 pipeline 裡

本關每個 job 什麼時候會跑

jobpush 到 mainpush 到 feature/xMR
always跑跑跑
default-branch-only跑——
feature-branch-only—跑—
mr-only——跑
docs-changed文件有改才跑文件有改才跑文件有改才跑
notes-exist檔案存在就跑同左同左
manual-release手動按鈕——

重點說明

  • 重複 pipeline:同一個分支開了 MR,push 一次會同時產生分支 pipeline 與 MR pipeline。workflow 的前兩條規則讓「有 MR 時只跑 MR pipeline」。
  • **parent_pipeline**:本課程根目錄的 .gitlab-ci.yml 把每一關當成子 pipeline 觸發,來源是 parent_pipeline,所以要在 workflow 明確放行,否則子 pipeline 會因為沒有任何 job 而建立失敗。
  • **changes**:比對這次 push 改了哪些檔案。手動執行、排程這類沒有「前一個 commit」的來源,changes 一律當成成立;要穩定比對可以加 compare_to: refs/heads/main。
  • manual 的兩種寫法:job 層的 when: manual 預設不擋 pipeline;寫在 rules 裡的 when: manual 預設會擋(pipeline 停在「blocked」),要不擋就加 allow_failure: true。正式環境的發布通常就是要擋,Lv.9 會再談。
  • 正規表示式:=~ /^feature\//,斜線要跳脫。

動手做

  1. 開一個 feature/try-rules 分支 push,feature-branch-only 出現、default-branch-only 消失。
  2. 對 main 開 MR,看 pipeline 只剩一條(MR pipeline),而且出現 mr-only。
  3. 「Run pipeline」時加變數 SKIP_LESSON6=yes,skip-switch 不會出現。
  4. 在 main 的 pipeline 按下 manual-release 的播放鍵。

常見錯誤

  • 在同一個 job 混用 rules 和 only/except:GitLab 會報錯。
  • when: never 放在最後一條:前面已經符合就輪不到它了,排除條件要放最前面。
  • changes 的路徑忘了加 **:lv06-rules/*.md 不包含子資料夾。

下一關

Lv.7 用 needs 打破階段限制,讓 job 一準備好就開始。

範例檔案

資料夾 lv06-rules/ 的全部檔案。.gitlab-ci.yml 可以直接複製到自己的專案根目錄執行。

lv06-rules/.gitlab-ci.yml
lv06-rules/.gitlab-ci.yml
# Lv.6 Rules 與 workflow:決定「整條 pipeline」與「每個 job」要不要跑、怎麼跑
# rules 由上往下比對,第一條符合的就決定結果;都不符合=這個 job 不會出現在 pipeline 裡。

# workflow:rules 決定整條 pipeline 要不要建立
workflow:
  rules:
    # 當成子 pipeline 被觸發時(本課程的根目錄 pipeline 就是這樣跑每一關)
    - if: $CI_PIPELINE_SOURCE == "parent_pipeline"
    # 有 MR 時只跑 MR pipeline,避免同一個 commit 跑兩條(分支+MR)
    - if: $CI_PIPELINE_SOURCE == "merge_request_event"
    - if: $CI_COMMIT_BRANCH && $CI_OPEN_MERGE_REQUESTS
      when: never
    - if: $CI_COMMIT_BRANCH
    - if: $CI_COMMIT_TAG

stages:
  - check
  - release

default:
  image: alpine:3.22

always:
  stage: check
  script:
    - echo "沒有 rules 的 job 永遠會跑(來源:$CI_PIPELINE_SOURCE)"

default-branch-only:
  stage: check
  rules:
    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
  script:
    - echo "只在 $CI_DEFAULT_BRANCH 執行"

feature-branch-only:
  stage: check
  rules:
    - if: $CI_COMMIT_BRANCH =~ /^feature\//
  script:
    - echo "只在 feature/ 開頭的分支執行"

mr-only:
  stage: check
  rules:
    - if: $CI_PIPELINE_SOURCE == "merge_request_event"
  script:
    - echo "只在 MR pipeline 執行,標題:$CI_MERGE_REQUEST_TITLE"

docs-changed:
  stage: check
  rules:
    # 這一關的說明文件有改才跑;手動、排程等沒有「改了什麼」的來源一律視為有改
    - changes:
        - lv06-rules/**/*.md
  script:
    - echo "說明文件有變更,執行文件檢查"
    - grep -q '^# Lv.6' lv06-rules/README.md

notes-exist:
  stage: check
  rules:
    - exists:
        - lv06-rules/release-notes.md
  script:
    - echo "找到 release-notes.md"
    - cat lv06-rules/release-notes.md

skip-switch:
  stage: check
  rules:
    # 設變數 SKIP_LESSON6=yes 就不跑;when: never 要放在前面
    - if: $SKIP_LESSON6 == "yes"
      when: never
    - when: on_success
  script:
    - echo "沒有設定 SKIP_LESSON6=yes,所以執行"

delayed:
  stage: release
  rules:
    - when: delayed
      start_in: 10 seconds
  script:
    - echo "等了 10 秒才開始"

manual-release:
  stage: release
  rules:
    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
      when: manual
      # rules 裡的 manual 預設會「卡住」pipeline,直到有人按下;allow_failure: true 表示不擋
      allow_failure: true
  script:
    - echo "有人按下了發布按鈕:$GITLAB_USER_LOGIN"
lv06-rules/release-notes.md
lv06-rules/release-notes.md
# Lv.6 示範用的發布說明

`notes-exist` 這個 job 用 `rules:exists` 偵測到本檔案存在才會執行。把這個檔案刪掉,下一條 pipeline 就看不到那個 job。