aws CodeBuildとAmazon SNSを組み合わせて単体テストエラー時にメールする

# はじめに
aws上でCI環境を構築するため、aws CodeBuildを使って単体テストを実行し、テスト結果がエラーの場合メールを送信します。
※本記事は以下記事の続きとして書いていますが、aws CodeBuildを使ったことがあればなるべく理解できるように書いています
[aws CodeBuildを使ってlambda(node.js)環境をテスト→デプロイする](https://qiita.com/kyuaz/items/604d11b8af1921112f99)

# CodeBuildでやりたい処理
CodeBuildで以下処理を行ないます。

- リポジトリからソースを取得
- 単体テストを実行する
- 単体テスト結果がエラーだった場合、amazon SNSを使ってメールを送信する

システムの全体図は以下のようになります。

![スクリーンショット 2018-11-21 18.18.09.png](https://qiita-image-store.s3.amazonaws.com/0/191873/27e19cb6-4c4f-3752-7d58-4e2c5acb58c5.png)


作業の流れは以下になります。

1. Amazon SNSの設定
2. CodeBuild用のIAM Roleを作成
3. CodeBuildのbuildspec.ymlを作成
4. CodeBuildプロジェクトの実行


# Amazon SNSの設定

公式サイトにある通り、Amazon SNSとはサブスクライブしているエンドポイントまたはクライアントへのメッセージの配信または送信を調整し、管理するウェブサービスです。例えば事前にサブスクライブ（登録）している人達に対して、一斉にメールを送信することができます。それをメールサーバとか建てることなしに、amazonのフルマネージドサービスとしてやってくれます。

今回はAmazon SNSを使って、単体テストエラー時にメールを送信するようにします。

イメージとしてはトピックを作成して、そのトピックに色々な人のメアドをサブスクライブ（登録）してもらいます。そうすると、そのトピックにメッセージを投稿するたびに、登録されたメアドに一斉送信されます。

ではSNSの設定手順について説明します。


Amazon SNSコンソールを開いて、
![スクリーンショット 2018-11-22 9.23.10.png](https://qiita-image-store.s3.amazonaws.com/0/191873/7747347b-fb13-caf6-18a7-e0c591655587.png)
トピック→新しいトピックの作成を選択。

![スクリーンショット 2018-11-22 9.25.12.png](https://qiita-image-store.s3.amazonaws.com/0/191873/4d722d7b-32d8-2e14-38d0-755e8020103b.png)
任意のトピック名と表示名を入力。

![スクリーンショット 2018-11-22 9.39.57.png](https://qiita-image-store.s3.amazonaws.com/0/191873/b8ecf37e-1e84-dd27-f39d-5297ac3e733a.png)
トピックが作成されました。ここに表示されているトピックのARNは、メッセージを投稿する時に必要になります。

![スクリーンショット 2018-11-22 9.26.50.png](https://qiita-image-store.s3.amazonaws.com/0/191873/94f511c4-7b56-4a7c-4dd6-0ebe0251f6da.png)
作成したトピックにチェックを入れて、アクション→トピックへのサブスクリプションを選択。

![スクリーンショット 2018-11-22 9.27.14.jpg](https://qiita-image-store.s3.amazonaws.com/0/191873/6c259038-da43-20a5-22d5-5159e9a88bbe.jpeg)
プロトコルでEmailを選択、エンドポイントにテスト結果を送信したいメアドを入力。

![スクリーンショット 2018-11-22 9.27.49.png](https://qiita-image-store.s3.amazonaws.com/0/191873/0c5296d6-7bf8-7ea6-6162-1e3b44fceefd.png)
サブスクリプションの項目を開くと、PendingConfirmationとなっているのが分かる。これはメールの承認をまだ行なっていないためなので、受信しているはずの承認メールを開いて「Confirm subscription」をクリックする。

![スクリーンショット 2018-11-22 9.29.00.jpg](https://qiita-image-store.s3.amazonaws.com/0/191873/d0790e36-fe63-0c3e-7738-fcf32e7cfdfa.jpeg)
さきほどPendingConfirmationとなっていた所が、サブスクリプションARNが設定されていてサブスクライブできたことが確認できます。

以上で、Amazon SNS側の設定は終わりです。

# CodeBuild用のIAM Roleを作成

次にCodeBuild用のIAM Roleを作成します。これはCodeBuildで単体テストを実行し、CodeBuildからAmazon SNSのトピックに対してアクセスできるようにアクセス権を付与するためです。

IAMコンソールを開きます。

![スクリーンショット 2018-11-21 17.42.25.png](https://qiita-image-store.s3.amazonaws.com/0/191873/bafcc197-bb3d-8578-8936-7dbd75bada7f.png)
新規に（あるいは既にCodeBuild用のRoleを作成済みの場合はその）Roleを用意し、そのRoleに権限を追加します。今回は既にあるRoleForCodeBuildというRoleに新たに権限を追加するためポリシーを作成します。

![スクリーンショット 2018-11-21 17.42.43.png](https://qiita-image-store.s3.amazonaws.com/0/191873/5fe355cb-c7f7-ec9d-72a1-8b676882f221.png)
ポリシーの作成画面で、

![スクリーンショット 2018-11-21 17.43.24.png](https://qiita-image-store.s3.amazonaws.com/0/191873/25de0ece-1a01-edcc-dc16-003e0b11d2b8.png)
サービスはSNS、アクションはPublish、リソースはすべてのリソースを選択します。

![スクリーンショット 2018-11-21 17.43.48.png](https://qiita-image-store.s3.amazonaws.com/0/191873/a2fba03a-0e67-fc72-33e5-294e1ac5becc.png)
ポリシーの名前はSnsPublishPolicyとでもしておきます。

以上で、SNSにpublishできるポシリーを追加したCodeBuild用のIAM Roleが作成できました。
これでCodeBuildからAmazon SNSトピックに対して投稿(publish)するための準備が整いました。

※コンソールでのIAMロール(ポリシーの追加)の作成はこのような手順でできます。CodeBuildを使って色々なサービスと新たに連携したり、デプロイするたびに同様に必要なポリシーを追加していくことになります。

# CodeBuildのbuildspec.ymlを作成
始めに簡単な例で説明し、次により実用的な例を示します。

## CodeBuild実行時にテスト結果がエラーならメールを送信する
CodeBuildのプロジェクト作成の流れは以下記事を参照してください。
[aws CodeBuildを使ってlambda(node.js)環境をテスト→デプロイする](https://qiita.com/kyuaz/items/604d11b8af1921112f99)

CodeBuildのbuildspec.ymlは以下の通りになります。

```yaml:buildspec.yml
version: 0.2

phases:
  install:
    commands:
      - npm install
  pre_build:
    commands:
      - sh prj_test.sh
```

prj_test.shは、テストを実行し、その結果に応じてamazon SNSにpublishするシェルスクリプトです。

```bash:prj_test.sh
npm test
if [ $? = 0 ]; then
    echo "npm test ok."
else
    echo "npm test failed. sending email."
    aws sns publish --topic-arn arn:aws:sns:ap-northeast-1:1234567890:helloworld_topic --subject "単体テスト結果エラー" --message "ただちにエラーを修正するか、エラー発生前のコミットに戻してください"
    exit 1
fi

```
テストが正常に終われば、"npm test ok."とechoして正常終了。テスト結果がエラーだった場合、aws cliを使ってamazon snsにpublishしています。この時に上記amazon SNSコンソールで作成したトピックのtopic ARNを指定します。

aws cli snsのリファレンスは以下参照
https://docs.aws.amazon.com/ja_jp/cli/latest/userguide/cli-sqs-queue-sns-topic.html

以上でとりあえずテストエラー時にメールが飛ぶようにすることができました。しかし、まだこれではどの（誰の）コミットでエラーになったのか分かりません。またCodeBuld単体だと手動で実行する必要があり、CIを実現するためにはリポジトリへコミット(push)される度に毎回必ず自動的にテストが実行されてほしいです。

では、どうやってやるか？ 
以下の方法で実現します。

- pushされる度にCodeBuld実行 →　CodePipelineの使用
- メール内容にコミットに関する情報を追加 → CodeCommitにaws cliでアクセスして情報を取得

## CodePipelineとCodeBuildを組み合わせ、コミット時にテストを自動実行し、エラーになったコミット情報をメールで送信する

完成後の全体のイメージは以下になります。
![スクリーンショット 2018-11-28 11.01.15.png](https://qiita-image-store.s3.amazonaws.com/0/191873/256fe260-2cae-4eda-61d1-f65b501816ce.png)



まずはCodePipelineを設定します。

### CodePipelineの設定

CodePipelineコンソールを開き、パイプラインを作成。

![スクリーンショット 2018-11-28 10.31.34.png](https://qiita-image-store.s3.amazonaws.com/0/191873/5ae5e5bb-5b16-725b-c8f5-74795d01b8e0.png)
パイプライン名を入力して、あとはデフォルトの設定のまま次へ。

![スクリーンショット 2018-11-28 10.32.08.png](https://qiita-image-store.s3.amazonaws.com/0/191873/72292c4f-7738-bfe8-fc46-68547d6781c1.png)
ソースプロバイダはCodeCommitを選択し、使用しているリポジトリ名とブランチを指定。変更検出オプションはCloudWatch Events。このように設定することで、指定したブランチに変更が検出されると本パイプラインが発動することになる。変更の検出は当然プルリクエストを承認してマージした場合も該当する。

そのため、例えば運用上1つ1つのコミットでcodepipelineを走らせたくない場合は、ここで指定したブランチとは異なるブランチで作業をして、codepipelineを走らせたくなったら、指定したブランチにマージするといったやり方もできる。

![スクリーンショット 2018-11-28 10.32.26.png](https://qiita-image-store.s3.amazonaws.com/0/191873/7471d045-6ed9-61ec-670e-a9ada33c21bf.png)
ビルドステージはCodeBuildを選択し、CodeBuildで作成したプロジェクトを選択する。

![スクリーンショット 2018-11-28 10.32.45.png](https://qiita-image-store.s3.amazonaws.com/0/191873/9db7f57e-a2b9-db92-cea2-9a4663052f2d.png)
デプロイステージはスキップする。なおデプロイステージ自体をスキップしたとしても、デプロイしたい場合はCodeBuildのプロジェクト内でaws cli経由でcloudformationを使ってデプロイ可能。

以上でCodePipelineは作成できました。これでCodeCommitに変更をプッシュする度に、自動的にCodeBuildのビルドが走ります。

### テストエラー時のコミット情報を取得する

次に、テストがエラーになった際のコミット情報を取得します。

CodeBuildで行ないます。CodeBuildでは環境変数が設定されていて、そこからコミットIDを取得できます。CODEBUILD_RESOLVED_SOURCE_VERSIONという変数です。

CodeBuildの環境変数
https://docs.aws.amazon.com/ja_jp/codebuild/latest/userguide/build-env-ref-env-vars.html

$CODEBUILD_RESOLVED_SOURCE_VERSIONを参照すればコミットIDが分かります。コミットIDが分かればaws cliのaws codecommitコマンドを使って指定したコミットIDのコミット情報を取得することができます。またコミットIDが分かれば、codecommitの当該コミットのURLも一意に生成することができます。

aws cli codecommitで可能な操作は以下参照
https://docs.aws.amazon.com/ja_jp/codecommit/latest/userguide/how-to-view-commit-details.html#how-to-view-commit-details-cli-commit

上記を踏まえ、以下の手順で実施します。

- CodeBuildがCodeCommitからコミット情報を取得できるようにCodeBuild用のRoleにアクセス権を付与
- CodeBuildが実行するスクリプトにaws cli(aws codecommit)でコミットIDを指定してコミット情報を引っ張ってくる記述を追加
- 取得したコミット情報から必要な情報をjqコマンドで抽出してメール本文に含める

RoleForCodeBuildに以下ポリシーを追加します。
AWSCodeCommitReadOnly

続いて上述したスクリプトファイルを以下のように変更します。

```bash:prj_test.sh
npm test
if [ $? = 0 ]; then
    echo "npm test ok."
else
    echo "npm test failed. sending email."
    echo $CODEBUILD_RESOLVED_SOURCE_VERSION
    RES=$(aws codecommit get-commit  --repository-name helloworld-repo  --commit-id $CODEBUILD_RESOLVED_SOURCE_VERSION)
    echo $RES
    dpkg -l jq
    if [ $? = 1 ]; then
        apt-get update
        apt-get install -y jq
    fi
    COMMITTER_NAME=$(echo $RES | jq '.commit.committer.name')
    COMMITTER_EMAIL=$(echo $RES | jq '.commit.committer.email')
    COMMITTER_MESSAGE=$(echo $RES | jq '.commit.message')
    aws sns publish --topic-arn arn:aws:sns:ap-northeast-1:1234567890:helloworld_topic --subject "単体テスト結果エラー:${COMMITTER_NAME}, ${COMMITTER_MESSAGE}" --message "以下のコミットでエラーが発生しています。修正お願いします。
    コミットID:${CODEBUILD_RESOLVED_SOURCE_VERSION}
    リンク:https://ap-northeast-1.console.aws.amazon.com/codesuite/codecommit/repositories/helloworld-repo/commit/${CODEBUILD_RESOLVED_SOURCE_VERSION}?region=ap-northeast-1
    コミットメッセージ:${COMMITTER_MESSAGE}
    コミット者:${COMMITTER_NAME}
    連絡先:${COMMITTER_EMAIL}"
    exit 1
fi

```
aws cliのcodecommit get-commitで取得されるコミット情報はjson形式で返ってきます。jsonを処理するためにjqコマンドを使うことにします。

デフォルトのCodeBuildのdocker imageだとjqコマンドが入っていなかったので、jqコマンドをインストールしています（2018/11/28時点）。ただしこれだとビルドの度にinstallが走ってしまうので、自分でデフォルトのdocker imageにjqコマンドをインストールしたカスタムdocker imageを作成して使用するか、jqコマンドを使わずに別のコマンドでjsonを処理する方がよいと思います。

codecommit get-commitで返ってくる値の仕様詳細については以下参照。
https://docs.aws.amazon.com/ja_jp/codecommit/latest/userguide/how-to-view-commit-details.html#how-to-view-commit-details-cli-commit

今回はコミットした人の名前、メアド、コミットメッセージをjqコマンドで抽出しています。
抽出した情報をsns publishコマンド内のsubjectとmessageに埋め込めば、当該コミットに関する情報を含んだメールを送信することができます。当該コミットへのリンクも含めておけば、エラーとなったコミットをブラウザ上ですぐに確認できます。

送信されるメール本文は以下のようになります。

```text

以下のコミットでエラーが発生しています。修正お願いします。
    コミットID:b8642a5d23avgb5875d446eb31cf94341f2621a0
    リンク:https://ap-northeast-1.console.aws.amazon.com/codesuite/codecommit/repositories/helloworld-repo/commit/b8642a5d23da5b5875d446eb31cf94341f2621a0?region=ap-northeast-1
    コミットメッセージ:"fixed bug"
    コミット者:"Tarou"
    連絡先:"user@example.com"

```

以上で完了です。

# まとめ
コミットの度に自動的に単体テストが実行され、mochaの実行結果にエラーがあれば、当該コミット情報に関するメールが飛ぶ、という仕組みを構築する手段について記述しました。

# awsで開発作業を自動化するのまとめ記事
本記事はawsで開発作業を自動化するシリーズの1つです。その他の記事については以下を参照ください。
[aws環境でnode.js、lambda、CodeCommit、CodeBuild、CloudFormation、CodePipelineを使って開発作業を自動化する](https://qiita.com/kyuaz/items/704efb51bce59f21c002)