Lambda で GitHub App を作成し、CodePipeline の実行結果を GitHub に反映させる
Solution Architect の t_maru です。
今回は、以前 CloudFormation で構築した Git-flow 向けの CodeBuild + CodePipeline の定義を CDK を使って書き直し、以前の構成では少し余分にかかってしまっていた CodeBuild の費用を削減する構成に変更してみようと思います。
以前の note 記事と CloudFormation を使ったサンプルは以下となります。
note 記事
https://note.com/build_service/n/ne3aba029dd02?magazine_key=m8220a76aed15
CloudFormation を使ったサンプルコード
https://github.com/t-maru078/code-pipeline-github-webhook
以前の構成の課題
以前紹介していた構成は、できる限り AWS リソースのデプロイだけで利用できるような簡単なものを作ろうというコンセプトで進めていたので、GitHub の Webhook から起動される CodeBuild が Pipeline 処理完了まで起動し続ける構成となっており、Pipeline のワークフローの完了に長い時間のかかる処理の場合は無駄に費用がかかってしまう構成でした。
以下に当時のサンプルの構成例を掲載します。
※ 以前の構成図は S3 から CodePipeline の起動を行う部分をより詳細に記載しました
では、なぜ CodeBuild が起動し続ける必要があったのでしょうか?
これについては、GitHub 側に反映される CI の処理結果の反映が関係してきます。
以下に GitHub の commit history の一部を掲載します。
緑色のチェックマークと赤色の X マークが表示されていると思いますが、こちらが CI/CD 処理の結果を表すものです。
このように GitHub 上で CI の結果が見えるようになっているとコードレビューや merge をする場合に一目で修正が必要なことがわかりますので非常に便利ですし、特に merge 処理においては GitHub の Repository 設定で status check の処理 (今回話題にあげているような CI 処理) が成功しないと merge できないようにする設定もありますので、GitHub を使って開発を進めていく場合にはぜひ導入していただきたい機能です。
以前の構成では、GitHub への Push をトリガーに CodeBuild が起動され、この CodeBuild の処理自体の成功/失敗が GitHub 側の status に反映されるように構成していたため、Pipeline のワークフローが完了するまで CodeBuild が起動している必要がありました。
Lambda と GitHub App で解決する
今回はこの課題を Lambda と GitHub App, Checks を使って解決してみました。
以前の構成はすでに説明した通り、Push で起動される CodeBuild 処理の成功/失敗を GitHub に通知する構成としていましたが、今回は GitHub の Checks を API 経由で呼び出すことで解決します。
GitHub checks の API を呼び出すためには GitHub App が必要になるのですが、細かい仕様については以下の公式ドキュメントを参照してください。
GitHub Apps について
https://docs.github.com/ja/apps/using-github-apps/about-using-github-apps
GitHub checks の REST API 仕様
https://docs.github.com/en/rest/checks?apiVersion=2022-11-28
今回構築した構成での処理の流れを図で整理してみました。
※ リストに括弧書きで記載した丸付きの番号が上記の図と対応しています。
GitHub が Webhook リクエストを送信する (①)
CodeBuild が起動
GitHub App (Lambda) に処理が起動したことを通知 (②)
ソースコードを zip ファイルに圧縮し S3 に PUT (③)
CodeBuild は zip ファイルを S3 に PUT したら処理終了
S3 Event Notification により Lambda が起動 (④)
Pipeline の実行を開始 (⑤)
GitHub App (Lambda) にワークフローが開始したことを通知 (⑥)
CodePipeline でワークフローが実行される
CodePipeline のワークフローが完了し、Event Bridge 経由で event が飛び Lambda が起動(⑦, ⑧)
GitHub App (Lambda) に Pipeline 処理の終了 (success/fail) を通知 (⑨)
以前は AWS 側から GitHub への status 連携は CodeBuild の処理終了の 1 回でしたが、今回は処理開始前、処理中、処理完了の 3 回に分けて状態を GitHub に通知するように変更したため、GitHub の Webhook によりトリガーされる CodeBuild は S3 へのファイル PUT が完了したらその時点で終了できるようになり、必要最低限の起動時間で済むようになりました。
また、上記で登場する GitHub App を今回は Lambda で実装しており、こちらも GitHub へのリクエスト送信に必要なタイミングで実行されるのみですので無駄な費用は発生しません。
GitHub App の作成方法
具体的な構築手順とサンプルコードは記事の最後にリンクを記載しますが、GitHub App の作成方法についてこの記事で補足します。
GitHub App の作成
まず、GitHub の Web console から右上のプロフィールをクリックし表示されるメニューの中から `Settings` をクリックします。
その後、`Developer settings` → `GitHub Apps` → `New GitHub App` という順番で GitHub App の作成ページを開きます。
まずは GitHub App name と Homepage URL に任意の値を設定します。
Homepage URL は作成した GitHub App を一般公開する場合に重要になってくる設定項目ですので、一般公開を予定している場合は意識して設定する必要があります。
次に Webhook の設定ですが、今回は GitHub App としては Webhook イベントの処理は行いませんので `Active` のチェックボックスを外しておきます。
(今回の構成の場合 CodeBuild の設定で GitHub の Webhook 設定を行っており、Webhook イベントを受け取るのは CodeBuild です)
Permissions の項目では、`Repository permissions` のうち、`Checks` の権限を `Read and write` に変更します。今回の GitHub App は Checks API のみ使用予定ですので、権限はこれだけで大丈夫です。
最後に GitHub App がインストールできる対象を選択しますが、今回は一般公開せずに使用しますので `Only on this account` のみとしておきます。
以上までの設定ができたら `Create GitHub App` ボタンを押して GitHub App を作成します。
作成できると画面上部 `About` セクションの `App ID` の箇所にこの GitHub App の ID が表示されます。AWS 上でインフラをデプロイする際の設定値として使用しますのでメモしておいてください。
Private key の作成
GitHub App の作成が終わりましたら、自動的に作成した App の設定画面に遷移すると思いますので、設定ページ下方の `Private keys` というセクションにて `Generate a private key` ボタンを押して、この GitHub App 用の Private key を生成します。
このファイルについても AWS 上にインフラをデプロイする際に Secrets Manager に登録して使用しますので、なくさないように保管します。また、この Private key ファイルを使うと作成した GitHub App として GitHub の処理を実行できるようになりますので、Git commit したり他人に渡したりしないようにご注意ください。
GitHub App の install
下記に作成した GitHub App のインストール手順に関する公式ドキュメントのリンクを掲載します。
https://docs.github.com/ja/apps/using-github-apps/installing-your-own-github-app
一度インストールした後は以下の手順にて、インストール先 Repository の変更などが可能です。
GitHub の Web console の右上のプロフィールから `Settings` メニューをクリック
サイドメニューから `Applications` を選択
作成した GitHub App の `Configure` ボタンをクリック
ここまでで GitHub 側での作業は完了となり、あとは CDK で定義した AWS リソースをデプロイするだけとなります。(デプロイ前に 1 手順のみ AWS Management Console で作業が存在します)
Lambda の実装
今回の Lambda は Node.js で作成し、Function URL の設定を行いました。
Function URL を使用して外部から直接 Function を呼び出せるようにしているのは、今後の改修により GitHub から Webhook イベントを受け取る可能性があるためです。
とはいえ、今回は AWS リソース上からのみのアクセスなので Function URL の auth type は AWS_IAM を設定し、第三者からの呼び出しが行われないように設定を行いました。今後の改修により GitHub からの Webhook を GitHub App が受け取る場合には認証周りについても再考が必要です。
※ Lambda Function URL の auth type を AWS_IAM で設定した場合のリクエスト認証方法については別の記事でご紹介します。
さて、Lambda から GtiHub の API を操作するにあたり GitHub API の使用に合わせたリクエストを送る必要があるのですが、独自実装でアクセストークンを生成しリクエストに付与するのは一苦労ですので、ここはライブラリの力を借ります。
GitHub のドキュメントの中でも紹介されているものですが、今回は下記のライブラリを利用します。
https://github.com/octokit/octokit.js
以下に、GitHub Check の Run を queued 状態で新たに登録するために必要な処理を、上記のライブラリを使って作成した例を記載します。
interface CreateCheckData {
name: string;
head_sha: string;
status: RunStatus;
}
const createCheck = async () => {
const app = new App({
appId: 'GitHub App の ID',
privateKey: 'GitHub App の private key',
});
// GitHub App の installation id
const installationId = 12345;
const owner = 'GitHub Repo の owner';
const repo = 'GitHub Repository';
const data: CreateCheckData = {
name: 'Check の名前(任意の文字列を設定)',
head_sha: 'commit hash など',
status: 'queued',
};
const octokit = await app.getInstallationOctokit(installationId);
const response = await octokit.request({
url: `https://api.github.com/repos/${owner}/${repo}/check-runs`,
method: 'POST',
data,
});
console.log(`create check response: ${JSON.stringify(response, null, 2)}`);
};
実際に GitHub にリクエストが送られる際にはアクセストークンなどが計算されて、必要なリクエストヘッダーを付けた状態でリクエストが行われますが、上記のように GitHub App ID や Installation ID など事前に取得な必要なパラメータを揃えておくだけで GitHub へのリクエストを実行することができます。
これ以上詳しい実装方法とここから先のデプロイ手順については本記事の末尾に記載するサンプルコードの README を参照ください。
まとめ
以前紹介した CloudFormation で Git-flow に対応した CodePipeline の定義において、GitHub へ CI/CD の進行状況と結果を送信する方法が CodeBuild から直接返却する方式となっていたため、CodeBuild をワークフロー終了まで起動しておく必要がありましたが、Lambda を使って GitHub App を作成し、S3 Event Notification や EventBridge の Rule を活用することで GitHub への CI/CD 結果の連携をイベント駆動に変更しました。
GitHub App としての Lambda 処理は必要最低限のものをサンプルとして用意しました。実際に開発で運用していくためには細かいカスタマイズが必要になってくると思いますが、CodePipeline の処理状況/処理結果を GitHub に反映する手段の 1 つとして今回ご紹介した方法がどなたかの助けになれば幸いです。
下記にリポジトリのリンクを掲載しておりますが、今回はすべてのリソースを CDK を使って定義し直しました。
CDK 版 (今回のサンプル)
https://github.com/t-maru078/code-pipeline-github-webhook-cdk
CloudFormation 版
https://github.com/t-maru078/code-pipeline-github-webhook