はじめに
こんにちは、最近はCDKを触る機会がやや多く、Terraformとどっちを使うのが良いのかやや悩み気味な宮本です。
今回の記事はAWS Amplifyのビルド通知についてです。
Amplifyのビルド通知
※以下の内容は全て2024/02/26現在の情報を元にしています
簡単にサイトを立ち上げることのできるAmplifyですが、ビルドする際の通知が貧弱という問題があります。
- デフォルトで用意されているのはEメールによる通知のみ
 - Eメールで送ることのできる通知内容が物足りない
 - Eメール通知もCDKやTerraformのようなIaCで管理する方法が公式で説明されていない
 
通知内容が物足りないという点ですが、実際に送られてくるのは以下のようなメールです。
Build notification from the AWS Amplify Console for app: https://<ブランチ名>.<app id>.amplifyapp.com/. Your build status is <ビルドステータス>. Go to https://console.aws.amazon.com/amplify/home?region=<リージョン>#<app id>/<ブランチ名>/<ビルド番号> to view details on your build.
Amplifyのビルド結果メール通知の例
メールに含まれている内容はapp idとブランチのみで、ぱっと見でどの環境かまではわかりません。複数のAmplifyによるアプリケーションを管理している場合は、もはや何がなんだかわかりません。URLがついているのでアクセスすればわかりますが、同じアプリの別ブランチの差であれば瞬時に見抜くのは難しいです。他にも、ビルドに失敗した場合であればコンソールのエラー内容も欲しいかもしれません。
Amplifyのビルド通知をSlackに送る
※重ねて注意。以下の内容は公式ドキュメントになく、仕様が変更される可能性は十分にあるのでご注意ください。
先に結論
- Amplifyのコンソールから設定できるEメールによるビルド通知はEventBridge+SNSで実装されている
- コンソールで通知を作成した場合は必要なリソースが自動で作成される
 
 - EventBridgeを用意すれば、そのまま直でLambda経由でSlackに通知することができる
- IaCで通知リソースを管理することもできる
 - アプリ名などAmplifyの作成するイベントに含まれないデータは、Lambda内で別途データを取得する必要がある
 
 
Amplifyのビルド通知
デフォルトだとAmplifyのビルド通知はコンソールからのみ作成できます。その際にAmplify自体に通知機能があるわけではなく、別のAWSサービスを利用してビルド通知を実現しているようです。なので、Amplify自体のTerraformやCDKに通知周りの設定がないんですね。
ビルド通知を設定した際に自動で作成されるリソースは次のようなものです。
- Amazon EventBridge ルール: amplify-<app_id>-<branch名>-branch-notification
- ブランチ指定しない場合: amplify-<app_id>-AMPLIBRANCHSENTINEL-branch-notification
 
 - Amazon SNS トピック: amplify-<app_id>_<branch名>
- ブランチ指定しない場合: amplify-<app_id>_AMPLIBRANCHSENTINEL
 
 
EventBridgeのドキュメントにも記載はないのですが、どうもAmplifyのビルド時にEventBridgeで引っ掛けることのできるイベントが配信されているようです。作成されるEventBridgeのルールを見ると、以下のようなイベントパターンを指定しています。ブランチを指定しない場合は、detailのbranchNameの項目自体がありません。
| 
					 1 2 3 4 5 6 7 8 9  | 
						{   "detail": {     "appId": ["<app_id>"],     "branchName": ["<ブランチ名>"],     "jobStatus": ["SUCCEED", "FAILED", "STARTED"]   },   "detail-type": ["Amplify Deployment Status Change"],   "source": ["aws.amplify"] }  | 
					
この条件で引っ掛けたイベントを元に入力トランスフォーマーを使ってメール文面を作成し、SNS経由で送っています。
なお、引っ掛けているイベントの全体像は以下のようになっています。残念ながら、Amplifyのアプリ名までは入っていません。そのため、アプリ名を入れたい場合はLambdaを挟んでそこで別途データを取得する必要があります。
| 
					 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18  | 
						{     "version": "0",     "id": "<イベントid>",     "detail-type": "Amplify Deployment Status Change",     "source": "aws.amplify",     "account": "<AWSアカウントid>",     "time": "2024-01-01T00:00:00Z",     "region": "ap-northeast-1",     "resources": [         "<Amplifyのarn>"     ],     "detail": {         "appId": "<app id>",         "branchName": "<ブランチ名>",         "jobId": "<ビルド id>",         "jobStatus": "<ステータス>"     } }  | 
					
