CodeCommitのプルリクをCodeBuildで検証しAWS ChatbotでSlack通知する

# はじめに
プルリクエストで提案されたコードの変更を、マージの前にテストすることで、コードの品質を保ち、動かないコードをマージするリスクを回避する事ができます。


AWS 認定 DevOps Engineer Professionalの対策をしていると、以下のブログを発見し、
今ならもっとシンプルな構成で作れるのではと思い構築してみました。
[Validating AWS CodeCommit Pull Requests with AWS CodeBuild and AWS Lambda](https://aws.amazon.com/jp/blogs/devops/validating-aws-codecommit-pull-requests-with-aws-codebuild-and-aws-lambda/)

GitHubのプルリクをCodeBuildでビルドする記事はよくありますが、CodeCommitのプルリクをビルドする詳細な記事はあまりなく、CodeCommitをリポジトリにCIを構築しようとする方の参考になれば幸いです。

SAMとLambdaのソースはGitHubの[こちらのリポジトリ](https://github.com/joe-king-sh/aws-codecommit-pull-request-aws-codebuild-aws-chatbot)に上げているので、興味がある方はそちらもご確認ください。

[こちらのServerlessApplicationRepositry](https://ap-northeast-1.console.aws.amazon.com/lambda/home?region=ap-northeast-1#/create/app?applicationId=arn:aws:serverlessrepo:ap-northeast-1:757523705531:applications/tests-pullrequest-in-codecommit-using-codebuild-and-notify-slack)にアプリケーションを公開しているので、
取りえあえずデプロイして試してみたい方は、下記のAWS Chatbotの手順だけマネジメントコンソールで行い、ワークスペースIDとチャンネルIDを指定し、デプロイできます。


# 今回作るもの
CodeCommitのプルリクエストをトリガーに、
CodeBuildでテストを行い、結果をSlackへ通知する以下の仕組みを構築します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/541733/7234ddf1-d319-65bf-6b77-859a942ee4e4.png)
1. developブランチからmasterブランチへのプルリクの作成・更新
2. CodeCommitの全てのイベントをAWS Chatbot経由でSlackへ通知
3. CodeBuildでテストを開始
4. Lambdaでプルリクに、ビルドステータスのバッジをコメント
5. CodeBuildのビルド進行状況を、AWS Chatbot経由でSlackへ通知

構築にはSAM（Serverless Application Model）を使用し、LambdaはGolangで書いてみました。

# 前提条件

 - macOS Catalina 10.15.6
 - go version go1.15.2 darwin/amd64
 - aws-cli/2.0.17
 - SAM CLI, version 1.2.0
 - Slack 4.8.0

# 実装

## AWS Chatbot
この作業のみ、マネジメントコンソールより手動で行う必要があります。

### チャットクライアントの設定
「新しいクライアントを設定」を開き、
<img width="100%" alt="new_chat_client1" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/541733/fe46c48c-42ca-7b68-1433-2d0fa9d9d6f3.png">

クライアントの種類にSlackを選択します
<img width="70%" alt="new_chat_client2" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/541733/141df16d-45d2-c253-f2ed-5dc4ad3df9d1.png">

すると、Slackの画面に遷移し、ログイン済みの組織に対してAWS Chatbotへの許可を求められるので、許可します。

<img width="50%" alt="slack_integration" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/541733/fe6f39de-b8d7-b3b4-4fbb-aa5bb5f091d7.png">

### SlackのワークスペースIDとチャンネルIDをメモしておく
ワークスペースIDはAWS Chatbotの設定済みクライアントから確認

<img width="100%" alt="workspaceid" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/541733/e0e8a85b-7668-a15d-a202-b4c2b1a97344.png">

チャンネルIDは、コピーしたリンクの末尾の英数字です

<img width="40%" alt="channelid1" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/541733/02a94d8f-140a-6266-a226-f9dc707161ca.png">
<img width="70%" alt="channelid2" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/541733/c6f2e6df-4768-1272-5d6e-a94fba237d49.png">

## SAM テンプレート

<details><summary>sam template クリックで開きます</summary><div>

```yaml:template.yml
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: Setup AWS CI/CD resources
Parameters:
  TargetWorkspaceId:
    Type: String
  TargetChannelId:
    Type: String

Resources:
  # ------------------------------------
  # SNS Topic
  # ------------------------------------
  CodeCommitPullRequestForChatbotTopic:
    Type: AWS::SNS::Topic
    Properties: 
      DisplayName: codecommit-pullrequest-for-chabot-topic

  # ------------------------------------
  # AWS Chatbot
  # ------------------------------------
  ChatbotRole:
    Type: "AWS::IAM::Role"
    Properties:
      RoleName: codecommit-pullrequest-chatbot-role
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: "Allow"
            Principal:
              Service: chatbot.amazonaws.com
            Action: "sts:AssumeRole"
      Policies:
        - PolicyName: codecommit-pullrequest-chatbot-policy
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Action:
                  - codecommit:Describe*
                  - cloudwatch:Get*
                  - cloudwatch:List*
                Resource:
                  - "*"

  CodeCommitPullRequestChatbot:
    Type: AWS::Chatbot::SlackChannelConfiguration
    Properties: 
      ConfigurationName: CodeCommitPullRequestSampleChatbotConfiguration
      IamRoleArn: !GetAtt ChatbotRole.Arn
      SlackChannelId: !Ref TargetChannelId
      SlackWorkspaceId: !Ref TargetWorkspaceId
      SnsTopicArns: 
        - !Ref CodeCommitPullRequestForChatbotTopic

  # ------------------------------------
  # AWS CodeCommit
  # ------------------------------------
  CodeCommitPullRequestSampleRepository:
    Type: AWS::CodeCommit::Repository
    Properties: 
      RepositoryDescription: This repository contains sample code that tested in CodeBuild project.
      RepositoryName: codecommit-pullrequest-sample-repository
      # 「トリガー」による通知ではAWS ChatBotの通知がうまく動きませんでした。 「通知」を使用する必要があります。
      # Triggers:
      #   - Name: MainTrigger
      #     DestinationArn:
      #       !Ref CodeCommitPullRequestForChatbotTopic
      #     Events:
      #     - all

  CodeCommitPullRequestCodeStarNotifications:
    Type: AWS::CodeStarNotifications::NotificationRule
    Properties: 
      DetailType: FULL
      EventTypeIds: 
        - codecommit-repository-comments-on-commits
        - codecommit-repository-comments-on-pull-requests
        - codecommit-repository-approvals-status-changed
        - codecommit-repository-approvals-rule-override
        - codecommit-repository-pull-request-created
        - codecommit-repository-pull-request-source-updated
        - codecommit-repository-pull-request-status-changed
        - codecommit-repository-pull-request-merged
        - codecommit-repository-branches-and-tags-created
        - codecommit-repository-branches-and-tags-deleted
        - codecommit-repository-branches-and-tags-updated
      Name: codecommit-pullrequests-codestar-notification-rule
      Resource: 
        !GetAtt CodeCommitPullRequestSampleRepository.Arn
      Targets: 
        - TargetType: AWSChatbotSlack
          TargetAddress: 
            !GetAtt CodeCommitPullRequestChatbot.Arn

  # ------------------------------------
  # AWS CodeBuild
  # ------------------------------------
  CodeBuildServiceRole:
    Type: AWS::IAM::Role
    Properties:
      Path: /
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Principal:
              Service: codebuild.amazonaws.com
            Action: sts:AssumeRole
      Policies:
        - PolicyName: codecommit-pullrequest-codebuild-execute-role
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Resource: "*"
                Effect: Allow
                Action:
                  - codecommit:*
                  - logs:CreateLogGroup
                  - logs:CreateLogStream
                  - logs:PutLogEvents
              - Resource: "*"
                Effect: Allow
                Action:
                  - s3:GetObject
                  - s3:PutObject
                  - s3:GetObjectVersion

  CodeBuildProjectPullRequest:
    Type: AWS::CodeBuild::Project
    Properties:
      Artifacts:
        Type: NO_ARTIFACTS
      BadgeEnabled: true
      Description: This build project is triggered on create or update pull request in AWS CodeCommit.
      Environment:
        ComputeType: BUILD_GENERAL1_SMALL
        Image: aws/codebuild/amazonlinux2-x86_64-standard:3.0-20.08.14
        Type: LINUX_CONTAINER
      Name: codecommit-pullrequests-codebuild-project
      ServiceRole: 
        !Ref CodeBuildServiceRole
      Source:
        Type: CODECOMMIT
        Location: !GetAtt CodeCommitPullRequestSampleRepository.CloneUrlHttp
      SourceVersion: refs/heads/develop

  CodeBuildCodeStarNotifications:
    Type: AWS::CodeStarNotifications::NotificationRule
    Properties: 
      DetailType: FULL
      EventTypeIds: 
        - codebuild-project-build-state-failed
        - codebuild-project-build-state-succeeded
        - codebuild-project-build-state-in-progress
        - codebuild-project-build-state-stopped

        - codebuild-project-build-phase-failure
        - codebuild-project-build-phase-success
      Name: codebuild-codestar-notification-rule
      Resource: 
        !GetAtt CodeBuildProjectPullRequest.Arn
      Targets: 
        - TargetType: AWSChatbotSlack
          TargetAddress: 
            !GetAtt CodeCommitPullRequestChatbot.Arn
    
  # ------------------------------------
  # Lambda
  # ------------------------------------
  LambdaManagedPolicy:
    Type: "AWS::IAM::ManagedPolicy"
    Properties:
      Description: "Lambda policy that allows post comments in pull requests on CodeCommit."
      PolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: "Allow"
            Action:
              - "codebuild:*"
              - "codecommit:*"
              - "logs:CreateLogGroup"
              - "logs:CreateLogStream"
              - "logs:PutLogEvents"
              - "logs:GetLogEvents"
              - "xray:*"
            Resource: "*"
  LambdaRole:
    Type: "AWS::IAM::Role"
    Properties:
      Description: "Lambda role that allows post comments in pull requests on CodeCommit."
      RoleName: codecommit-pullrequest-lambda-role
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: "Allow"
            Principal:
              Service:
                - "lambda.amazonaws.com"
            Action:
              - "sts:AssumeRole"
      ManagedPolicyArns:
        - !Ref "LambdaManagedPolicy"
  CommentBuildStatusToPullRequest:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: src/comment-build-status-to-pull-request/
      Handler: main
      Runtime: go1.x
      FunctionName: "comment-build-status-to-pull-request"
      Description: "Comment build badge url to pullrequest."
      Tracing: Active
      Environment:
        Variables:
          CODEBUILD_ARN: 
            !GetAtt CodeBuildProjectPullRequest.Arn
      Role:
        !GetAtt LambdaRole.Arn
      Events:
        OnPullRequestEvent:
          Type: EventBridgeRule
          Properties:
            Pattern: 
              source: 
                - "aws.codecommit"
              detail-type: 
                - "CodeCommit Pull Request State Change"
              detail: 
                event: 
                  - "pullRequestCreated"
                  - "pullRequestSourceBranchUpdated"
                pullRequestStatus: 
                  - "Open"
                sourceReference:
                  - "refs/heads/develop"
                destinationReference:
                  - "refs/heads/master"
              resources: 
                - !GetAtt CodeCommitPullRequestSampleRepository.Arn
  
  # ------------------------------------
  # CloudWatch Event Rule
  # ------------------------------------
  OnPullRequestEventRuleManagedPolicy:
    Type: "AWS::IAM::ManagedPolicy"
    Properties:
      Description: "CloudWatch Event policy that allows invoking lambda functions and starting CodeBuild projects."
      PolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: "Allow"
            Action:
              - "codebuild:*"
              - "lambda:*"
            Resource: "*"
  OnPullRequestEventRuleRole:
    Type: "AWS::IAM::Role"
    Properties:
      Description: "CloudWatch Event role that allows invoking lambda functions and starting CodeBuild projects."
      RoleName: codecommit-pullrequest-cloudwatch-event-role
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: "Allow"
            Principal:
              Service:
                - "events.amazonaws.com"
            Action:
              - "sts:AssumeRole"
      ManagedPolicyArns:
        - !Ref OnPullRequestEventRuleManagedPolicy

  OnPullRequestEventRule: 
    Type: AWS::Events::Rule
    Properties: 
      Description: "Trigger CodeBuild project and lambda function on PR's in CodeCommit."
      EventPattern: 
        source: 
          - "aws.codecommit"
        detail-type: 
          - "CodeCommit Pull Request State Change"
        detail: 
          event: 
            - "pullRequestCreated"
            - "pullRequestSourceBranchUpdated"
          pullRequestStatus: 
            - "Open"
          sourceReference:
            - "refs/heads/develop"
          destinationReference:
            - "refs/heads/master"
        resources: 
          - !GetAtt CodeCommitPullRequestSampleRepository.Arn
      State: "ENABLED"
      Targets: 
        - 
          Arn: !GetAtt CodeBuildProjectPullRequest.Arn
          Id: CodeBuildProject
          RoleArn: !GetAtt OnPullRequestEventRuleRole.Arn
```

</div></details>

いくつか注意点を挙げるとすると、
ワークスペースIDとチャンネルIDはデプロイ時にパラメータの上書きで指定します。

```yaml
Parameters:
  TargetWorkspaceId:
    Type: String
  TargetChannelId:
    Type: String
  CodeCommitPullRequestChatbot:
    Type: AWS::Chatbot::SlackChannelConfiguration
    Properties: 
      ConfigurationName: CodeCommitPullRequestSampleChatbotConfiguration
      IamRoleArn: !GetAtt ChatbotRole.Arn
      SlackChannelId: !Ref TargetChannelId
      SlackWorkspaceId: !Ref TargetWorkspaceId
      SnsTopicArns: 
        - !Ref CodeCommitPullRequestForChatbotTopic
```

CodeCommit→AWS ChatBotの連携は、「トリガー」ではなく、「通知」を用いています。
「トリガー」からもターゲットにAWS Chatbotを指定することができますが、そちらではうまく動きませんでした。

```yaml
  # ------------------------------------
  # AWS CodeCommit
  # ------------------------------------
  CodeCommitPullRequestSampleRepository:
    Type: AWS::CodeCommit::Repository
    Properties: 
      RepositoryDescription: This repository contains sample code that tested in CodeBuild project.
      RepositoryName: codecommit-pullrequest-sample-repository
      # 「トリガー」による通知ではAWS ChatBotの通知がうまく動きませんでした。 「通知」を使用する必要があります。
      # Triggers:
      #   - Name: MainTrigger
      #     DestinationArn:
      #       !Ref CodeCommitPullRequestForChatbotTopic
      #     Events:
      #     - all
```


## Lambda

<details><summary>Lambda ソース クリックで開きます</summary><div>

```go:main.go
package main

import (
	"context"
	"encoding/json"
	"fmt"
	"log"
	"os"
	"strings"

	"github.com/aws/aws-lambda-go/events"
	"github.com/aws/aws-lambda-go/lambda"
	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/session"
	"github.com/aws/aws-sdk-go/service/codebuild"
	"github.com/aws/aws-sdk-go/service/codecommit"
)

type EventDetail struct {
	SourceReference      string   `json:"sourceReference"`
	LastModifiedDate     string   `json:"lastModifiedDate"`
	Author               string   `json:"author"`
	PullRequestStatus    string   `json:"pullRequestStatus"`
	IsMerged             string   `json:"isMerged"`
	NotificationBody     string   `json:"notificationBody"`
	DestinationReference string   `json:"destinationReference"`
	PullRequestId        string   `json:"pullRequestId"`
	CallerUserArn        string   `json:"callerUserArn"`
	Title                string   `json:"title"`
	CreationDate         string   `json:"creationDate"`
	RepositoryNames      []string `json:"repositoryNames"`
	DestinationCommit    string   `json:"destinationCommit"`
	Event                string   `json:"event"`
	SourceCommit         string   `json:"sourceCommit"`
}

const region = "ap-northeast-1"

func HandleRequest(ctx context.Context, event events.CloudWatchEvent) (string, error) {

	mySession := session.Must(session.NewSession())

	// Get PR informations from CloudWatchEvents
	var eventDetail EventDetail
	err := json.Unmarshal(event.Detail, &eventDetail)
	if err != nil {
		print(err.Error())
		return "", err
	}
	pullRequestId := eventDetail.PullRequestId
	repositoryName := eventDetail.RepositoryNames[0]
	afterCommitId := eventDetail.DestinationCommit
	beforeCommitId := eventDetail.SourceCommit
	log.Printf("Target repository name is %s\n", repositoryName)
	log.Printf("PullRequests title is %s\n", eventDetail.Title)

	// Get CodeBuild badge url.
	codebuildSvc := codebuild.New(mySession, aws.NewConfig().WithRegion(region))
	codeBuildArn := os.Getenv("CODEBUILD_ARN")
	names := []*string{&codeBuildArn}
	batchGetProjectsOutput, err := codebuildSvc.BatchGetProjects(&codebuild.BatchGetProjectsInput{
		Names: names,
	})
	if err != nil {
		print(err.Error())
		return "", err
	}
	badgeUrl := batchGetProjectsOutput.Projects[0].Badge.BadgeRequestUrl

	// Set content to post to PR's comment
	commentTemplate := `Unit tests have been started in CodeBuild.  
Build Status:
![BuildBadge](%s)
`
	//Branch in badge URL is master by default, so, replace to 'develop branch'
	content := fmt.Sprintf(commentTemplate, strings.Replace(*badgeUrl, "master", "develop", -1))

	// Post comment for PR
	codecommitSvc := codecommit.New(mySession, aws.NewConfig().WithRegion(region))
	output, err := codecommitSvc.PostCommentForPullRequest(
		&codecommit.PostCommentForPullRequestInput{
			RepositoryName: &repositoryName,
			AfterCommitId:  &afterCommitId,
			BeforeCommitId: &beforeCommitId,
			Content:        &content,
			PullRequestId:  &pullRequestId,
		})
	if err != nil {
		print(err.Error())
		return "", err
	}

	log.Printf("Result for post comment for PullRequests:  %s\n", output)

	return output.GoString(), nil
}

func main() {
	lambda.Start(HandleRequest)
}
```

</div></details>

EventBridgeから、コメントを投稿するプルリクの情報を取得、
CodeBuildのARNを環境変数から受け取り、LambdaのなかでBadgeURLを取得し、
プルリクにコメントを投稿します。

## ディレクトリ構成

samテンプレートとLambdaのソースを以下のような構成で格納しています

```bash
% tree
.
├── Makefile
├── src
│   └── comment-build-status-to-pull-request
│      ├── go.mod
│      ├── go.sum
│      ├── main.go
│      └── tests
└── template.yml
```

## Makefile

lambdaのデプロイにgoのbuildも必要なため、下記のようなMakefileを用意しました。

```makefile:Makefile
all: gobuild sambuild

sambuild:
	sam build	

gobuild:
	cd ./src/comment-build-status-to-pull-request && \
	GOOS=linux go build main.go

```

# デプロイ
## ビルド実行

makeコマンドでgoの実行ファイルとsamのビルドを行います

```zsh
% make
cd ./src/comment-build-status-to-pull-request && \
        GOOS=linux go build main.go
sam build
Building function 'CommentBuildStatusToPullRequest'
Running GoModulesBuilder:Build

Build Succeeded

Built Artifacts  : .aws-sam/build
Built Template   : .aws-sam/build/template.yaml

Commands you can use next
=========================
[*] Invoke Function: sam local invoke
[*] Deploy: sam deploy --guided
```

## デプロイ実行

--guided オプションで、ガイドに従い設定値を入力しデプロイします。

```zsh
% sam deploy --guided

Configuring SAM deploy
======================

        Looking for samconfig.toml :  Not found

        Setting default arguments for 'sam deploy'
        =========================================
        Stack Name [sam-app]: sam-app-codecommit-pull-request 
        AWS Region [us-east-1]: ap-northeast-1
        Parameter TargetWorkspaceId []: 前項で確認したワークスペースIDを指定
        Parameter TargetChannelId []: 前項で確認したチャンネルIDを指定
        #Shows you resources changes to be deployed and require a 'Y' to initiate deploy
        Confirm changes before deploy [y/N]: y
        #SAM needs permission to be able to create roles to connect to the resources in your template
        Allow SAM CLI IAM role creation [Y/n]: Y
        Save arguments to samconfig.toml [Y/n]: Y

        Looking for resources needed for deployment: Found!

                Managed S3 bucket: aws-sam-cli-managed-default-samclisourcebucket-xxxx
                A different default S3 bucket can be set in samconfig.toml

        Saved arguments to config file
        Running 'sam deploy' for future deployments will use the parameters saved above.
        The above parameters can be changed by modifying samconfig.toml
        Learn more about samconfig.toml syntax at 
        https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-config.html
Uploading to sam-app-codecommit-pull-request/d5209e02222bf6a54a6a3ba6e56e58ae  7290759 / 7290759.0  (100.00%)

        Deploying with following values
        ===============================
        Stack name                 : sam-app-codecommit-pull-request
        Region                     : ap-northeast-1
        Confirm changeset          : True
        Deployment s3 bucket       : aws-sam-cli-managed-default-samclisourcebucket-xxxx
        Capabilities               : ["CAPABILITY_IAM"]
        Parameter overrides        : {'TargetWorkspaceId': '前項で確認したワークスペースIDを指定', 'TargetChannelId': '前項で確認したチャンネルIDを指定'}

Initiating deployment
=====================
Uploading to sam-app-codecommit-pull-request/fa83cb8561abe30cc609828a3ae3c87b.template  9174 / 9174.0  (100.00%)

Waiting for changeset to be created..
Error: Failed to create changeset for the stack: sam-app-codecommit-pull-request, ex: Waiter ChangeSetCreateComplete failed: Waiter encountered a terminal failure state Status: FAILED. Reason: Requires capabilities : [CAPABILITY_NAMED_IAM]
```

名前付きIAMRoleを作成しているため、Capabilitiesは`CAPABILITY_IAM`なので、エラーが発生しました。
Capabilitiesを`CAPABILITY_NAMED_IAM`上書きして再デプロイします。

Capabilitiesの設定については下記の公式ドキュメントに記載があります。
[AWS Identity and Access Management によるアクセスの制御](https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/using-iam-template.html)

```zsh
% sam deploy --capabilities CAPABILITY_NAMED_IAM

〜中略〜

Successfully created/updated stack - sam-app-codecommit-pull-request in ap-northeast-1

```

無事デプロイに成功しました！:raised_hands:

# 動作確認
## CodeCommitへサンプルコードの格納

先ほど作成したcodecommitの空のリポジトリをクローンします

```zsh
% git clone https://git-codecommit.ap-northeast-1.amazonaws.com/v1/repos/codecommit-pullrequest-sample-repository
Cloning into 'codecommit-pullrequest-sample-repository'...
warning: You appear to have cloned an empty repository.
```

index.htmlとbuildspec.ymlを作成し、コミットします。
今回はサンプルなので、
buildspec.ymlでは、index.htmlの中に`succeeded `の文字列があるかどうかだけをテストしています。

```html:index.html
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>Codecommit PullRequests Test</title>
</head>
<body>
  <div align="center">
    <h1>sample build has been succeeded!!</h1>
  </div>
</body>
</html>
```

```yaml:buildspec.yml
version: 0.2

phases: 
    install:
        runtime-versions:
            nodejs: 10
        commands:
            - echo "installing something"
    pre_build:
        commands: 
            - echo "we are in the pre build phase"
    build:
        commands:
            - echo "we are in the build block"
            - echo "we will run some tests"
            - grep -Fq "succeeded" index.html
    post_build:
        commands:
            - echo "we are in the post build phase"
```
ファイルを作成したら、CodeCommitにPushします

```bash
% git status
On branch master

No commits yet

Untracked files:
  (use "git add <file>..." to include in what will be committed)
        buildspec.yml
        index.html

nothing added to com

% git add -A
% git commit -m"Add: index.html and buildspec.yml"
[master (root-commit) 552df43] Add: index.html and buildspec.yml
 2 files changed, 31 insertions(+)
 create mode 100644 buildspec.yml
 create mode 100644 index.html

% git push
Enumerating objects: 4, done.
Counting objects: 100% (4/4), done.
Delta compression using up to 8 threads
Compressing objects: 100% (4/4), done.
Writing objects: 100% (4/4), 615 bytes | 615.00 KiB/s, done.
Total 4 (delta 0), reused 0 (delta 0), pack-reused 0
To https://git-codecommit.ap-northeast-1.amazonaws.com/v1/repos/codecommit-pullrequest-sample-repository
 * [new branch]      master -> master
```

<img width="60%" alt="codecommit_to_chatbot" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/541733/a4c91c93-dda5-428b-f7da-24eb0c8a4b34.png">
CodeCommit → AWS Chatbotの通知が、指定したチャンネルに投稿されました:ok_hand:

## Developブランチ → Masterブランチのプルリクエスト作成

developブランチを作成します

```zsh
% git checkout -b develop
Switched to a new branch 'develop'
```

index.htmlを下記のように修正します
`<h1>sample build has been succeeded!!</h1>`
↓
`<h1>sample build has been succeeded!! v2</h1>`

修正内容をコミットしリモートリポジトリへプッシュします

```zsh
% git status
On branch develop
Your branch is up to date with 'origin/develop'.

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
        modified:   index.html

% git add -A
% git commit -m"fix: index.html v1 to v2"
[develop 8d1a621] fix: index.html v1 to v2
 1 file changed, 1 insertion(+), 1 deletion(-)

% git push -u origin develop
Total 0 (delta 0), reused 0 (delta 0), pack-reused 0
To https://git-codecommit.ap-northeast-1.amazonaws.com/v1/repos/codecommit-pullrequest-sample-repository
 * [new branch]      develop -> develop
Branch 'develop' set up to track remote branch 'develop' from 'origin'.
```

develop → masterのプルリクエストをaws-cliで作成します

```zsh
% aws codecommit create-pull-request \
--title "Test pull request" \
--description "pull request from develop to master branch." \
--targets repositoryName=codecommit-pullrequest-sample-repository,sourceReference=develop,destinationReference=master \
--output table

----------------------------------------------------------------------------------------------
|                                      CreatePullRequest                                     |
+--------------------------------------------------------------------------------------------+
||                                        pullRequest                                       ||
|+--------------------+---------------------------------------------------------------------+|
||  authorArn         |  arn:aws:iam::123456789012:user/joe-king-sh                         ||
||  clientRequestToken|  47cbaf00-bd2d-4805-a344-xxxxxxxxxxxx                               ||
||  creationDate      |  2020-10-03T16:42:54.023000+00:00                                   ||
||  description       |  pull request from develop to master branch.                        ||
||  lastActivityDate  |  2020-10-03T16:42:54.023000+00:00                                   ||
||  pullRequestId     |  8                                                                  ||
||  pullRequestStatus |  OPEN                                                               ||
||  revisionId        |  12e9f4e685a56132264f0f50cda6d4bafa9055b04c02b697bb76xxxxxxxxxxxx   ||
||  title             |  Test pull request                                                  ||
|+--------------------+---------------------------------------------------------------------+|
|||                                   pullRequestTargets                                   |||
||+------------------------------+---------------------------------------------------------+||
|||  destinationCommit           |  552df43e0634ee022df8a8a0aeb4xxxxxxxxxxxx               |||
|||  destinationReference        |  refs/heads/master                                      |||
|||  mergeBase                   |  552df43e0634ee022df8a8a0aeb4xxxxxxxxxxxx               |||
|||  repositoryName              |  codecommit-pullrequest-sample-repository               |||
|||  sourceCommit                |  8d1a621ee5d921a8ad63a03dfe90xxxxxxxxxxxx               |||
|||  sourceReference             |  refs/heads/develop                                     |||
||+------------------------------+---------------------------------------------------------+||
||||                                     mergeMetadata                                    ||||
|||+------------------------------------------------+-------------------------------------+|||
||||  isMerged                                      |  False                              ||||
|||+------------------------------------------------+-------------------------------------+|||
```

## プルリクエストへのコメントの確認

プルリクエストが作成されると、Lambdaでテスト開始の旨と、CodeBuildのバッジURLがコメントされます。
<img width="100%" alt="badge_in_progress" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/541733/ae7e8585-7fdf-e5b3-2d65-40e44b5bed96.png">
ビルドが正常に完了するとバッジのステータスが`passing`変わります🎉
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/541733/bcdba2bb-db26-f9c0-78f3-bff3e2d5f513.png)
試しにdevelopブランチのindex.htmlを下記のように変更しプッシュすると、
`<h1>sample build has been succeeded!! v2</h1>`
↓
`<h1>sample build has been failed!! v3</h1>`
プルリクエストが更新され、再度ビルドが走りますが、
テストに失敗するため、ステータスが`failing`に変わります
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/541733/281dc541-9380-fe2e-38af-c1bdb901e932.png)

