GitHub Actionsを使用してDependabotが作成したPRをデフォルト以外のブランチに自動マージする
まとめ
背景
- DependabotのPRは滞留しがちなので条件を付けて自動マージしたい
- でも運用を考えるとmain(デフォルトブランチ)にはマージしたくない
方法
- GitHub Actionsで「Dependabotが作成したPRを自動マージする」前に「マージ先の変更」を挟む
- Dependabotのtarget-branchは使用しない
- security updatesに対して設定を適用できないため
Depentabotとは
GitHubが提供しているbotです。
リポジトリ内で使用されているパッケージのバージョンアップデートや脆弱性を監視し、通知やPRの作成を行ってくれます。
Dependabotを使用することで、パッケージの依存関係を最新の状態に保つことができます。
Dependabotの導入手順
リポジトリTOPから
- タブ内「Insights」を押下
- サイドバー内「Dependency graph」を押下
- タブ内から「Dependabot」を押下
- 「Enable Dependabot」を押下
- ローカルリポジトリで
.github/dependabot.yml
を作成してマージ
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: weekly
time: '10:00'
timezone: Asia/Tokyo
open-pull-requests-limit: 10
- package-ecosystem: 'npm'
directory: '/'
schedule:
interval: daily
time: '10:00'
timezone: Asia/Tokyo
open-pull-requests-limit: 10
Insights > Dependencygraph > Dependabot内の表示が「Checking now」になれば完了です。
Dependabot導入によって発生する課題:PRの滞留
DependabotがPRを作成してくれても、開発に追われているとどうしても対応優先度は下がってしまいます。
その一方でPRは作成され続けるので、結果としてDependabotのPRは溜まっていくことになります。
GitHub ActionsによるPRの自動マージ
パッケージの更新に終わりがない以上マンパワーでの解決には限界があるので、ある程度は仕組みでカバーしてあげる必要があると思いました。
そこで解決方法について調査を行ったところ、下記の記事を発見しました。
記事中では、DependabotがSemantic Versioningに基づいて依存関係のバージョニングを行っていることに着目し、PRが
- PATCH version(後方互換性のあるバグフィックス)
- MINOR version(後方互換を保った機能追加)(※)
のどちらかに該当した場合GitHub Acrionsを使用してPRの自動マージを行うことで、Dependabot運用の効率化を図っています(+CIがpass)。
ただし、MINOR versionに関しては定義こそ「後方互換を保った機能追加」ではあるものの、ライブラリによっては破壊的変更が含まれている可能性もあるということで、devDependenciesなライブラリのみ自動マージしているとのことです。
Semantic Versioningに基づいた判定ということで意思決定の基準が明確であり、またMINOR versionの取り扱いについても私自身がこれまでDependabotを運用してきた中で得た感覚と近かったので、この基準で自動マージを行うことにします。
自動マージ設定手順
リポジトリTOPから
- タブ内「Setting」を押下
- 「Pull Requests」内の「Allow auto-merge」にチェック
-
.github/workflows/dependabot_auto_merge.yaml
を作成してマージ
name: Dependabot auto-merge
on:
pull_request:
types:
- opened
permissions:
pull-requests: write
contents: write
repository-projects: write
jobs:
dependabot-automation:
runs-on: ubuntu-latest
if: ${{ github.actor == 'dependabot[bot]' }}
timeout-minutes: 13
steps:
- name: Dependabot metadata
id: metadata
uses: dependabot/fetch-metadata@v1.4.0
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
- name: Approve & enable auto-merge for Dependabot PR
if: |
steps.metadata.outputs.update-type == 'version-update:semver-patch' ||
(steps.metadata.outputs.update-type == 'version-update:semver-minor' && steps.metadata.outputs.dependency-type == 'direct:development')
run: |
gh pr review --approve "$PR_URL"
gh pr edit "$PR_URL" -t "(auto merged) $PR_TITLE"
gh pr merge --auto --merge "$PR_URL"
env:
PR_URL: ${{ github.event.pull_request.html_url }}
PR_TITLE: ${{ github.event.pull_request.title }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GitHub Actionsを使用したPRの自動マージ手順については、GitHub公式でも公開されています。
この状態でPATCH versionの自動マージを試してみたところ、下記のエラーで失敗しました。
failed to create review: GraphQL: GitHub Actions is not permitted to approve pull requests. (addPullRequestReview)
これは、GitHub ActionsでのPR操作権限がオフになっていることが原因です。
下記の記事が参考になりました。
設定後、PATCH versionのPRで自動マージされることを確認しました。
Dependabotのtarget-branchを使用してマージ先ブランチの変更を試みる
自動マージの設定自体は以上で完了ですが、このままだとPRはmain(デフォルトブランチ)にマージされることになります。
一方で、Gitの運用ルールによってはmainへの直接マージは避けたかったり、PATCH versionの更新とはいえリリースする際は別ブランチで動作確認したいことも多いと思います。
ということで、次にDepndabotが作成するPRの自動マージ先をデフォルトブランチ以外に変更することについて考えてみます。
これは、Dependabotのtarget-branch
と関係がありそうです。
target-branch
デフォルトでは、Dependabot はデフォルトのブランチでマニフェストファイルをチェックし、このブランチに対するバージョン更新のプルリクエストを発行します。マニフェスト ファイルと pull request に別のブランチを指定するには、target-branch を使います。このオプションを使用すると、このパッケージマネージャーの設定は、セキュリティアップデートのために発行されたプルリクエストに影響しなくなります。
https://docs.github.com/ja/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file#target-branch
target-branch
はdapentabot.yml
のoptionの一つであり、設定することでDependabotがPRを作成するブランチを変更することができます。
また、
マニフェスト ファイルと pull request に別のブランチを指定するには、target-branch を
使います。
と書いてある通り、target-branch
を設定するとDependabotの監視先もtarget-branch
に変更されます。
target-branchを設定することで、Dependabotの監視先が変更されることの検証
- 新しいブランチ
dependabot-auto-merge
を作成し、デフォルトブランチとする -
dependabot.yml
を下記のように変更する
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: weekly
time: '10:00'
timezone: Asia/Tokyo
open-pull-requests-limit: 10
+ target-branch: dependabot-auto-merge # こちらの変更は今回は関係ない
- package-ecosystem: 'npm'
directory: '/'
schedule:
interval: daily
time: '10:00'
timezone: Asia/Tokyo
open-pull-requests-limit: 10
+ target-branch: dependabot-auto-merge
- main(旧デフォルトブランチ)とdependabot-auto-merge(新デフォルトブランチ)の両方に対して、脆弱性対応ではないバージョン更新を持つ異なるライブラリをインストールする(意図的に少し前のバージョンをインストールする)
上記の手順で検証したところ、dependabot-auto-merge(新デフォルトブランチ)にインストールしたライブラリのみPRが作成されました。
security updatesはtarget-branchを含むdependabot.ymlの設定を参照しない
一方で、気になるのは
このオプションを使用すると、このパッケージマネージャーの設定は、セキュリティアップデートのために発行されたプルリクエストに影響しなくなります。
https://docs.github.com/ja/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file#target-branch
の一文です。これだけだとよくわからなかったので、もう少し調べてみます。
注: これらの構成オプションの一部は、脆弱性のあるパッケージ マニフェストのセキュリティ更新のために送信される pull request にも影響を与える可能性があります。
脆弱性のあるパッケージマニフェストのセキュリティアップデートは、デフォルトブランチでのみ発生します。 構成オプションが同じブランチに設定されていて (target-branch を使っていないかぎり該当します)、脆弱性のあるマニフェストの package-ecosystem と directory を指定している場合、セキュリティ更新の pull request で関連オプションが使われます。
一般に、セキュリティアップデートでは、メタデータの追加や動作の変更など、プルリクエストに影響する設定オプションが使用されます。 セキュリティ更新プログラムについて詳しくは、「Configuring Dependabot security updates (Dependabot セキュリティ アップデートの構成)」をご覧ください。
https://docs.github.com/ja/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file#configuration-options-for-the-dependabotyml-file
dependabot.yml ファイルをカスタマイズしていると、セキュリティ アップデートに対して発行された pull request の変更点に気づくかもしれません。 これらのプルリクエストは、Dependabot スケジュールではなく、常に依存関係のセキュリティアドバイザリによってトリガーされます。 ただし、バージョン アップデートに別のターゲット ブランチを指定していなければ、関連する構成設定は dependabot.yml ファイルから継承されます。
https://docs.github.com/ja/code-security/dependabot/dependabot-version-updates/customizing-dependency-updates
依然としてよくわかっていませんが、security updatesはtarget-branchが設定されていない場合のみdependabot.yml
のオプションを使用する、ということでしょうか...。
Dependabotは
- Dependabot alerts: 脆弱性が検知されたとき、通知を行う
- Dependabot security updates: 脆弱性が検知されたとき、PRを作成する
- Dependabot version updates: バージョンの更新をチェックし、PRを作成する
の3つの機能で構成されているのですが、「taget-branchを設定するとsecurity updatesとversion updatesのふるまいに差異が生じるようになる(あるいは、差異が顕著になる)」と読み換えることもできそうです。
検証してみます。
target-branch設定時のふるまいの検証
-
yml:dependabot.yml
を下記のように修正する
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: weekly
time: '10:00'
timezone: Asia/Tokyo
open-pull-requests-limit: 10
- package-ecosystem: 'npm'
directory: '/'
schedule:
interval: daily
time: '10:00'
timezone: Asia/Tokyo
open-pull-requests-limit: 10
+ commit-message:
+ prefix: 'npm prod'
+ prefix-development: 'npm dev'
+ include: 'scope'
- デフォルトブランチに対して、「脆弱性のあるパッケージ」と「脆弱性のないパッケージ」の両方をインストールする
- 想定通りであれば、security updatesによって作成されたPRにはprefixが付与されないはず
以下が結果です。
想定通り、security updatesによって作成されたPR(luxon v3.0.0 to v3.2.1)にはprefixが付与されていません。version updatesによってもPRが作成されている(luxon v3.0.0 to v3.3.0)のでわかりやすいです。
GitHub Actionsを使用してマージ先を変更する
target-branch
によるマージ先の変更はversion updatesに対しては所望の結果を得られるものの、security updatesに対しては機能せず、デフォルトブランチにマージされてしまいます。
「脆弱性のあるパッケージはmainにマージされる」と考えると聞こえは良いですが、Dependabotのマージ先を変えたいと考えている時点で脆弱性があろうとなかろうと別ブランチにマージしたい場合の方が多いと思います。
そこで、Dependabotのtarget-branch
は使用せず、GitHub Actionsの方でマージ先を変更することを考えてみます。この方法であれば、version updates/security updates関係なくdependabotが作成したPRすべてに対して適用されるはずです。
name: Dependabot auto-merge
on:
pull_request:
types:
- opened
permissions:
pull-requests: write
contents: write
repository-projects: write
jobs:
dependabot-automation:
runs-on: ubuntu-latest
if: ${{ github.actor == 'dependabot[bot]' }}
timeout-minutes: 13
steps:
- name: Dependabot metadata
id: metadata
uses: dependabot/fetch-metadata@v1.4.0
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
+ - name: Change merge branch for Dependabot PR
+ run: |
+ gh pr edit "$PR_URL" --base 'dependabot-auto-merge' #マージ先の変更
+ env:
+ PR_URL: ${{ github.event.pull_request.html_url }}
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Approve & enable auto-merge for Dependabot PR
if: |
steps.metadata.outputs.update-type == 'version-update:semver-patch' ||
(steps.metadata.outputs.update-type == 'version-update:semver-minor' && steps.metadata.outputs.dependency-type == 'direct:development')
run: |
gh pr review --approve "$PR_URL"
gh pr edit "$PR_URL" -t "(auto merged) $PR_TITLE"
gh pr merge --auto --merge "$PR_URL"
env:
PR_URL: ${{ github.event.pull_request.html_url }}
PR_TITLE: ${{ github.event.pull_request.title }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
この状態で検証したところ、所望の結果を得ることができました。
dependabot.yml
のtarget-branch
の設定は行っていません(Dependabotはデフォルトブランチを見てPRを作成します)。
わからなかったこと
ドキュメントを読む限りだと、security updatesに対してもtarget-branchを使用してマージ先を適用できるようにも見えます...(target-branchの項目に対して×がついていないため)。
とはいえ、上でも引用した通り
脆弱性のあるパッケージマニフェストのセキュリティアップデートは、デフォルトブランチでのみ発生します。 構成オプションが同じブランチに設定されていて (target-branch を使っていないかぎり該当します)、脆弱性のあるマニフェストの package-ecosystem と directory を指定している場合、セキュリティ更新の pull request で関連オプションが使われます。
とも書かれているので、やはりできないのかもしれません。今後十分に理解できた場合は更新します。
おわりに
target-branch
周りで苦しみましたが、ひとまずやりたかったことは実現できたので良かったです。
本文中の内容や検証結果に誤りがある場合は、コメントにてご指摘いただけると本当に助かります。
Discussion