GitHubをチームで利用できるように整える

GitHubをチームで利用できるように整えました。

そのための工程が多く、詰まりポイントがいくつもあったので、記事にしてみることにしました。

## 目的

mainブランチにMergeするためには、Approveを必要としたい

## 知らなかったこと

* レビューが通ってないとmainブランチにMergeできなくするには、Organization機能（有料）を有効にする必要がある
* Organization機能は、チームメンバー一人あたり月4ドルかかる
* Pull request発行者自身は、レビューアとしてカウントされない
* 自動でApproveするためには、パーソナルアクセストークン（PAT）もしくは、GitHub App Tokenが必要

## やろうとしたこと

Pull request発行者自身は、レビューアとしてカウントされないので、他のチームメンバーにApproveしてもらう必要があります。しかし私にはチームメンバーがいないので、工夫する必要がありました。（チームメンバーが増えた時の練習として、今回の実装を行いました）

### 参考にした記事
https://qiita.com/sounisi5011/items/eba7cf3ed65973e6edb2

記事によると、管理者権限を使って強引にMergeすることはお勧めできないとのことでした。私もその意見に共感しました。

代替案は、botにレビューをしてもらうというものでした。

https://github.com/katsuhirodoi2/udemy_terraform_aws_study/blob/main/.github/workflows/pr-auto-approve.yml

このようなワークフローを作成することで、管理者自身がPull requestした場合は、botがApproveしてくれるようになりました。このワークフローの主要な処理については、hmarr/auto-approve-actionというサードパーティーのActionsを使用させてもらっています。

## はまったこと

### Pull request発行者自身はApproveできない

![スクリーンショット 2024-04-07 16.53.48.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/438423/c20d700f-b617-1eec-ec57-d333b2274de8.png)

当たり前のことかもしれませんが、私はGitHubの経験が少なかったのでこの点を意識したことがありませんでした。このエラーがきっかけとなり、かなり遠回りすることになりました。

![スクリーンショット 2024-04-07 16.57.37.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/438423/0dd1b81b-498f-ba6b-432d-17d9b91b863a.png)

GitHub Copilotに相談したところ、

> hmarr/auto-approve-action@v3アクションは、デフォルトで${{ secrets.GITHUB_TOKEN }}を使用しますが、GitHubのセキュリティポリシーにより、このトークンを使用して自身のプルリクエストを承認することはできません。

（この引用文を※1とします。後で使用します。）

という回答とともに、以下のようなymlファイルの書き方の変更を提示されました。

```yml
       - name: Auto approve
         uses: hmarr/auto-approve-action@v3
 +       with:
 +         github-token: "${{ secrets.BOT_GITHUB_TOKEN }}"
```

パーソナルアクセストークンを勧められましたが、Pull requestを上げる人のトークンではなく、別のユーザーのトークンが必要であることは予想できました。別のユーザーということはOrganizationにそのユーザーを追加する必要があり、月額4ドルが追加で必要になることも予想できました。ユーザーを2つ管理するのは煩雑だと考え、別の方法を調査していると以下のような記事を見つけました。

https://dev.classmethod.jp/articles/mapping-secrets-github-token/

その記事から、GitHub App Tokenというものがあると知りました。

さらには、hmarr/auto-approve-action@v3アクションがデフォルトで使用する${{ secrets.GITHUB_TOKEN }}の権限についても書かれていました。

権限の一覧を見ても、その権限名から可能な処理を想像するのは難しかったです。権限（Permissions）を把握するために、試行錯誤をすることになりました。

と、ここまで書いてきましたが、実は権限の試行錯誤をし始める前に、botによる自動Approveは成功していました。パーソナルアクセストークンやGitHub App Tokenは必要なく、hmarr/auto-approve-action@v3アクションがデフォルトで使用する${{ secrets.GITHUB_TOKEN }}の権限でbotによる自動Approveは可能でした。

権限の試行錯誤にハマったのは、以下の件を解決するために権限の試行錯誤をしていたしていたにも関わらず、途中からbotによる自動Approveにも権限が必要だと私が勘違いしたからでした。既に文章が分かりにくくなっている懸念がありますが、ここでいう権限とは、GitHub App Tokenに割り当てる権限のことです。ymlファイルに記載する権限（permissions）のことではありません。

### Pull requestをbotに作成させたい

この実装をすることに最もハマりました。なぜなら、GitHub App Tokenの権限の種類を把握しておらず、さらにはymlファイルに記載する権限（permissions）とGitHub App Tokenの権限には関連性はないと私が思い込んでいたからです。