Amplifyのビルド通知をSlackに送るために必要なリソースのCDK
さて、Amplifyのビルド通知の仕組みがEventBridgeが元になっていると分かればこっちのものです。以下はCDKの場合ですが、このようなEventBridgeのリソースを作成することでイベントをLambdaに送ることができます。ビルド開始時の通知が不要な場合はパスパターンから STARTED を省くなど、工夫のしがいがあります。(なおlambdaのコード周り含めたリソース定義をここで書くと長くなるので省略します。)
| 
					 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48  | 
						from aws_cdk import (     aws_iam as iam,     aws_lambda as _lambda,     aws_events as events,     aws_events_targets as targets,     Stack, ) from constructs import Construct class BuildNotificationStack(Stack):     """指定したLambdaにAmplifyのビルド通知を送るEventBridge"""     def __init__(self, scope: Construct, id: str, **kwargs) -> None:         super().__init__(scope, id, **kwargs)         app_id_list = ["hogehoge"]  # 通知したいAmplifyのidリスト         notification_brunch_list = ["master"]  # ビルド通知したいブランチのリスト         function = _lambda.Function(self, "NotificationLambda", ...)  # 通知用のLambdaを作成(細かいコードは省略)         # 以下の権限はLambdaから追加でAmplifyのデータを取得する場合に必要         function.add_to_role_policy(             iam.PolicyStatement(                 actions=[                     "amplify:GetApp",                 ],                 resources=["*"],             )         )         # EventBridgeからLambdaを起動するために必要な権限         function.add_permission(             "Eventbridge", principal=iam.ServicePrincipal("events.amazonaws.com")         )         # appIdに複数のidを指定することで、一つのEventBridgeで複数のAmplifyのビルド通知を受け取ることも可能         notification_rule = events.Rule(             self, "NotificationEventRule",             event_pattern={                 "detail": {                     "appId": app_id_list,                     "branchName": notification_brunch_list,                     "jobStatus": ["SUCCEED", "FAILED", "STARTED"]                 },                 "detail_type": ["Amplify Deployment Status Change"],                 "source": ["aws.amplify"]             },         )         build_notification_target = targets.LambdaFunction(function)         notification_rule.add_target(build_notification_target)  | 
					
Slackに通知を送るLambda
EventBridgeから叩かれた際、Lambdaはハンドラーの第一引数に入るeventとしてAmplifyから発行されたイベントがそのまま辞書形式で入ってきます。前述の通りAmplifyのアプリ名まではイベントに入っていないため、必要な場合はLambdaでアプリの情報を取得しなければいけません。
以下は、SlackのWebhook経由でビルド通知を送るLambdaの一例です。この例のメッセージ周りはだいぶ簡素なので、ビルドステータスによって必要な情報を増やしたり(e.g. 失敗した時のみコンソールへのURLを追加する)、通知文自体も見やすいようにカスタマイズしていくとわかりやすくなると思います。
| 
					 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37  | 
						import os import json import boto3 import requests from aws_lambda_powertools import Logger logger = Logger() # Lambdaの環境変数SLACK_WEBHOOK_URLにあらかじめSlackのWebhookを登録しておく SLACK_WEBHOOK = os.environ["SLACK_WEBHOOK_URL"] status_message = {     "STARTED": "ビルドを開始します",     "SUCCEED": "ビルドに成功しました",     "FAILED": "ビルドに失敗しました", } @logger.inject_lambda_context def lambda_handler(event, context):     try:         logger.info("AmplifyのApp名取得")         amplify_client = boto3.client("amplify", region_name="ap-northeast-1")         # eventに入っていないAmplifyのアプリ名をboto3を使って取得する         # lambdaのroleに「amplify:GetApp」の権限が必要         response = amplify_client.get_app(appId=event["detail"]["appId"])         app_name = response["app"]["name"]         logger.info("Slack通知")         data = json.dumps({"text": f"{app_name}の{event['detail']['branchName']}の{status_message[event['detail']['jobStatus']]}"})         response = requests.post(             SLACK_WEBHOOK, data, headers={"Content-Type": "application/json"}, timeout=3         )         return None     except Exception as e:         logger.exception(e)         return None  | 
					
通知のサンプル

おわりに
今回はAmplifyのビルド通知をSlackに流す方法について紹介しました。いくつか似たような方法を紹介していた記事はあったのですが、基本的にAWSのリソースは全てIaC管理しておきたい病にかかっているので、CDKで管理できるようにしました。リソース的にはEventBridgeとLambdaおよびそれに必要なIAM Roleを用意しているだけなので、Terraformでも問題ありません。
また、今回はLambdaを使いましたが、もともと出力されるイベントに含まれる内容自体で十分であれば、EventBridgeから直にchatbotに送ることもできるようです。Lambdaを挟むとどうしてもランタイムのEOLによる継続的なメンテが必要になったりするので、不要であれば省きたいです。しかし、利用できるデータがどうしても限られてしまう点は現時点では解消できないため、細かくカスタマイズするのであればLambdaはまだ必要そうです。
他にも失敗したい場合はエラーログなども出せるとありがたいのですが、そもそもAmplifyのビルド時のログはCloudWatchにすら吐き出されないため現状打つ手が無さそうです。
            

