GitHub Dependabotが自動作成してくれるPRの中で、パッチバージョンの更新だけAutoMergeする

2021.09.13

吉川@広島です。

先日、

GitHubにDependabotを導入して依存ライブラリを自動アップデートする | DevelopersIO

という記事を書きました。さらに掲題のことを実現するにはどうすれば良いのかなーと思っていたところ、ちょうどはてなブックマークで下のドキュメントが浮上していて解決できましたので、その方法を紹介したいと思います。

[B! github] Automating Dependabot with GitHub Actions - GitHub Docs

Automating Dependabot with GitHub Actions - GitHub Docs

TL;DR

  • GitHub DependabotでPRを自動作成する
  • GitHub ActionsでPRを自動マージする

という方法で実現できました。

Dependabot設定ファイル

# .github/dependabot.yml

version: 2
updates:
  - package-ecosystem: 'npm'
    directory: '/'
    schedule:
      interval: 'daily'

GitHub Actions設定ファイル

# .github/workflows/dependabot-auto-merge.yml

name: Dependabot auto-merge
on: pull_request_target

permissions:
  pull-requests: write
  contents: write

jobs:
  dependabot:
    runs-on: ubuntu-latest
    if: ${{ github.actor == 'dependabot[bot]' }}
    steps:
      - name: Dependabot metadata
        id: metadata
        uses: dependabot/fetch-metadata@v1.1.1
        with:
          github-token: '${{ secrets.GITHUB_TOKEN }}'
      - name: Enable auto-merge for Dependabot PRs
        if: ${{ steps.metadata.outputs.update-type == 'version-update:semver-patch' }}
        run: gh pr merge --auto --merge "$PR_URL"
        env:
          PR_URL: ${{github.event.pull_request.html_url}}
          GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}

やってみた

依存パッケージがいい感じに古くなっているサンプルとして、「何か思いついて作りかけたけど飽きて途中で放置している個人リポジトリ」を対象に試してみました(皆さんもあると思います・・・)。

マイナーバージョン以上の更新はPR自動作成に留まっています。

パッチバージョンの更新はPR作成+自動マージしてくれています。

詳細を開くと、このようにGitHub Actionsによってマージされたことが確認できました。

なぜパッチバージョンだけ自動マージしたいのか

多くのパッケージがsemverに沿ったバージョニングを行っています。

セマンティック バージョニング 2.0.0 | Semantic Versioning

1.APIの変更に互換性のない場合はメジャーバージョンを、
2.後方互換性があり機能性を追加した場合はマイナーバージョンを、
3.後方互換性を伴うバグ修正をした場合はパッチバージョンを上げます。

{メジャー}.{マイナー}.{パッチ} ということですね。

で、Dependabotが自動で作成してくれるアップデートPRをどのように管理していくかですが、品質面での理想を追求するならば、

  • すべてのアップデートについて、開発者が確認した上で手動マージする

というフローを採ることになりそうです。

ただ、そのプロジェクトの性質や依存パッケージの数、開発者のリソースにもよりますが、あまり完璧主義な方針を採ってしまうと、「すべてのチェックにかかる工数が大きくなりすぎて、プロジェクトの他の作業の進行にしわ寄せがいってしまって辛い」となる可能性もあり、その結果継続的なアップデートの取り組みが放置されてしまう、といったケースもそれなりにあるようです。

そこで、

  • プロジェクトにとって無理がない
  • 一定以上安全といえる

を両立できる落とし所を模索する必要があり、その一つとして

  • メジャーバージョン・マイナーバージョンのアップデートについては開発者が確認した上で手動マージする
  • パッチバージョンのアップデートは自動マージする

という方針がそれなりによく採られているのではないかと思います。

GitHub Actionsで何をやっているのか

自分なりに調べた結果、おそらくこういうことだろうと理解できた範囲で共有していきます。

github.actor

    if: ${{ github.actor == 'dependabot[bot]' }}

Context and expression syntax for GitHub Actions - GitHub Docs

The login of the user that initiated the workflow run.

  • workflowを起動させたユーザがDependabotかどうか

を判定するための条件と考えておけば良さそうです。

dependabot/fetch-metadata

        uses: dependabot/fetch-metadata@v1.1.1

dependabot/fetch-metadata: Extract information about the dependencies being updated by a Dependabot-generated PR.

Extract information about the dependencies being updated by a Dependabot-generated PR.

  • Dependabotが作成したPRから依存関係の情報を抽出する

という役割を果たしているようです。

具体的には、 steps.metadata.outputs というグローバルオブジェクト(と呼べば良いのか・・・?)に情報をセットしてくれるので、後述する「パッチバージョンのアップデートかどうか」といった条件分岐のためにこの情報を取り出して利用するということになります。

どうやってそんなことしてるんだろう・・・と気になったのでコードを確認したところ、 @actions/core パッケージの setOutput という関数を利用していました。

fetch-metadata/output.ts at main · dependabot/fetch-metadata

steps.metadata.outputs.update-type

        if: ${{ steps.metadata.outputs.update-type == 'version-update:semver-patch' }}

dependabot/fetch-metadataでセットした情報を取得して、

  • パッチバージョンのアップデートかどうか

を判定しています。

GITHUB_TOKEN

          github-token: '${{ secrets.GITHUB_TOKEN }}'

GitHub Secretsに自分で何かセットする必要があるのかな?と思いますが、それはしなくて良いようです。

ワークフローで認証する - GitHub Docs

GitHubは、ワークフローで利用するGITHUB_TOKENシークレットを自動的に生成します。 このGITHUB_TOKENは、ワークフローの実行内での認証に利用できます。

このように自動生成されるトークンを取得して利用しているので、ただ上の記述をするだけで良く、その他に自分で何かセットしたりすることは必要ありません。

さらに色々やりたい

さらにフローをカスタムしたいという場合も、GitHub Actions上という性質を活かしていくらでも応用ができそうです。

例えば、

      - name: Enable auto-merge for Dependabot PRs
        if: ${{ steps.metadata.outputs.update-type == 'version-update:semver-patch' }}
        run: |
+         npm run test # jestによる自動テストを走らせる
          gh pr merge --auto --merge "$PR_URL"
        env:
          PR_URL: ${{github.event.pull_request.html_url}}
          GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}

のようにすることで「自動テストがPASSした場合のみマージする」みたいなことも実現できると思います。

参考