まず、結論を申し上げると、ymlファイルに記載する権限（permissions）とGitHub App Tokenの権限は強い関係性がありそうでした。

![スクリーンショット 2024-04-07 21.28.09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/438423/1131e31a-eb8e-4038-2ac8-f13d9296c075.png)

上記のようにymlファイルのpermissions欄に「contents: write」と書かれている場合、GitHub App設定画面の「Permissions & events」の「Permissions」の「Repository permissions」欄の「Contents」リソースへの「write」権限が必要になるようです。

![スクリーンショット 2024-04-07 21.30.07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/438423/df1eb68d-8adb-a03f-0e67-ae8e4272c273.png)
（図1）

![スクリーンショット 2024-04-07 21.33.24.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/438423/2673abf9-e1d0-6469-b359-5b1eefadb341.png)

ymlファイルのpermissions欄の「contents: write」のうち「contents」という文字列に試行錯誤開始直後の私は全く焦点をあてることができておらず、GitHub App設定画面の「Permissions」とは関係がないと思っていました。GitHub App設定画面の「Permissions」画面で適切な権限を与えさえすれば良いと思っていました。

https://github.com/katsuhirodoi2/udemy_terraform_aws_study/blob/main/.github/workflows/create_pr.yml

最終的には、上記リンク先のようなコードに落ち着きましたが、Pull requestをbotが作成するためにGitHub App Tokenで必要な権限が、既にコード中に書かれていた（「contents: write」のこと）とは全く考えていませんでした。

図1の画面から権限を付けたり減らしたりすることを繰り返して、ようやく必要な権限（今回の場合は、Contentsリソースへのwrite権限だった）を特定することができました。

前述したように、GitHub App Tokenを使用する前からbotによる自動Approveは成功していたにも関わらず、「botによる自動Approve」にも権限が必要だと私が勘違いしたために、「botによる自動Approve」と「botによるPull request作成」の両方に必要な権限を探し始め、解決までに多くの手数をかけることになりました。

GitHub App Tokenを使用する前からbotによる自動Approveは成功していたにも関わらず、「botによる自動Approve」にも権限が必要だと勘違いしたのは、以下のエラーが試行錯誤の途中に出たためです。

repository 'https://github.com/xxxxx...' not foundというエラーが出ている原因は、GitHub App Tokenの権限が足りないからだと考えていました。

![スクリーンショット 2024-04-07 21.56.58.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/438423/9756605c-fb71-b17a-9b03-bab5feae1374.png)

このエラーの原因はいまだによく分かっていませんが、少なくとも「botによる自動Approve」の処理のために「Checkout」は必要ないので無駄なことをしていました。なぜ「Checkout」をしようとしたかというと、その前に「.github/workflows/get_github_token.sh」というファイルが存在しないというエラーが出ていたためでした。

![スクリーンショット 2024-04-07 22.30.19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/438423/57862b7f-5edd-ccc7-8013-afe9444ba4e7.png)

「Checkout」をしてないからファイルが無いと考えていましたが、「botによる自動Approve」のワークフローでは「Checkout」は一度も成功しておらず、そもそも「Checkout」が必要なかったので原因は分かっていません。

そして「.github/workflows/get_github_token.sh」というファイル（これは、GitHub App Tokenを取得するスクリプト）自体も必要ありませんでした。なぜなら、「botによる自動Approve」にはGitHub App Tokenは不要だからです。にも関わらず、GitHub App Tokenを使用しようとしていたのは、この時点では「botによる自動Approve」のymlファイルは以下の赤枠部分のように、secrets.GITHUB_TOKENと書いていたからです。※1にて「hmarr/auto-approve-action@v3アクションは、デフォルトでsecrets.GITHUB_TOKENを使用」と書かれているにも関わらず、明示的にsecrets.GITHUB_TOKENを記載しなければならないと思い込んでしまっていました。「botによるPull request作成」の処理には、GitHub App Tokenを使用する方針がほとんど決まっていたので、「botによる自動Approve」でもGitHub App Tokenを使用しようと考えていました。しかし、「hmarr/auto-approve-action@v3アクションは、デフォルトでsecrets.GITHUB_TOKENを使用」するので、明示的にsecrets.GITHUB_TOKENを記載する必要もなく、さらにはGitHub App Tokenを使用するために必要な「.github/workflows/get_github_token.sh」というファイルが存在しないというエラーを解消する方法が分からなかったので、GitHub App Tokenを使用するための試行錯誤自体が最終的には不要となりました。（「botによるPull request作成」ではGitHub App Tokenを使用するのに、「botによる自動Approve」にsecrets.GITHUB_TOKENというコードを明示したくなかったので、「botによる自動Approve」でもGitHub App Tokenを使おうとしていました。）

