🐧

自分が管理する全 OSS の Issue や Pull Request を 1 つの GitHub Project に集約

2024/07/23に公開

タイトルの通り、自分が管理する全 OSS の Issue や Pull Request (以下 PR) を 1 つの GitHub Project に集約した話を紹介します。
自分は様々な OSS をメンテしており、様々なリポジトリで作られる GitHub Issues や PR を日々ハンドリングする必要があります。
しかしこれだけリポジトリの数が増えると一つ一つリポジトリを巡回してハンドリングしていくのは困難です。
ユーザーによって issue や PR が作成・更新されるとメール通知で気付けるようにはなっていますが、色々な issue や PR に対応しているとつい存在を忘れてしまうこともあります。
また Renovate の PR がマージされずに放置されたりもしていました。

そこで全ての Issue や PR を一つの GitHub Project に追加し、一元的に管理することにしました。

GitHub Project に Issue や PR を追加する方法

Project で管理するには Issue や PR を Project に追加する必要があり、これを自動化する必要があります。

幾つか方法があります。

  1. GitHub Project 公式の機能
  2. Issue や PR の create event を契機に GitHub Actions を実行
  3. Issue や PR を検索し条件にマッチしたものを Project に追加する workflow を GitHub Actions で定期実行 <= 今回採用したのがこれ

1. GitHub Project 公式の機能

GitHub Project 公式の機能で追加を自動化出来ますが、この機能には幾つか問題があります。

https://docs.github.com/en/issues/planning-and-tracking-with-projects/automating-your-project/using-the-built-in-automations

  1. リポジトリ毎に workflow を追加しないといけない

  1. 追加できる workflow の数には上限がある

2. Issue や PR の create event を契機に GitHub Actions を実行

そこで GitHub Actions によって自動化します。

https://docs.github.com/ja/issues/planning-and-tracking-with-projects/automating-your-project/automating-projects-using-actions

Project に追加するための公式の Action もあります。

https://github.com/marketplace/actions/add-to-github-projects

https://dev.classmethod.jp/articles/github-actions-auto-register-issues-to-projects/

https://engineering.nifty.co.jp/blog/23504

しかし今回はこれは使いません。

この方法だと、対象となる全てのリポジトリに workflow を追加する必要があり、メンテが大変だからです。

3. Issue や PR を検索し条件にマッチしたものを Project に追加する workflow を GitHub Actions で定期実行

今回は Issue, PR を API で検索し Project に追加する job を定期実行するようにします。
こうすると 1 つの workflow を管理するだけで済むようになります。

このための CLI ツールを Go で実装しました。

https://github.com/suzuki-shunsuke/ghproj

このツールはかなり粗削りで改善の余地がありますが、まぁ自分のやりたいことは実現できてしまいましたし、他にも色々やることはあるので一旦良しとしています。

ghproj init # 設定ファイルの作成
ghproj add # Issue, Project を検索し Project に追加
ghproj.yaml
entries:
    # query: Project に追加する Issue, PR を検索する GraphQL API の Query
  - query: |
      is:open
      -project:suzuki-shunsuke/5
      archived:false
      owner:suzuki-shunsuke
      owner:aquaproj
      owner:lintnet
      is:public
    # expr: 検索結果をフィルタリングする評価式
    # expr-lang/expr を使っている
    # 検索結果の各 item ごとに評価される
    # 結果は boolean でなければならない
    # false ならばその item は対象から除外される
    # expr はオプションで、未設定ならば全ての検索結果が対象になる
    expr: |
      (! Item.Repo.IsFork) &&
      (Item.Title != "Dependency Dashboard")
    # project_id: Issue, PR が追加される project の ID
    # project id は GitHub CLI の gh project list で取得可能
    project_id: PVT_kwHOAMtMJ84AQCf4

ghproj は設定ファイルを読み、 GitHub の GraphQL API で Issue, PR を検索します。
条件に応じて一部の Issue, PR を除外し、まだ対象 Project に追加されていない Issue, PR を追加します。

GitHub Access Token

Project に Issue, PR を追加するには GitHub Access Token が必要です。
GitHub Access Token には幾つか種類があります。

  • GitHub Actions token: 検証してないけど、多分 Project への追加には使えないはず
  • GitHub App: User の Project を扱う権限を付与できない
  • OAuth App (GitHub CLI)
  • Personal Access Token
    • classic PAT: 権限や scope を柔軟に制限できず、他の token と比べるとセキュアではない
    • fine-grained PAT: Project をサポートしていない

GitHub App は User の Project を扱う権限を付与できません。

https://dev.classmethod.jp/articles/github-actions-auto-register-issues-to-projects/

また、Project (Beta) のアクセス権限が Organization permission 扱いになっており、GitHub App から個人の Project (Beta) を操作する方法が今のところないため、個人の場合は Personal Access Token (PAT) を使用する方法をお試しください。

