pull_request_target で skip ci を実現する
まとめ
- pull_request_target では skip ci が機能しないので、同様のことがやりたければ独自に実装するしかない
- やり方は色々あるが、特定の PR label をつけたら job を skip するのが楽では
- 特定の PR label をつけたら特定の job を fail させることで、特別な権限なしに PR をマージできてしまうのを防ぐことができる
導入
以前 pull_request_target に関する記事を書きました。
ところで、 GitHub Actions は [skip ci]
と commit message に書くと workflow の trigger させないでおけます。
しかしこの機能は pull_request はサポートされているものの pull_request_target はサポートされていません。
Skip instructions only apply to the push and pull_request events. For example, adding [skip ci] to a commit message won't stop a workflow that's triggered on: pull_request_target from running.
なぜこのような仕様なのかはよくわかりませんが、 pull_request_target であっても CI を skip したくなることはあります。
そこで pull_request_target でも CI を skip する方法を考察します。
そもそも CI を skip してよいのか
勿論基本は skip すべきではありませんし、 branch protection rule (branch ruleset) で CI を pass しないとマージできないようにすべきです。
しかし大きなマイグレーションで CI が実行されると困るから CI を skip して admin 権限で force merge したいというケースはままあります。
その場合
- audit log として残るようにして後から追跡できるようにする
- force merge したら通知がいくようにし気づけるようにする
- なぜ force merge したのか理由を pull request に残す
- 他の人からの approve をもらう
といったことをすべきでしょう。
workflow run 自体を skip することはできず、 job を skip するしかない
workflow run 自体を skip するのは on
filter によってしかできず、 commit message などによって skip することはできません。
branch filter で skip できるかと一瞬思いましたが、 branch filter は base branch による filtering なので難しいでしょう。
なので job の if
で job を skip するしかありません。
job の skip で branch protection rule を pass するべきか?
job を skip することで required check を pass することもできます。
というか、単純に skip すると pass することになります。
こうすると force merge しなくてもマージできるということになります。
これには一長一短あるでしょう。
pros:
- force merge しなくてよい
- admin 権限不要
- 一時的に branch protection rule を修正する必要がない
cons:
- 特別な権限がなくても容易に CI を pass できてしまう
- CI が skip されていることに reviewer が気づかずに approve, merge される可能性がある
- audit log などが残らない可能性がある
CI を pass しないようにするにはどうしたらよいか
やり方は幾つか考えられますが、 CI を skip したい場合だけ fail し、それ以外では skip される job を用意すると良いでしょう。
jobs:
skip-ci:
if: contains(github.event.issue.labels.*.name, 'skip-ci')
steps:
- run: exit 1
job を skip する方法
- commit message で skip
- PR label で skip
- PR title で skip
commit message で skip
これは skip ci と互換性があるため、ユーザーにとって分かりやすいでしょう。
一方で最新の head commit の commit message を取得する必要があります。
自分の理解が正しければ event の payload にコミットメッセージは含まれていないため、 API で取得する必要があります。
そのため、最初に実行する job で commit message を取得し、後続の job の if で skip する必要があり、 workflow の構成を変える必要もあるかもしれません。
尤も、以下の記事に書いてあるように、 dorny/paths-filter のような action を使って job を filtering している場合、 dorny/paths-filter を実行する job で commit message を取得すればいいので大きく構成を変える必要はありません。
PR label or title で skip
jobs:
foo:
if: |
!startsWith(github.event.pull_request.title, '[skip ci]')
このやり方は skip ci とは互換性がないため、ユーザーを混乱させるかもしれません。
独自ルールになるので、きちんとドキュメントを書くのが望ましいでしょう。
また pull_request と pull_request_target が混在しているような場合 pull_request を skip できません。
尤も、混在させる必要はあまりない気はします。
PR title や label は event の payload に含まれているため API で取得する必要はありませんし、 job を追加したりする必要もありません。
尤も、後から PR title や label を修正して workflow run を retry しても payload は変わらないのでそこは注意が必要です。
その場合、新たに commit を push するなどする必要があります。
[skip ci]
のみならず幾つかのパターンをサポートしようと思うとその分 if は冗長になります。
また skip ci されたものを検索したいときにクエリが複雑になるので、決め打ちで条件を絞ったほうが良いかと思います。
title と label はどちらでも良さそうですが、個人的には label が良いかなという気はします。
- title のほうがパッとわかりやすいかもしれない
- 検索クエリは label のほうがシンプル (例えば
label:skip-ci
)
jobs:
foo:
if: |
!contains(github.event.issue.labels.*.name, 'skip-ci')
PoC