2023年2月12日日曜日

GitHubでコメント駆動CI

 アプリケーション開発でCIによる自動テストを行っているとき、CIの実行回数や実行時間を減らしたい場合があります。

どういうときかって?そらもうアレよ。コミット回数が多すぎるとか一回のCIに時間がかかりすぎるとかでCIサービスの時間上限にひっかかりそうなときです。

GitHub Actionsでのやり方を説明します。

ちなみに記事の前半は特別なことは何も書いていないので、この記事のタイトルにつられて来た人は「コメント駆動で動かそう」まで読み飛ばしても大丈夫です。

基本方針

まず最初に、開発フローに極力影響を与えないことを基本的な方針とします。

例えばこういうことは行いません。

  • コミット回数が多いのが問題なら一度のコミットでたくさん変更を入れて、なるべくコミット回数を減らそう!
  • リモートリポジトリーへのプッシュはギリギリまで我慢して、できるだけローカルで開発しよう!
  • CIに時間がかかるなら、テストは最小限にしよう!

いずれもCIの実行回数や実行時間は減るかもしれませんが、代わりに開発フローが面倒になったりアプリケーションの品質に影響が出てしまいます。そのため、開発フローを崩さずにこれまで通りテストを書いて、これまで通りコミットして、これまで通りプッシュしながらCIの実行時間を削減することを目指します。

pre-pushを活用しよう

まずは、プッシュの前にローカルでもテストを走らせましょう。手動でやってももちろんいいんですが、どうしても忘れることがあるので自動化してみます。

Gitのpre-push hookを使えば簡単に実現できます。

  • .git/hooks/ディレクトリー以下にpre-pushという名前のファイルを作る
  • このファイルをシェルスクリプトとして、shebangと必要なCI処理を書く
  • このファイルに実行権限をつける

これだけです。GitにはBashが組み込まれているので、Windowsでも動くんじゃないかと思います。知らんけど。

pre-pushの中身は各開発者に任せるとばらつきが出るので、例えば.githooksのようなディレクトリーの中に作っていて、READMEに開発チュートリアルとして「最初にmv .githooks/pre-push .git/hooks/を実行すること」とでも書けばよいでしょう。

これで、少なくともリモート(GitHub)側で毎回CIを走らせる必要はなくなりました。ローカルで毎回走らせてんじゃねーかというツッコミはやめてください。元々の目的はCIサービスの時間節約です。

CIは最後の砦

「あれ、ローカルで毎回テストしているならリモートで走らせる必要はないのでは?」

そのとおりです。ただし、開発者が全員pre-pushを設定していればの話です。現時点ではgit clone時のpre-pushの自動設定はできないので、開発マシンを新調してgit cloneしたときにpre-pushの設定を忘れてしまうことがあるかもしれません。

あるいは、「毎回テストが走るのがダルいからこっそりpre-pushスクリプトを消しちゃえ」という不届き者がいるかもしれません。

そのため、最後の砦としてリモート側でもCIを通さないとメインブランチにマージできない仕組みは必要です。

この「CIを通さないとメインブランチにマージできない」やり方は、Branch protection rulesを使えばいいのでやり方は省略します。

CIを手動で起動しよう

とは言ってもプッシュされるたびにCIを走らせていたら元々の目的が達成できないので、CIを走らせるタイミングを工夫します。つまり、「毎回じゃないけど要所要所で走らせる」という感じです。

今まで色々やってみました。たとえばレビューが通ったとき(approve)に走らせるとか、ブランチを最新状態にしたとき(ベースブランチからマージしたとき)に走らせるとか。そしてあるとき気づきました。

やっぱり手動で実行するのが一番。

いや、上で書いた方法でも悪くないんですけどね。ただ、複数人で並列で開発していると

  • approveしてもらってCIが走った
  • でも他の人がすでにベースブランチにマージしており、トピックブランチと競合を起こした
  • 競合を解決したらもう一度レビューをしてもらう必要がある
  • approveしてもらったのでまたCIが走った

のように、どうしても無駄なCIが走ってしまいます。というわけで手動で実行することにしました。

コメント駆動で動かそう

こんな感じで特定のプルリクエストメッセージに反応してCIを走らせます。

GitHub Actionsは色々なイベントをサポートしているので簡単だろうと思ったんですが、意外と手こずりました。この記事には要点だけ書こうと思ったんですが、色々面倒なので検証に使ったリポジトリーを晒します。

https://github.com/shimataro/pr_comment_ci_test

verify.ymlが本体です。フォークしていろいろいじってください。

苦労したのは主に該当プルリクエストのステータスを更新する部分です。GitHub Actionsの仕様ではプルリクエストのコメント更新イベントがコミットハッシュに紐付いていないので、さんさんさんの記事を参考に該当のコミットハッシュを取得しました。

あとは、GitHub Actions起動時についてくるアクセストークンではステータスを変更できなかったので、"repo:status"へのアクセス権限があるクラシックトークン(↓)か

"Commit statuses"への読み書き権限があるFine-grained token(↓)を作ります。

このトークンをGH_TOKENという名前でリポジトリーのシークレットに登録すれば動くはずです。

Q&A

なんでプルリクエストのコメント更新イベントがコミットハッシュに紐付いていないの?

もっともな疑問です。プルリクエストのイベントなんだからコミットに紐付いても良さそうなものなのにね。

これは想像ですが、多分GitHubの内部ではイシューとプルリクエストが同じリソースとして扱われているのが原因じゃないかと思っています。コミットと関係ないイシューのコメントに対してもこのイベントが発火されるので、プルリクエストイベントとコミットハッシュを紐付けようがなかったということじゃないかと思っています。

そろそろGitHubの中の人もこの設計を後悔している頃じゃないですか?(勝手な想像)

アクセストークン新しく作るのダルイんだけど?

対象リポジトリーの設定を変更することで、デフォルトのGITHUB_TOKENを使うこともできます。

GITHUB_TOKEN設定によってpermissive/restrictを切り替えられます。こちらを参考に、Read and write permissions"に変更すればGITHUB_TOKENを使えるはずです。試してないけど

0 件のコメント:

コメントを投稿