![スクリーンショット 2024-04-07 22.36.55.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/438423/9a9e4c5d-7352-3f39-660b-bf20990ec658.png)

上記差分の右側が「botによる自動Approve」でもGitHub App Tokenを使おうとし始めた形跡です。何度も書きますが、hmarr/auto-approve-action@v3アクションは、デフォルトでsecrets.GITHUB_TOKENを使用するので、差分の赤い部分も緑の部分も最終的には不要になりました。

本章の見出しが「Pull requestをbotに作成させたい」にも関わらず、前章の「Pull request発行者自身はApproveできない」に関することを書きすぎて、これを読んでいる方に苦労をおかけしてしまっていると思います。「botによる自動Approve」でもGitHub App Tokenを使用しようと考え始めたことが、目的の全処理の実装のために試行錯誤が難航した最大のきっかけだったと振り返ります。

また、同時に2つのこと（「botによる自動Approve」と「botによるPull request作成」の実装）をやろうとしたことも良くなかったと思います。最後の方は、「botによる自動Approve」の件を解決したいのか、「botによるPull request作成」の件を解決したいのか分からないような切り分けを私はしていたと振り返ります。

### GitHub Appの権限設定

GitHub Appの権限設定の仕様を把握するのにも難儀しました。

GitHub Appは、権限を増やした時はAppの再インストールが必要で、権限を減らした時はAppを再インストールせずとも権限は削除される（※2）ようです。

GitHub App Tokenを使えるようにするまでが大変だったので、権限の増減に対する仕様を把握しようとするころには頭のリソースが足りなくなっていました。それに加えて※2の仕様があったので、頭はパンク寸前になっていました。

試行錯誤の序盤は、正攻法で「botによる自動Approve」と「botによるPull request作成」を動かすために、GitHub App設定画面の「Permissions」画面から権限を増やしたり減らしたり繰り返していました。しかし、※2の仕様を把握していなかったので、権限を追加したはずなのにワークフローが権限エラーになり、その権限は関係なかったと結論付けていました。※2の仕様に気付けないと、全ての権限を付けてもワークフローが動かないことになってしまうので、詰んでしまいます。

いろんな画面を見ていく中で、Organization AccountのAppのインストール状態を表示する（設定を変更する画面では無い）画面（以下の図）を見つけ、再インストールをしないとGitHub Appの権限追加設定が反映されないことに気付きました。

![スクリーンショット 2024-04-08 1.24.40.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/438423/e7fa01ab-3b5e-85d2-83b5-69e4fd91f8ba.png)
（※3）

Organization機能やGitHub Appを知ったばかりの私にとっては、それはハードなゲームになりました。

### GitHub App Tokenの有効期限は1時間

GitHub App Tokenの準備にあたって、参考にさせていただいた記事は以下です。

https://blog.chick-p.work/til/github-app-token/

GitHub App Tokenを作成する準備として、

1. GitHub Appを作成する
2. GitHub Appをレポジトリにインストールする
3. リポジトリにGitHub App IDと秘密鍵を登録する

があり、GitHub App Tokenを生成するには、

1. アプリのJSON Webトークン（JWT）を生成する
2. インストールIDを取得する
3. JSON Webトークン（JWT）とインストールIDを使って、GitHub App Tokenを生成する

という手順が必要なようでした。

JWTやインストールIDなど、読んだだけでは把握が難しい単語ばかりでしたが、何度かGitHub App Tokenを生成するうちに、理解し始めました。

はじめ私は、リポジトリにGitHub App IDと秘密鍵を登録する意味が分かっていませんでした。なぜなら、GitHub App Tokenを生成の手順（上記記事で紹介されていた以下URL）では、リポジトリへの情報の登録の話は出てこず、GitHub App Tokenをローカル端末などから発行するような手順に見えたからです。

https://docs.github.com/ja/apps/creating-github-apps/authenticating-with-a-github-app/generating-an-installation-access-token-for-a-github-app

上記URLにもある程度書かれていますが、私がGitHub App Tokenを生成するにあたってローカル端末で実施したコマンドライン群は以下です。

#### JWTの生成
```
create_github_jwt.sh <<<GitHub App ID>>> <<<秘密鍵ファイル名>>>
```

