TL;DR
dependabotのPRによって実行されるジョブでキャッシュを作らせたくなければ、キャッシュするstepでこれを書く。
if: ${{ !startsWith(github.head_ref, 'dependabot') }}
説明
起こりうる問題
GitHub Actionsでテストなどを回すときに、ライブラリを都度インストールせずに済むよう、pathを指定してキャッシュすることがあると思います。
以下は Gemfile.lock
が同一である場合にキャッシュを利用する ci.yml
の例です。
on:
pull_request:
branches: [develop]
jobs:
rspec:
# ...
steps:
- name: Cache bundle gems
uses: actions/cache@v3
with:
path: vendor/bundle
key: ${{ runner.os }}-gem-${{ hashFiles('**/Gemfile.lock') }}
これは通常のPRでは問題ないのですが、dependabotを使っているときに面倒になります。
dependabotはライブラリ更新のPRを出します。ということは、この例においては Gemfile.lock
も更新するので、キャッシュを新しく取り直します。
このキャッシュ作成がPRのたびに発生するため、キャッシュ容量次第ではGitHub Actionsのストレージが大幅に食われます。
仮に、以下のようなタイムラインでPRが作成されていくとします。無料ストレージ容量は1GBで、それを超えたときの課金は未設定、ライブラリキャッシュの容量は300MBとしましょう。
- 2023/03/13 12:00 開発者がPR#1001を作成
- 2023/03/13 15:00 dependabotがPR#1002、PR#1003、PR#1004を同時に作成
- 2023/03/13 18:00 以上のPRがマージされないまま、開発者がPR#1005を作成
このとき、PR#1005によって実行されるジョブでは、ライブラリキャッシュを利用できません。
累積のキャッシュ容量が無料ストレージ容量を超え、かつ自動課金によるスケールが未設定の場合、古いキャッシュから削除されていきます。
If you exceed the limit, GitHub will save the new cache but will begin evicting caches until the total size is less than the repository limit.
https://docs.github.com/en/actions/using-workflows/caching-dependencies-to-speed-up-workflows#usage-limits-and-eviction-policyより
以上の例の場合、PR#1004が作成された時点でキャッシュ容量が1.2GBに到達するので、一番古いPR#1001のキャッシュが消え、PR#1005では利用できません。
以下を見ると分かる通り、GitHub Actionsの使用について提供される無料ストレージ容量は(GitHub Enterprise Cloudを除いて)〜2GBですので、意外と起こりうる話かと思います。(2022/03/16現在)
dependabotとキャッシュの関係
また、そもそもdependabotに、 Gemfile.lock
を変更トリガとした環境でインストールするgemのキャッシュを作らせる必要性が薄いと思います。
例えば、dependabotが「ライブラリAの更新PR」「ライブラリBの更新PR」「ライブラリCの更新PR」を同時に出してきたとして、それぞれに対応するキャッシュが再利用されることはほとんどないはずです。
PR作成時およびdevelopブランチへのマージ時にCIが回るようになっている場合、developブランチへのマージ時にはキャッシュを利用できます。それでも1回しか使われず、他のライブラリ更新がマージされれば不要になります。
開発者が本当にキャッシュしてほしいのは、基本的には「ライブラリAとBとCが更新された状態のライブラリ一覧」のはずです。
解決策
ということで、dependabot経由のPRにキャッシュを作らせるのを止めます。
dependabotが作成するPRのブランチにはdependabotという共通のプレフィックスがあるため、これを利用します。
jobs.<job_id>.steps[*].if
を使ってstepを飛ばしましょう。
on:
pull_request:
branches: [develop]
jobs:
rspec:
# ...
steps:
- name: Cache bundle gems
if: ${{ !startsWith(github.head_ref, 'dependabot') }} # この1行を足す
uses: actions/cache@v3
with:
path: vendor/bundle
key: ${{ runner.os }}-gem-${{ hashFiles('**/Gemfile.lock') }}
startsWith( searchString, searchValue )
は、 searchString
が searchValue
から始まるかを確認します。
github.head_ref
がPR元のブランチ名なので、これが dependabot
から始まるかを確認しているわけです。
ruby/setup-ruby
を使っている場合は、 bundler-cache: ${{ !startsWith(github.head_ref, 'dependabot') }}
に設定すればいいと思います。
やること自体は簡単なんですが、ググってみてもdependabot経由のPRにキャッシュさせないという話をあまり見かけなかったので記事にしました。
GitHubでは gh-actions-cache
を使う方法を紹介していますが、今回の例に限ってはdependabot経由のPRにキャッシュを作らせないほうが簡単な気がします。
世間ではどうやって対処してるんでしょうか。よかったら教えて下さい。