[GitHubActions] 自作Actionを3年間運用してます

タイトルの通り自作Actionを運用して3年経ったので、運用回りのあれこれを書いてみようと思います。運用というのはGitHubActionをただ使っているというだけではなく、OSSとして公開してメンテナンスも続けているという感じの意味も入ってます

**いろいろと詰め込むことになると思うので、詳細書いてほしいとかあったらコメントにリクエスト書いてください**

## 前段
初期のGitHub ActionsはHCLという記法が採用されてました。自分はその頃使っていなかったのでHCLの頃の説明を省きますが、2019年夏ごろにHCLからYAML記法に変わりました。これは内部的にAzure Pipelinesのコードを利用する形に変更された影響ですね

前年にAzure PipelinesがOSS向けに無料公開されていたということもあり、自分のCI/CD環境はAzure Pipelinesがメインでした。Azure Pipelinesでは自分用にCustomTask[^1]を作ったりと、まぁまぁ楽しませていただきましたが、GitHub Actionsの仕様変更を聞くと「このブルーオーシャンには乗るしかねぇええ」ということで[MeilCli/danger-action](https://github.com/MeilCli/danger-action)を作りました。これが始まりの動機になりますね(笑)

## 自作ActionをOSSとして運用する
自作Actionをpublic repositoryにしておいて放置するということもできましたが、作って満足して放置するのは卒業しないといけないなぁと思っていた頃合いだったということもあり、OSSとして運用することにしました

自作Actionを運用するにあたってしなければいけないことは

- 依存ライブラリーのアップデートに追従する
- 定期的にpack[^2]してReleaseを作成する
- IssueやPullRequestへの対応

の3つですね。使う人が少ないうちは前2つが特にコストのかかるところかなと思います

### 依存ライブラリーのアップデートに追従する
GitHub ActionのActionはnode.jsで動作します。自分はJavaScriptが書けないのでTypeScriptでActionを作成しています。そうなってくると、依存ライブラリーは[actions/toolkit](https://github.com/actions/toolkit)だけに収まらず、TypeScriptやeslintやjestなどなど多数のライブラリーに依存することになります

[Dependabot](https://docs.github.com/ja/code-security/dependabot/dependabot-version-updates/about-dependabot-version-updates)を使うと、自動でライブラリーのアップデートを行うPullRequestを作成してくれます。ただeslintやjest周りはアップデートの頻度が高いため、真っ先に自動化の対象になりました

Workflowとしては、DependabotがPullRequestを作成する→自動でApproveする→すべてのStatusCheckが通るとマージする、の流れですね
ref: 
- https://github.com/MeilCli/danger-action/blob/master/.github/workflows/ci-pr.yml
  - https://github.com/MeilCli/danger-action/blob/master/.github/workflows/ci-base-approve.yml
- https://github.com/MeilCli/danger-action/blob/master/.github/workflows/merge.yml

### 定期的にpackする
手動運用できる状態(すなわち、npm scriptにpackするコマンドがある)ならば、Master/Mainブランチへpushされるたびにpackし、差分があればPullRequestを作成するというWorkflowを作れば、常に最新の実行ファイル(.js)を用意することができます

つい最近まではPullRequestを作るところまでは自動で、手動でApproveすることによって自動でマージが行われるというフローにしていました。ただ、それも面倒なのでPullRequest作成からマージまで自動化を行っています(ちゃんと動いてくれるかは当分観察します)

ref:
- https://github.com/MeilCli/danger-action/blob/master/.github/workflows/ci-master.yml
  - https://github.com/MeilCli/danger-action/blob/master/.github/workflows/ci-base-build.yml
- https://github.com/MeilCli/danger-action/blob/master/.github/workflows/ci-pr.yml 
  - https://github.com/MeilCli/danger-action/blob/master/.github/workflows/ci-base-merge.yml

### 定期的にリリースを作成する
ActionをWorkflowで利用するには、`uses: owner/repository@branch_or_tag_or_commit`のような形で指定します。Master/Mainブランチで開発を行うならば、別のブランチやタグをStableVersionとする必要がありますね

一方で、定期的にリリースするには自動でバージョン情報が更新される仕組みが欲しいです

諸所の運用を自動で行いたい願望が出たので、諸所をいい感じにやってくれるActionを自作しました。そのため、MeilCliが作成しているActionはすべて[MeilCli/bump-release-action](https://github.com/MeilCli/bump-release-action)による自動リリースを行っています

このActionを使うと
- Commit messageやPullRequestのラベルからメジャー・マイナー・パッチアプデを算出し、最新のリリースから次のバージョンを決定
- Commit messageやPullRequestからリリースノート作成
- リリースの作成
- `v1`, `v1.1`のようなバージョンブランチの作成・プッシュ
- ファイルに書かれているバージョン情報を書き換え

をやってくれます。Action用に作成したActionですが、使い方次第ではライブラリーやアプリのバージョンアップフローにも使えると思います

ref:
- https://github.com/MeilCli/danger-action/blob/master/.github/workflows/release.yml
- https://github.com/MeilCli/danger-action/blob/master/.github/bump.yml

### IssueやPullRequestへの対応
DependabotのPullRequestを放置せずにちゃんとアップデートしていると、だんだん使ってくれる人が増え、IssueやPullRequestが作られたりし始めました

IssueはIssueTemplateを用意し、PullRequestはCONTRIBUTING.mdを用意しています

ref:
- https://github.com/MeilCli/danger-action/tree/master/.github/ISSUE_TEMPLATE
- https://github.com/MeilCli/danger-action/blob/master/.github/CONTRIBUTING.md

ただ、eslintやprettierでコードフォーマットしているので、PullRequestではちゃんとコードフォーマットした状態にしてほしいところではあります。コードレビュー用のbotやactionはいろいろありますが、ちょっとした野望もあり、自分は自作の[MeilCli/common-lint-reporter](https://github.com/MeilCli/common-lint-reporter)を使っています

ref:
- https://github.com/MeilCli/danger-action/blob/master/.github/workflows/ci-base-build.yml
- https://github.com/MeilCli/danger-action/blob/master/.github/workflows/report.yml

## Tips
### リポジトリーが多すぎて管理しきれない
現在自分は十数個の自作Actionを運用しています。そうなってくると可能な限り自動化をしないと、手に負えません

そのため2つの自動化をしています

1. 新規Action作成はtemplate repositoryを使う
   - https://github.com/MeilCli/action-template
1. リポジトリーの設定はterraformを使う
   - https://github.com/MeilCli/github-terraform

まだまだ自動化具合は足りませんが、これで最低限の管理はできています。ただ新規ActionのSecret設定が面倒[^3]だったりと、terraformを辞めようかなぁという気持ちは徐々に抱いています(このあたりおすすめのツールがあったら教えていただけると助かります)

### dependabotやforked repositoryからのpull_requestにはreadonly tokenが入っている
[Keeping your GitHub Actions and workflows secure: Preventing pwn requests](https://securitylab.github.com/research/github-actions-preventing-pwn-requests/)で書かれていることです

どういうことかというと、外部からのPullRequest自体にtokenを露出させるコードが仕込まれていると、そのPullRequestにおいてのCI/CDでtokenが盗まれてしまうので、外部からのPullRequestではreadonly tokenにしたりsecretsを入れなかったりをしてセキュアな状態にするよという感じの内容です

OSSやpublic repositoryでのGitHub Actionsではこのあたりのセキュリティーを考慮したWorkflowを作らないといけないのですが、ぶっちゃけ面倒ですねｗ

"外部からのPullRequestのCIが完了した"というトリガーで動く`workflow_run`なWorkflowを定義すると、そのWorkflowは内部扱いでsecretsとかを使うことができるのですが、元となったWorkflowのevent payloadを受け渡すのが面倒だったりします

そのため[MeilCli/common-lint-reporterにはDependabotやOSS向けのAction](https://github.com/MeilCli/common-lint-reporter/blob/master/documents/oss-or-dependabot-usage.md)を用意しています

### 1Repository=1Actionではない
[MeilCli/common-lint-reporter](https://github.com/MeilCli/common-lint-reporter)には複数のActionが存在しています。WorkflowでActionを参照する場合は`uses: owner/repository@branch_or_tag_or_commit`なので1Repository=1Actionと思われがちかと思いますが、正確には`uses: owner/repository/path/to/action@branch_or_tag_or_commit`の形式なので、Repositoryのroot以外に置いているaction.ymlを使うことができます

※ただし、Marketplaceで公開できるActionはRepositoryのrootにあるaction.ymlだけです

Repository内に複数のActionを定義することで、多種多様な挙動を取れるようにすることができます。(そうできなかったらcommon-lint-reporterのような複合ActionはRepositoryの管理がやばいですよね)

自分が運用している他のActionはvercel/nccでpackしていますが、base libraryをそれぞれのaction用のjsに含めるとRepositoryの容量が増えてしまうので、Webpackを使用して、base library部分は共有する構成にしています

ref: https://github.com/MeilCli/common-lint-reporter/blob/master/webpack.config.js

### fine-grained personal access tokenを使う
https://docs.github.com/ja/rest/overview/permissions-required-for-fine-grained-personal-access-tokens?apiVersion=2022-11-28

ついこないだ知ったのですが、より細かいScopeで区切ったPATを作成できるようになったようです(Beta)  
複数のpublic repositoryがある中で、使用するGitHub ActionsにPATを使うと漏洩リスクがつきまといます。GitHub Actionsにおいてはそこそこ気を付けたWorkflowを作成していますが、より細かいScopeに区切れるなら区切っておいたほうが安心ですね

今のところREST APIにしか対応していないようなので、GraphQLを使ったActionを使うことはできないので注意です

### Repositoryの言語集計でAction用に用意する.jsファイルを無視できる
小ネタです

`.gitattributes`に`*.js linguist-language=TypeScript`を宣言すると.jsをTypeScript扱いできます。Repositoryの言語集計にJSが入るのが許せない人はTS扱いさせてみるといいでしょう。たしか同様な方法で完全にignoreもできたんじゃないかなと思います

ref: https://github.com/MeilCli/danger-action/blob/master/.gitattributes

### action.ymlでGitHub Tokenをデフォルト値にできる
小ネタです

GitHubのTokenが必要なActionで、inputとしてtokenを引き受けるけどデフォルト値設定していないActionを見かけることがあるので書いておきます

github-actions[bot]のTokenでいい場合は`default: ${{ github.token }}`と記述することでデフォルト値にしておくことができます。利用側の設定を1つ省くことができるのでおすすめです

ref: https://github.com/MeilCli/common-lint-reporter/blob/master/action.yml

## おわりに
3年ほど十数個の自作Actionを運用してきましたが、ただただ面倒でつらいだけです。Dependabotは毎週のように数個ずつPullRequestを作ってくるので、自動化していたとしても通知が埋まってしまいます

唯一よかったことといえば、GitHubがお小遣いをくれたことですね(絶賛GitHub Sponsorになってくれるひと募集中です: https://github.com/sponsors/MeilCli)

https://twitter.com/penguin_sharp/status/1540480459721048065

## 運用中のAction
よかったらStarください

- https://github.com/MeilCli/danger-action
- https://github.com/MeilCli/common-lint-reporter
- https://github.com/MeilCli/bump-release-action
- https://github.com/MeilCli/action-template
- https://github.com/MeilCli/slack-upload-file
- https://github.com/MeilCli/setup-crystal-action
- https://github.com/MeilCli/hidable-comment-action
- https://github.com/MeilCli/nuget-update-check-action
- https://github.com/MeilCli/gradle-update-check-action
- https://github.com/MeilCli/swiftpm-update-check-action
- https://github.com/MeilCli/carthage-update-check-action
- https://github.com/MeilCli/cocoapods-update-check-action
- https://github.com/MeilCli/npm-update-check-action
- https://github.com/MeilCli/detekt-statistics
- https://github.com/MeilCli/android-lint-statistics
- https://github.com/MeilCli/test-command-action
- https://github.com/MeilCli/regex-match
- https://github.com/MeilCli/check-run-auto

[^1]: GitHubActionsでいうところのAction
[^2]: TypeScriptで作成しているのでzeit/ncc(現vercel/ncc)やwebpackを使ってJavaScriptに変換しています
[^3]: いちおうTerraformでsecretを設定できるけど、secretの設定まで行うTerraformをpublic repositoryで管理する度胸と技量がなかったのでやってないです