🚮

Close IssueというBotの霊圧が消えたので、代替となるBotをDenoで作ってみたという話。

2023/05/15に公開約5,200字

要約

  • 「Close Issue」というGitHubアプリが半年以上前から停止していた。probotというフレームワークで作られていることと、ドキュメント不足も相まって誰もメンテナンスを引き継いでいなかった。
  • 自分もこのアプリを導入したかったが、メンテナンスを引き継ぐコストを考慮した結果、ゼロから代替アプリを開発することにした。
  • オリジナルでの反省点を活かして、HonoというWebフレームワークと、Octokit.jsというGitHub公式ライブラリのみを使用することにした。
  • 環境構築の手間やメンテナンスのしやすさも考えて、代替アプリではDenoを使って作成することにした。オールインワンは正義である。
  • ChatGPTの助けを借りてソースコードを作成したが、使用しているプログラミング言語がDenoであるという前提を与えているにも関わらず誤ったコードを出力することが多く、あまり使い物にならなかった。
  • バックエンドサーバーとして「Deno Deploy」を使うことにしたが、環境変数の設定時に改行コードが削除されてしまう仕様のため、GitHubの秘密鍵が認識されないという問題が発生した。
  • 自分の知識不足もあり、秘密鍵が認識されない問題の解決に約1週間かかった。改行コードが削除されると秘密鍵として認識されないという仕様もこの時に初めて知った。
  • 最終的に、BASE64エンコードされた文字列を環境変数として登録し、アプリ側でデコードすることにより、改行コードが削除されても秘密鍵が認識されるようにした。
  • GitHubアプリの作成は初めての経験で、非常に多くのことを学んだ。今後もこの経験を活かして、OSSプロジェクトのメンテナンスコストを削減するためのGitHubアプリを作成していきたい。
  • 技術力不足などの要因により、オリジナルにあった機能の一部が代替アプリに移植できなかった。そこも改善していきたい。

はじめに

大規模OSSプロジェクトで時たま見かけるClose IssueというGitHubアプリをご存知だろうか。
このアプリは 「Issue内に特定の文字列が含まれていなかった場合、設定されたコメントを行って自動でIssueを閉じてくれるBot」 というもので、テンプレートを守らない無法者たちからメンテナーの精神的負担を軽減してくれる。
しかし、このアプリが少なくとも今年1月から停止したままであり、このBotを管理しているリポジトリのIssueに停止している旨のコメントが投稿されても作者からの反応はなかった。

引き継ぎの検討、そして代替アプリ開発へ

自分はまだ大規模OSSプロジェクトを任されたこともなく、そのような大規模OSSプロジェクトのオーナーをしている訳でもないが、前もってインストールしていても害はないと考えて導入を検討した。
しかし、テストリポジトリで試験導入したところ全く動作せず、困り果てて作者のリポジトリを確認しにいったところ、停止している旨のIssueを見つけた次第である。
これには困ったと考え、すぐさまプロジェクトを引き継ぐかどうかを検討した。しかし、以下の点から引き継ぎを断念し、代替アプリの開発へと舵を切った。

probotというフレームワーク

Node.js用に作られたGitHub Appsで動かすBot用フレームワークとのこと。GitHub - probot/probot
内部で様々なライブラリをラップしているため、完全にブラックボックス化しており、また依存するモジュールの多さもバックエンド環境にサーバーレスを採用することを考えると不安となる要素だった。

ドキュメントの少なさ

当初から引き継がれることを考えていなかったのか、READMEがわずか51行という短文であり、またprobot公式サイトへのリンクだけなど不親切な点がいくつか見受けられた。
そのため、フォークを確認しても誰も開発を引き継いでおらず、Renovateだけがコミットを増やしていただけで、人間の手でメンテナンスを行われている痕跡はなかった。
これではソースコードを引き継いで開発しても継続した運用は厳しいと考えた。

技術スタックの選定

代替アプリ開発にあたって最大限重視したのが「メンテナンスのしやすさ」である。縁の下の力持ち的なGitHubアプリであったため、万が一こちらの環境にあるバックエンドサーバーが停止しても、即座に別の環境にて動かせるようにしたかったのである。
そのためにはミニマムな実装が必要であり、Webフレームワークとして「Hono」とGitHub公式ライブラリである「Octokit.js」のみに依存することにした。
また、Node.jsではなくDenoを使用することにした。TypeScript + ESMで書きたかったのと、テストフレームワークやリンター、フォーマッターを別個で入れるのが非常に面倒くさかったからである。メンテナンスコストの観点からもオールインワンであることは正義である。

Chat AIの活用、そして限界を知る

ソースコード生成AIといえば「GitHub Copilot」が有名であるが、オリジナルの機能をできるだけ引き継ぐという目的だったため、CopilotではなくChatGPTを活用することにした。
しかしこのChatGPT、予めDenoであることを伝えてもNode.jsの要素をふんだんに混ぜたソースコードを吐き出してくることが多かった。もっとも、機械学習に使われたソースコードの量が段違いなので仕方ないことだが。
モジュール内に存在しない関数名などを混ぜ込んでくるのは単に指摘すればいいだけなので気にしないのだが、ゼロからChatGPTだけでプログラミングするにはまだまだ時間がかかりそうだなと思った。
もっとも、ChatGPTが全然頼りにならなかった訳でもなく、自身が書いたソースコードをChatGPTに渡してレビューをしてもらったりと大いに活用した。ユニットテストもほとんどChatGPTに生成してもらったものである。
Denoの組み込みテストモジュールに置き換えるだけの手間はかかってしまったが、それぐらいのコストは安いものである。