fine-grained PAT は Project をサポートしていません。

https://github.com/orgs/community/discussions/36441

There are also some APIs that do not yet support the fine-grained permission model, that we'll be adding support for in time:

  • Packages
  • Projects
  • Notifications

そこで 2 つ選択肢があります。

  1. GitHub App を使って GitHub Organizations (以下 Org) の Project に issue や PR を追加する <= 推奨
  2. classic PAT を使って GitHub User の Project に issue や PR を追加する

Project が Org に属するか User に属するかによって変わります。
GitHub App を使うほうが classic PAT よりセキュアなので個人であっても Org を作って Org の Project で管理するほうが良いと思います。

1. User の Project の場合

上述の通り GitHub Actions token や GitHub App, fine-grained PAT は使えないので classic PAT を使います。
PAT の有効期限は設定したほうが良いでしょう。
scope は read:org, project で十分です。

2. Org の Project の場合

GitHub Actions で tibdex/github-app-tokenactions/create-github-app-token を使って installation access token を生成します。

まず GitHub App を作ります。

以下の Permissions を付与します。

  • Repository permissions: metadata: read-only
  • Organization permissions: Projects: Read and write

そして GitHub Actions を実行する Repository に GitHub App をインストールします。
なぜ metadata: read-only が必要かというと、なにかしら Repository permissions を付与しないと Repository に App をインストールできず、 上記の action で installation access token を生成できないからです。

Private Repository の Issue や PR を扱いたい場合、 Pull Requests: read-only, Issues: read-only の権限を付与し、対象の Repository に App をインストールする必要があるでしょう。

条件に応じて GitHub Projects の item をアーカイブ

GitHub Projects の item の数には上限があり、上限に引っかかるとそれ以上 item を Project に追加できなくなります。
新たに item を追加するには既存の item を削除するかアーカイブする必要があります。

https://docs.github.com/en/issues/planning-and-tracking-with-projects/automating-your-project/archiving-items-automatically

Archiving items will help you stay below the limit of 1,200 items in each project.

上記の link 先の自動 archive 機能を使うでも良いのですが、場合によってはまとめて archive したくなる場合があります。

例えば ghproj add を実行したところ、最早メンテしていない古い OSS の issue や PR が大量に追加されてしまったため、その Repository を archive しました。
ただし、 Repository を archive しても Project に issue や PR は残るので、 archive した Repository の issue や PR をまとめて archive したくなりました。

そこで ghproj に条件にマッチした item をアーカイブする機能を追加しました。

e.g.

ghproj.yaml
entries:
  - expr: |
      (Item.Open && Item.Repo.IsArchived) ||
      (Item.Title == "Dependency Dashboard")
    action: archive
    project_id: PVT_kwHOAMtMJ84AQCf4

GitHub GraphQL API では issue や PR と違って GitHub Project item を複雑なクエリで検索することが出来ません。
そのため、特定の Project の全 item を API で取得したあと、 expr-lang/expr という Go の Expression Engine を使ってフィルタリングしています。

最早メンテしていないリポジトリの archive

今回作業を進める中で最早メンテしていないリポジトリは手作業で archive しました。
最終コミットの日時で古いものをまとめて archive とかすればよかったかもしれません。

GitHub Actions による自動化

ghproj を GitHub Actions で定期実行 (現状 10 分毎) することで新しく追加された issue や PR を常に Project に追加するようにしました。

https://github.com/szksh-lab/.github/blob/main/.github/workflows/update-project.yaml

workflow を見れば分かる通り、基本は ghproj を実行しているだけです。
ただし、 ghproj が何らかの理由で失敗したときに来づけるように、専用の issue を作成し、失敗したら issue を open してコメントをし、成功したら close されるようにしました。

複数の Project に分けて集約したい場合

今回は 1 つの Project に集約しましたが、場合によっては条件に応じて複数の Project 分けて集約することも可能でしょう。
例えば業務で言えば team ごとに Project を分けて集約したいということもあるでしょう。
その場合でも ghproj は entries の要素ごとに project id を指定できるので、複数の Project に対応可能です。
複数 Project であっても単一 Project の場合と同様に自動化が可能なはずです(試してませんが)。

さいごに

以上、自分が管理する全 OSS の Issue や Pull Request (以下 PR) を 1 つの GitHub Project に集約した話を紹介しました。
以下の Project に集約されています。

https://github.com/orgs/szksh-lab/projects/1

現状とりあえず Project に追加しただけで整理されていないので、これから少しずつ整理していきたいと思います。
まずは人力で Priority を雑に割り当てていくつもりですが、これも特定の条件にマッチした item の Priority を一括で設定するとかできると便利かもしれません。
もっとも人間による判断も必要でしょう。
LLM で Priority を自動判別とか出来たら便利かもしれません。

Discussion