⚙️

GitHub Actionsを使用してDependabotが作成したPRをデフォルト以外のブランチに自動マージする

2023/05/18に公開

まとめ

背景

  • DependabotのPRは滞留しがちなので条件を付けて自動マージしたい
  • でも運用を考えるとmain(デフォルトブランチ)にはマージしたくない

方法

  • GitHub Actionsで「Dependabotが作成したPRを自動マージする」前に「マージ先の変更」を挟む
  • Dependabotのtarget-branchは使用しない
    • security updatesに対して設定を適用できないため

Depentabotとは

https://docs.github.com/ja/code-security/dependabot

GitHubが提供しているbotです。

リポジトリ内で使用されているパッケージのバージョンアップデートや脆弱性を監視し、通知やPRの作成を行ってくれます。

Dependabotを使用することで、パッケージの依存関係を最新の状態に保つことができます。

Dependabotの導入手順

リポジトリTOPから

  1. タブ内「Insights」を押下
  2. サイドバー内「Dependency graph」を押下
  3. タブ内から「Dependabot」を押下
  4. 「Enable Dependabot」を押下
  5. ローカルリポジトリで.github/dependabot.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

Insights > Dependencygraph > Dependabot内の表示が「Checking now」になれば完了です。

Dependabot導入によって発生する課題:PRの滞留

DependabotがPRを作成してくれても、開発に追われているとどうしても対応優先度は下がってしまいます。

その一方でPRは作成され続けるので、結果としてDependabotのPRは溜まっていくことになります。

GitHub ActionsによるPRの自動マージ

パッケージの更新に終わりがない以上マンパワーでの解決には限界があるので、ある程度は仕組みでカバーしてあげる必要があると思いました。

そこで解決方法について調査を行ったところ、下記の記事を発見しました。
https://moneyforward-dev.jp/entry/2022/12/16/dependabot-automation/

記事中では、DependabotがSemantic Versioningに基づいて依存関係のバージョニングを行っていることに着目し、PRが

  • PATCH version(後方互換性のあるバグフィックス)
  • MINOR version(後方互換を保った機能追加)(※)

のどちらかに該当した場合GitHub Acrionsを使用してPRの自動マージを行うことで、Dependabot運用の効率化を図っています(+CIがpass)。

ただし、MINOR versionに関しては定義こそ「後方互換を保った機能追加」ではあるものの、ライブラリによっては破壊的変更が含まれている可能性もあるということで、devDependenciesなライブラリのみ自動マージしているとのことです。

Semantic Versioningに基づいた判定ということで意思決定の基準が明確であり、またMINOR versionの取り扱いについても私自身がこれまでDependabotを運用してきた中で得た感覚と近かったので、この基準で自動マージを行うことにします。

自動マージ設定手順

リポジトリTOPから

  1. タブ内「Setting」を押下
  2. 「Pull Requests」内の「Allow auto-merge」にチェック
  1. .github/workflows/dependabot_auto_merge.yamlを作成してマージ
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公式でも公開されています。
https://docs.github.com/en/code-security/dependabot/working-with-dependabot/automating-dependabot-with-github-actions#enable-auto-merge-on-a-pull-request

この状態でPATCH versionの自動マージを試してみたところ、下記のエラーで失敗しました。

failed to create review: GraphQL: GitHub Actions is not permitted to approve pull requests. (addPullRequestReview)

これは、GitHub ActionsでのPR操作権限がオフになっていることが原因です。
下記の記事が参考になりました。
https://zenn.dev/kenghaya/articles/d7f766e5db6437

設定後、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-branchdapentabot.ymlのoptionの一つであり、設定することでDependabotがPRを作成するブランチを変更することができます。

また、

マニフェスト ファイルと pull request に別のブランチを指定するには、target-branch を
使います。

と書いてある通り、target-branchを設定するとDependabotの監視先もtarget-branchに変更されます。

target-branchを設定することで、Dependabotの監視先が変更されることの検証

  • 新しいブランチdependabot-auto-mergeを作成し、デフォルトブランチとする
  • dependabot.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
+   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を下記のように修正する
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すべてに対して適用されるはずです。

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: 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.ymltarget-branchの設定は行っていません(Dependabotはデフォルトブランチを見てPRを作成します)。

わからなかったこと

https://docs.github.com/ja/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file#configuration-options-for-the-dependabotyml-file

ドキュメントを読む限りだと、security updatesに対してもtarget-branchを使用してマージ先を適用できるようにも見えます...(target-branchの項目に対して×がついていないため)。

とはいえ、上でも引用した通り

脆弱性のあるパッケージマニフェストのセキュリティアップデートは、デフォルトブランチでのみ発生します。 構成オプションが同じブランチに設定されていて (target-branch を使っていないかぎり該当します)、脆弱性のあるマニフェストの package-ecosystem と directory を指定している場合、セキュリティ更新の pull request で関連オプションが使われます。

とも書かれているので、やはりできないのかもしれません。今後十分に理解できた場合は更新します。

おわりに

target-branch周りで苦しみましたが、ひとまずやりたかったことは実現できたので良かったです。

本文中の内容や検証結果に誤りがある場合は、コメントにてご指摘いただけると本当に助かります。

Discussion