概要
- ライブラリのアップデートに追従するのにDependabotは非常に便利
- Dependabotのプルリクは設定の仕方によって集約することが出来る
Dependabotとは
- プロジェクトの依存関係が持つ脆弱性の特定・報告をしてくれる
- 依存関係を持つライブラリのバージョンアップデートをしてくれる
導入方法
問題点
デフォルトブランチに対するプルリクが作られた際にCIを行うGitHub Actionsのジョブを実装するのは多々ある。
Dependabotをデフォルトの設定のまま導入すると、プルリクのターゲットブランチがデフォルトブランチとなるため、Dependabotがプルリクを作成した時点で実装したGitHub Actionsのジョブが走ることになる。
Dependabotは基本的に1ライブラリごとにバージョンアップデートのプルリクを作ってくるので、仮にDependabotが10個のライブラリのバージョンアップデートをしようとすると10本のプルリクが作られる。
そうするとプルリクが作られた時点で10回はGitHub Actionsのジョブが実行されることになる。
しかもDependabotは賢いのでデフォルトブランチが更新された場合は自動的にリベースを試みるため、リベースが実行されると再度GitHub Actionsのジョブが走ることになる。
つまり10本のプルリクが作成された場合、トータルで走るGitHub Actionsのジョブは10 + 9 + 8 + 7 + 6 + 5 + 4 + 3 + 2 + 1 = 55回となり、1回あたりのジョブの実行時間が5分だとすると55 × 5 = 275分になる。
GitHub Actionsの実行時間には無料枠があり、プランによって上限は異なるがGitHub Freeの場合は2000分/月である。
先程の例でいけば1回のDependabotのバージョンアップデートの実行でひと月の15%近くを消費してしまう。
JavaScript界隈のライブラリなんかは数も多く、更新頻度もそれなりに高いので何もしていないとDependabotのプルリクだけで無料枠をほぼ使い切ってしまう、なんてことも起きてくる。
本題
ブランチ戦略を立てる
とは言いつつもDependabotの自動バージョンアップデートの恩恵は受けたい、でもライブラリのバージョンアップデートをしたことでデフォルトブランチが壊れる(動かなくなる)ようなことは避けたい。それではどうするか?
そう、答えは「Dependabotのプルリクをデフォルトブランチではないブランチに集約する」である。
つまり以下のようにする。
- Dependabotが作るプルリクのターゲットブランチは集約用のブランチに向ける
- 集約用のブランチに対するプルリクではGitHub Actionsのジョブを走らせないようにする
- 集約用のブランチからデフォルトブランチに対するプルリクではGitHub Actionsのジョブを走らせる
こうすることでDependabotの恩恵は受けつつも、デフォルトブランチに取り込む際には事前にCIを挟むことでデフォルトブランチを保護することが出来る。
Dependabotの設定
3つのモジュールを持つリポジトリを例にする。
dependabot-sample
├── .github
│ ├── dependabot.yml
│ └── workflows
│ ├── create-dependabot-branch.yml
│ ├── on-pull-request-moduleA.yml
│ ├── on-pull-request-moduleB.yml
│ └── on-pull-request-moduleC.yml
├── moduleA
│ ├── package.json
│ └── yarn.lock
├── moduleB
│ ├── package.json
│ └── yarn.lock
└── moduleC
├── package.json
└── yarn.lock
Dependabotの設定( .github/dependabot.yml
)を以下のように設定する。
version: 2
updates:
- package-ecosystem: npm
directory: /moduleA
schedule:
internal: weekly
day: 'mondy'
time: '09:00'
timezone: 'Asia/Tokyo'
open-pull-requests-limit: 10
commit-message:
prefix: chore
prefix-development: chore
include: scope
target-branch: dependencies/moduleA
- package-ecosystem: npm
directory: /moduleB
schedule:
internal: weekly
day: 'mondy'
time: '09:00'
timezone: 'Asia/Tokyo'
open-pull-requests-limit: 10
commit-message:
prefix: chore
prefix-development: chore
include: scope
target-branch: dependencies/moduleB
- package-ecosystem: npm
directory: /moduleC
schedule:
internal: weekly
day: 'mondy'
time: '09:00'
timezone: 'Asia/Tokyo'
open-pull-requests-limit: 10
commit-message:
prefix: chore
prefix-development: chore
include: scope
target-branch: dependencies/moduleC
ポイントは2つ。
- Dependabotの起動タイミングを毎週月曜9:00に固定する
- ターゲットブランチをデフォルトブランチではなく各モジュールの集約用ブランチに向ける
Dependabot用のGitHub Actionsの実装
Dependabot向けの集約ブランチを自動生成するGitHub Actionsを実装する( .github/workflows/create-dependabot-branch.yml
)。
name: 'Create Branch for Dependabot'
on:
schedule:
# Monday 8:00 at JST
- cron: '0 23 * * SUN'
jobs:
create-branch:
runs-on: ubuntu-latest
timeout-minutes: 3
strategy:
fail-fast: false
matrix:
target-branch:
- 'dependencies/moduleA'
- 'dependencies/moduleB'
- 'dependencies/moduleC'
steps:
- uses: actions/checkout@v3
- name: create branch if not exists
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
shell: bash -x {0}
run: |
git branch -a | grep remotes/origin/${{ matrix.target-branch }}
if [ "$?" -eq 0 ]; then
echo "${{ matrix.target-branch }} branch is exists. will not create branch."
else
set -eo pipefail
git switch -c ${{ matrix.target-branch }}
git push origin ${{ matrix.target-branch }}
fi
ポイントは2つ。
- GitHub Actionsのcronで毎週月曜8:00にジョブを起動する
- Dependabotの起動タイミングの前になるように時間を調整する
- Dependabot向けの集約用ブランチが存在しなければデフォルトブランチからブランチを作成する
CI用のGitHub Actionsの実装
各モジュールのCIを行うGitHub Actionsを実装する。
.github/workflows/on-pull-request-moduleA.yml
name: '[moduleA] CI'
on:
pull_request:
branches-ignore:
- 'dependencies/moduleA'
paths:
- 'moduleA/**'
jobs:
# (省略)CIの実装
.github/workflows/on-pull-request-moduleB.yml
name: '[moduleB] CI'
on:
pull_request:
branches-ignore:
- 'dependencies/moduleB'
paths:
- 'moduleB/**'
jobs:
# (省略)CIの実装
.github/workflows/on-pull-request-moduleC.yml
name: '[moduleC] CI'
on:
pull_request:
branches-ignore:
- 'dependencies/moduleC'
paths:
- 'moduleC/**'
jobs:
# (省略)CIの実装
ポイントは2つ。
branches-ignore
を使い、Dependabotが作成するプルリクはCI対象外とする
paths
を使い、各モジュールのディレクトリ配下のファイル更新を起動トリガーにする
運用方法
- 毎週月曜にデフォルトブランチからDependabot用の集約ブランチを自動的に作成し、集約ブランチに対して各モジュールの依存ライブラリのバージョンアップデートを取り込んでいく
- 各集約ブランチへのバージョンアップデートの取込が完了したら、集約ブランチからデフォルトブランチへのプルリクを作成する
- 集約ブランチに対するCIが通ったらデフォルトブランチへのマージを行う