create_github_jwt.shは、以下記事で紹介されているbashスクリプトです。

https://docs.github.com/ja/apps/creating-github-apps/authenticating-with-a-github-app/generating-a-json-web-token-jwt-for-a-github-app

#### インストールIDの取得

```
jwt=<<<さきほどのスクリプトで取得したjwtの値>>>
```

```
curl -L \
  -H "Accept: application/vnd.github+json" \
  -H "Authorization: Bearer ${jwt}" \
  -H "X-GitHub-Api-Version: 2022-11-28" \
  https://api.github.com/app/installations
```

##### 実行結果
```
{
  "id": 111111, ←ここに表示されるのがインストールID,
  "slug": "katsuhirodoi2org-github-app",
  "node_id": "A_xxxxxxxxxxx",
  "owner": {
    "login": "katsuhirodoi2org",
    "id": 222222222, ←これは違う模様（おそらくこれはOrganizationのID）,
    "node_id": "O_xxxxxxx",
   ...
```
    
#### GitHub App Tokenの生成

```
curl --request POST \
--url "https://api.github.com/app/installations/<<<さきほどのコマンドで入手したインストールID>>>/access_tokens" \
--header "Accept: application/vnd.github+json" \
--header "Authorization: Bearer ${jwt}" \
--header "X-GitHub-Api-Version: 2022-11-28"
```

##### 実行結果

```
{
  "token": "ghs_XXXXXXXXXXXXXXXXXXXXXXXXXXX", ←これがGitHub App Token
  "expires_at": "2024-04-06T09:50:40Z", ←有効期限（UTC表記である模様）
  "permissions": { ←トークンの権限
    "metadata": "read",
    "pull_requests": "write",
    "repository_projects": "write"
  },
  "repository_selection": "selected"
}
```

この記事を書いていて完全に理解しましたが、「有効期限」も「トークンの権限」もここに書かれていますね。「有効期限」の部分は発行日時だと思い込んでいました。なぜなら有効期限はUTC表記であるようで、発行した時には過去の日時となっており、過去の日時が有効期限だとは思えなかったからです。トークンの権限についても、今となっては、ymlファイルに記載する権限（permissions）と矛盾がないか等を確認しやすい情報だと思えました。

ここで発行されたGitHub App Tokenをsecrets.APP_GITHUB_TOKENとして利用すると、「botによる自動Approve」がうまく動きました。（そもそも、下記の変更前の状態でも動いていましたが）

![スクリーンショット 2024-04-08 0.11.05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/438423/6b03b4a5-3ec9-8408-938d-9f77ab184c8c.png)

![スクリーンショット 2024-04-08 0.16.45.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/438423/e4a59dc7-3135-048d-e477-e317aa338f1d.png)

しかし、その後しばらくしてから「botによる自動Approve」の処理でトークンが不正な認証情報であるというエラーが出ました。

![スクリーンショット 2024-04-08 0.16.10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/438423/5d8fb54a-372f-e6f8-d2ba-612619aaa2d8.png)

このあたりは、用語をしっかり区別している人だとすぐに原因が分かるのかもしれませんが、私は「botによるPull request作成」のためにGitHub App設定画面の「Permissions」にて権限を削除したことが原因だと思い込んでしまいました。

同時に2つのこと（「botによる自動Approve」と「botによるPull request作成」の実装）をやろうとしたこと、権限の試行錯誤が難航したことも絡み合って、GitHub App Tokenの有効期限が短いことに気付けませんでした。（勝手に1年くらいの有効期限だと決めつけていました）

そもそもエラーに「Bad credentials」と書かれているので、権限ではなく認証情報に問題があることは、用語の区別をしっかりしていれば気付けたと思います。

原因を調査するために「GitHub App Token」などのキーワードで記事を漁っている中で、GitHub App Tokenの有効期限が1時間であることに気付きました。

この段階では、GitHub App Tokenをリポジトリの「Actions secrets and variables」画面の「Repository secrets」に「GH_TOKEN」として手動で登録していたので、1時間に1回GitHub App Tokenの生成を手動で行って、secretsに登録するのは非効率的すぎると落胆しました。

とはいえ、自動的に生成する仕組みはあるはずだとすぐに考えることができたので、「get_github_token.sh」のようなスクリプトを見つけることができました。ここでようやく、リポジトリにGitHub App IDと秘密鍵を登録する意味が分かりました。

GitHub App Tokenを自動的に生成するスクリプトについては、以下の記事の「スクリプトの全体像」の章を参考にさせていただきました。