## Slack通知の確認

### CodeCommitの通知
プルリクエストの登録時、更新時共に、通知が届いています！🔔

<img width="60%" alt="codecommit-nortification1" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/541733/327489fe-6b78-033a-8cc6-9f6355fb8e92.png">
<img width="60%" alt="codecommit-nortification2" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/541733/9b2add81-a712-c856-8391-fdd950ba3f6e.png">

### CodeBuildの通知
こちらも、Slackに通知が届いています！🔔
<img width="60%" alt="codebuild-nortification" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/541733/3e1d0305-bf07-97b5-4816-a5e75e216e8c.png">

# 後片付け

要らなくなったら、デプロイしたCloudformationスタックを削除します。

```zsh
% aws cloudformation delete-stack --stack-name sam-app-codecommit-pull-request
```

# おわりに

[元ネタのブログ](https://aws.amazon.com/jp/blogs/devops/validating-aws-codecommit-pull-requests-with-aws-codebuild-and-aws-lambda/)では、
ビルド結果を別のLambdaで書き込んでおり、そのLambdaへコメント投稿先のプルリクエストの情報を渡すため?に、
CodeBuildのトリガーにCodePipelineを使用していましたが、
CodeBuildのバッチURLを使用することで、Lambdaは１本で、CodePipelineも必要のない今回の構成になりました。

ただしBadgeURLは指定したブランチの最後のビルド結果しか残さないので、プルリクエスト更新時に、全てのバッジが変わる問題は微妙だなと感じつつも、一旦ここをゴールにしたいと思います。

AWS Chatbotはこの春GAになり、使ってみたいと思っていましたが、
SNSや開発者ツールの通知設定から直接Slackチャンネルを指定できて、とても便利でした！
通知のためだけのLambdaの実装などが不要になるので、面倒で入れていなかったプロジェクトにもすぐに導入したいと思いました。