この記事ははてなエンジニア Advent Calendar 2021の23日目の記事です。
昨日は @polamjag によるDevTools の Web 技術でできている部分を覗き見るでした。
今回はGithub Checks APIを活用して、プルリクエスト上でリリースまでに必要なテストを正常に終えているか確認できるようにするレシピを考えてみました!実際に手を動かして動かしてみたいので、モデルケースを想定し、そのケースに登場するプルリクエストで活用することを考えてみます。今回メインに説明したいのは、GitHub Actions
周りであるのですが、AWS EventBridge
に絡めると面白いのでその内容に少し触れています。
モデルケース: 今回考えるリリースまでの流れ
モデルケースではAWS Cloud
にある本番環境にデプロイする1段階前に、本番とほぼ同様の検証環境のAWS Cloud
上のステージング環境にデプロイして動作確認をするケースを考えます。このモデルケースは以下の流れをたどります。
-
GitHub
上の特定のブランチstaging
に対してプルリクエストを立てる - ステージング環境に
staging
ブランチの内容をデプロイし、その環境に対して自動テストをする - 自動テストの結果が全て正常であれば、メインブランチ
main
にstaging
ブランチをマージする - 本番環境に、
main
ブランチの内容をデプロイする
ここでstaging
環境は検証用環境なので開発者以外はアクセスできません。
さらに、main
ブランチにstaging
ブランチをマージするのは、テストが正常である場合に限りたいので、GitHub
のブランチの保護設定を有効にします。往々にして、GitHub
ではプルリクエストのコードに対するテストの実行にはGitHub Actionsを活用でき、そのテストが正常な時に限りマージ可能とすることができます。このモデルケースではGitHub Actions
で種々のテストを実行しており、どのテストが通ったか通っていないかプルリクエスト上で確認できるものとします。GitHub Actions
で容易に実施できるテストとして、プルリクエスト上のコードの静的解析や、コードがビルド可能かなどが挙げられます。
`GitHub Actions`でどのテストが通っているかプルリクエストで見て、全部通っていた場合に限りマージしたい…… https://github.co.jp/features/actions
このようにGitHub
はプルリクエストに対するテストの状態を持たせることができ、テストが成功か失敗か実行中か、予定中かなどを表現できます。この状態をCheck Runsといい、各コミットに複数紐づいています。特にプルリクエストの最新のコミットのCheck Runs
はプルリクエストに紐づいています。Check Runs
はプルリクエストで確認することができるので、そのプルリクエストが妥当か否かを確認するのに便利なのはもちろん、前述のブランチの保護
の条件にも利用できます。
ここまでイラストにすると次のように表せます。
このモデルケースでは、自動テストやブランチの保護
を駆使してリリースにおいて本番環境の内容であるmain
ブランチが常に正常に動くものであるということ保証しようとしています。
ただし、今回のモデルケースでは次の課題を抱えていると仮定します。
モデルケースにおける課題
今回は何らかの理由でステージング環境に対する自動テストがGitHub Actions
で実行ができないと仮定します。例えば、ステージング環境にGitHub Actions
からアクセスできなかったり、ステージング環境に対する自動テストのコストがGitHub ACtions
では高い場合などで、この仮定が現実のものになるでしょう。
この仮定のため、ブランチの保護
ではステージング環境に対する自動テストが通っていなければstaging
ブランチのmain
ブランチへのマージを不可にすることができないという問題を抱えています。
迂回策として、ステージングの自動テスト結果をエンジニアの目視して、それが良しならばマージをするという手段を取れます。とはいえ、大抵のテストの状態がプルリクエストで確認できる状況ならば、プルリクエストで確認したいところです。
解決策
今回試してみる解決策は、GitHub Actions
上でGitHub Checks APIを利用してCheck Runs
をステージング環境に対するテストに対応するように制御し、Check Runs
の成功を以ってブランチへのマージが可能となるブランチの保護
を有効にすることです。
構成
Checks Runs
の作成
まずはユーザーがstaging
ブランチのプルリクエストにプッシュした時に、ステージング環境へのテストが保留されている状態になるようにChecks Runs
を更新するところから始めます。
GitHub Actions
で特定のブランチへのプッシュ起動で、最新コミットにChecks Runs
を紐付けて、その状態を保留にするには、LouisBrunner/checks-actionを使って次のようなGitHub Actions
を記述します。これによってstaging
ブランチへのプッシュのたびにstaging-test
という名前のChecks Runs
が作成されます。
name: staging-test-actions
on:
push:
branches:
- staging
jobs:
# プッシュされた時はqueueにする
init:
if: github.event_name == 'push'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2.3.4
- uses: LouisBrunner/checks-action@v1.1.1
with:
token: ${{ secrets.GITHUB_TOKEN }}
name: staging-test
status: queued
Checks Runs
の更新
次にGitHub Actions
外でステージング環境の自動テストの実行が完了次第、成功か失敗かの結果を先ほど作ったChecks Runs
に反映させます。GitHub Actions
を外部から実行するにはrepository_dispatchを利用できます。今回はAWS EventBridgeでイベントを受け取り、それをトリガーにAWS Lambda
を実行します。
まず、GitHub Actions
側に成功か失敗か受け付けるrepository_dispatch
トリガーを追加し、Check Runs
を更新できるようにします。更新のために、名前が"staging-test"
で最新のCheck Runs
のIDをGitHub Actions Scriptで記述していることに注目してください。
name: staging-test-actions
on:
push:
branches:
- staging
repository_dispatch:
types: [success, failure]
jobs:
# プッシュされた時はqueueにする
init:
if: github.event_name == 'push'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2.3.4
- uses: LouisBrunner/checks-action@v1.1.1
with:
token: ${{ secrets.GITHUB_TOKEN }}
name: staging-test
status: queued
# プッシュではない時の処理
update_checks:
if: github.event_name != 'push'
runs-on: ubuntu-latest
steps:
# 最新のCheck RunsのIDを取得する
# 最新のCheck RunsのIDは${{steps.get-staging-test-check-runs-id.outputs.result}}にstring形式で格納される
- uses: actions/checkout@v2.3.4
- name: Return issues
uses: actions/github-script@v2
id: get-staging-test-check-runs-id
with:
script: |
// github-scriptでnameがstaging-testのCheck RunsのIDの一覧を新順で取得する
const list = await github.checks.listForRef({
owner: context.repo.owner,
repo: context.repo.repo,
ref: 'staging',
check_name: 'staging-test',
filter: 'latest'
});
return list.data.check_runs[0].id;
result-encoding: string
# repository_dispatchの結果を最新のCheck Runsに反映する
- uses: LouisBrunner/checks-action@v1.1.1
if: github.event_name == 'repository_dispatch'
with:
token: ${{ secrets.GITHUB_TOKEN }}
check_id: ${{steps.get-staging-test-check-runs-id.outputs.result}}
status: completed
# successならばsuccessにし、それ以外はfailureにする
conclusion: ${{ (github.event.action == 'success' && 'success' ) || 'failure' }}
GitHub Actions
のrepository_dispatch
トリガー
次に、GitHub Actions
のrepository_dispatch
トリガーを呼び出します。少しAWS EventBridge
に絡めて具体的に考えてみます。今回のステージング環境への自動テストをAmazon CloudWatch Syntheticsを使ってスクリーンショットを撮影するテストとでもしましょう。Amazon CloudWatch Synthetics
のCanary
をstaging-snapshot-test
と命名して、これの成功あるいは失敗に反応するAWS EventBridge
のルールを記載すると以下のようになります。
{
"detail-type": [
"Synthetics Canary TestRun Successful",
"Synthetics Canary TestRun Failure"
],
"source": [
"aws.synthetics"
],
"detail": {
"canary-name": [
"staging-snapshot-test"
]
}
}
最後に、EventBridge
で渡ってきた成功失敗かの情報をGitHub Actions
に投げるLambda
を記述すればシステムの完成です。次のコードでは、GitHub API
のクライアントライブラリとして@octkit/restを利用し、さらに、AWS Secrets ManagerにGitHub API
を実行可能なGitHub
のトークンが"repository-token"
のTOKEN
フィールドに保存されているものとします。このLambda
のトリガーとして先ほど作成したEventBridge
を設定すると、CloudWatch Synthetics Canary
が成功、あるいは失敗するたびに、次のコードが実行されます。
const AWS = require("aws-sdk");
const { Octokit } = require("@octokit/rest");
const SecretId = "repository-token";
const region = "ap-northeast-1";
const owner = "repository-owner";
const repo = "SomeRepository";
exports.handler = async (event) => {
const detailType = event["detail-type"];
// Secret ManagerからGitHubのToken取得
const client = new AWS.SecretsManager({ region });
const data = await client.getSecretValue({ SecretId }).promise();
const auth = JSON.parse(data.SecretString).TOKEN;
const octokit = new Octokit({ auth });
const getResult = async () => {
const event_type =
event["detail-type"] === "Synthetics Canary TestRun Successful"
? "success"
: "failure";
return await octokit.rest.repos.createDispatchEvent({
owner,
repo,
event_type,
});
};
const { status } = await getResult();
const body = JSON.stringify({ status, detailType });
const response = {
statusCode: 200,
body,
};
console.log(body);
return response;
};
これで、staging
ブランチにプッシュされた後に、ステージング環境への自動テストたるCloudWatch Synthetics Canary
が実施されない限り、テストステータスが保留となります。また、CloudWatch Synthetics Canary
が実施され、それが成功か失敗すると、プルリクエストに結果が反映されるようになりました。ここで、ブランチの保護
を有効にすると、晴れてmain
ブランチにテストが失敗するstaging
ブランチをマージ不可にできます。
ここまでで、表題のGitHub Checks APIをGitHub Actionsで使う!AWS EventBridgeの結果をGitHub Checksに反映させる!を実現できました。
終わりに
はてなエンジニア Advent Calendar 2021の明日は @nabeop Hatena Developer Blog 編集部の活動の紹介です。
今年はえらい大雪でこれ書いているうちも、屋根雪が切れたりしていてえらいことなっております。
参考文献