バックエンドサーバーの選択、そして「Deno Deploy」へ

GitHubから送られてくるWebhookイベントに応じてBotを動作させなければならないので、必然的にバックエンドサーバーが必要となる。
前項にも書いていた通り、バックエンドにサーバーレス環境を使うことを検討していたので「Deno Deploy」を選ぶことにした。せっかくなので。
初めての環境あるあるで、いくつかの罠を踏み抜いてしまった。後学のためにここへメモを残しておくことにする。

アプリ認証について

最初に断っておきたい。この記事では「GitHubアプリの作り方」は説明しない。詳しくはGitHub アプリに関するドキュメントを参照してほしい。
ここで説明するのはアプリ認証における秘密鍵の形式と、Deno Deployにおける仕様に引っかかって無駄な時間を使ってしまったことである。自分の無知も一因にあるが。

秘密鍵の形式について

アプリ認証を行うために、GitHubから秘密鍵を生成して保存しなければならないのだが、ここでダウンロードできる秘密鍵の形式はPKCS#1形式である。
すでに知識を持っている方なら問題がすぐに分かるだろうが、実はこのままではOctokit.jsで秘密鍵として認識されない。内部でWebCryptoを使っているためか、PKCS#8形式に変換しなければならないのである。

御託はいいから変換コマンドを掲載しろという声が聞こえてきそうなので下記に記載しておく。

openssl pkcs8 -topk8 -nocrypt -in <private_key> -out <converted_key>

Deno Deployにおける環境変数の設定について

上記コマンドで無事にPKCS#8形式へ変換を行い、Web UIから環境変数として秘密鍵を登録することになるであろう。
しかしここでも罠があり、なんとWeb UIから環境変数を登録すると「すべての改行コードが削除され、一行にされてから」登録されてしまう。
ここは自分の無知も原因があったが、改行コードが削除されてしまうとPKCS#8形式であっても秘密鍵として認識されなくなってしまう。

ローカルでは無事に動いているにも関わず、Deno Deploy上ではエラーを吐いて全く動かず、問題解決に約1週間かかってしまった。
ChatGPTにも聞いたところPKCS#8形式に改行コードは関係ないという大嘘をつかれてしまい、解決に時間がかかってしまったことも要因である。
この時ばかりは、自分では解決できない問題だと開発中止を考えたほど心がすり減りボロボロになってしまった。

解決のヒントはprobotのドキュメントにあった

もはや万事休すと考えた自分は、一筋の光を求めてprobotのドキュメントやソースコードを読み始めた。正常に動くものを読むのが一番解決に近くなると考えたからだ。
そして、ドキュメント内で気になる記述を発見した。Configuration

秘密鍵を環境変数へ登録する際に、改行コードがすべて削除されてしまう場合はBASE64エンコードをしてから登録しろと書いてあるのだ。
目から鱗だった。まさかそんな手があったなんてと驚きすら隠せなかった。こうして、何故か秘密鍵が読めない長く苦しい戦いはあっけなく幕を閉じた。

初めてGitHubアプリとして動作するBotを作ってみての感想

この記事を読んで「俺もGitHubアプリとして動作するBot作ってみるか」なんて思う奇特な方はまずいないと思っているが、WebhookとREST API、そして少しのサーバーレス環境についての知識があればBotを作れることが分かった。
GitHub Actionsを使った場合はユーザー名が固定されてしまうが、GitHubアプリとして動作するBotの場合は自由にユーザー名とアイコンをつけられるというのも利点である。

最後に、自分が作ったBotについて下記に貼り付けておく。残念ながらオリジナル版にあった一部の機能を引き継げなかったことや、ユニットテストが不足している部分があるが、おおむね動くものができた。
もし足りない部分について助けてもらえるのであれば、是非プルリクエストをしてほしい。特にGitHub APIを呼び出す部分は後述するが、ユニットテストをどう書けばいいか分からなかったので書けていない。
GitHub - err931/issues-watchdog-app: A GitHub App to automatically close garbage issues.

すごく恥ずかしいが、自分が試行錯誤を繰り返したコミット履歴もすべて残してある。だが、少しでも資料になるのであれば幸いである。

余談

上記で「GitHub APIを呼び出す部分について」のユニットテストを書けていない問題について、少し言い訳させてほしい。
実はGitHub公式としてモックサーバーを用意してくれているのだが、Node.js 18 LTSとDeno両方において全く動かないのである。
動かないという言い方は語弊があるが、モックサーバーへの呼び出しに対してシナリオがないと返されてしまうのである。もちろんLinux上において何度も試行している。
ここは原因不明なので、もし知っている方がいればコメントなりIssueなり立てて教えていただきたい。

Discussion

ログインするとコメントできます