https://zenn.dev/tmknom/articles/github-apps-token

## 結局、何をしたかったのか

目的は、mainブランチにMergeするためには、Approveを必要としたいということでしたが、以下の制約があったためにやることが多くなりました。

* チーム（Organization）には私一人しかいない
* けど、今後のためのレビューの仕組みを整えておきたい
* Pull request発行者自身は、レビューアとしてカウントされない（なので、私が発行したPull requestはMerge要件に合致せず、mainブランチにMergeできない）

なので、「botによる自動Approve」のワークフローを作成しました。私がPull requestを発行した場合は、hmarr/auto-approve-actionによってApproveされるようにしました。（ワークフローのコードは本記事で既に紹介させていただいた通りです）

では、「botによるPull request作成」のワークフローを作成した理由はというと、実は明瞭な理由はありません。作成したきっかけは、先に作成しようとしていた「botによる自動Approve」がうまく動かず、Pull request発行者自身は、レビューアとしてカウントされない課題を別の手段を解決しようとしていたことでした。

半分こじつけですが、

* チームメンバーが増えた時に私がレビューアとしてApproveボタンを押した時の挙動を確認したかった
* 「botによる自動Approve」の動作確認をしたかった（自分以外（つまりbot）がPull requestを上げたときは、「botによる自動Approve」はskipされることを確認したかった）

というものになります。まぁ色々やってみたかったというのが正直なところですね。

## 結局何をやったのか

前提条件の記載漏れや、言葉を省くことによる意図していない解釈のされ方を防ぐために本記事は長文となりました。おそらくほとんどの読者は、結局どういう設定をすればやりたいことができるのかというところを知りたいものだと推測します。

この記事に書いたこと、書いてないけどやったこと（詰まりポイントではなかったので紹介していない）をまとめて記載します。

1.　Organization機能（有料）を有効にした
→ApproveをもらわないとMergeできないようにするには必要である認識

2.　「botによる自動Approve」のワークフローを作成した
→コードへのリンクは本記事中で紹介済み

3.　「botによるPull request作成」のワークフローを作成した
→コードへのリンクは本記事中で紹介済み
→GitHub App Tokenを自動発行するスクリプトも用意（[コード](https://github.com/katsuhirodoi2org/udemy_terraform_aws_study_private/blob/main/.github/workflows/get_github_token.sh)　※4）

4.　※4に必要なRepository secretsを登録（以下画像の赤枠部分）

![スクリーンショット 2024-04-08 1.15.29.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/438423/d770c489-df49-a620-dfbf-b883fe60a2bb.png)

5.　GitHub App Tokenを使用するためのApp作成
→Appの作成方法は本記事中で紹介させていただいた記事に記載されている

6.　GitHub App設定画面の「Permissions」設定（本記事中の※3の画面）
→「botによるPull request作成」に必要な権限「Read and write access to code and pull requests」（codeと書かれているが、本記事中のcontentsのこと。ややこしい）
> ymlファイルのpermissions欄に「contents: write」と書かれている場合、GitHub App設定画面の「Permissions & events」の「Permissions」の「Repository permissions」欄の「Contents」リソースへの「write」権限が必要

　と記載したが、実は「pull requests」のwrite権限も必要である模様

→「Read access to metadata」についてはデフォルトで付いていたのでそのままにしている

→「botによる自動Approve」は、GitHub App Tokenを使用しなかったので無関係

7.　ブランチルール（下図の通り）

![FireShot Capture 001 - Settings · Branches · Branch protection rule · katsuhirodoi2org_udemy_ - github.com.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/438423/e05f46ef-ee4e-8ae5-52a2-46c67fdc64cd.png)

8.　リポジトリの一般設定（Workflow permissionsの設定は重要だったと理解している）

![FireShot Capture 002 - General - github.com.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/438423/840a99b9-3f7d-b719-f848-f81a24d1716d.png)

9.　Organizationの一般設定（同じく、Workflow permissionsの設定は重要だったと理解しているが、8と9の設定の依存関係的な部分は理解していない）

![FireShot Capture 003 - Actions settings · katsuhirodoi2org - github.com.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/438423/5e54fdb2-970a-9610-bd38-c0eb015bc6d4.png)

## まとめ

長くなりましたが、以上になります。一気に書いたので、矛盾した文章や誤字、分かりづらい箇所などあるかもしれません。お気づきの際はコメント等で指摘いただけると幸いです。また、何か質問がありましたらお気軽にご連絡ください。

ここまでお読みいただきありがとうございました。