CodeCommitからS3バケットへファイルを自動配信する

#概要
CodeCommitの特定ブランチにプッシュがあったとき、S3バケットへファイルを自動配信する。
![CodeCommit2S3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/262401/2921e1c0-0f31-cf73-bf0a-6e9c320c67e1.png)

##実現する内容
- 追加または変更されたファイルだけを配信する（変更のないファイルは配信しない）
- ブランチから削除されたファイルは配信先バケットからも削除する
- ブランチごとに配信先バケットを切り替える

##利用するサービス
- Amazon S3
- AWS Codecommit
- AWS CodeBuild
- AWS Lambda

##処理の流れ
1. CodeCommitにプッシュがあったときLambdaを呼び出す
2. Lambdaでブランチを指定してCodeBuildを呼び出す
3. CodeBuildが指定ブランチをクローンする
4. CodeBuildでgit-set-file-timesを実行する
5. CodeBuildでaws s3 syncを実行する

#構築
AWSで各サービスを設定する。

##前提条件
- Codecommitにリポジトリを作成してあること
- S3に配信先バケット（本番バケットおよび開発バケット）を作成してあること
- アクセス権限は適切に設定しておくこと

##CodeCommit

###ブランチ構成
|ブランチ|配信先バケット|
|:-------|:-------------|
|master  |本番バケット  |
|develop |開発バケット  |

###ディレクトリ構成
```
├─ buildspec.yml            # buildspecファイル
├─ git-set-file-times.pl    # git-set-file-timesソースコード
└─ contents                 # S3に配信するファイルを格納したディレクトリ
　 ├─ index.html
　 :
　 :
```

####buildspec.yml

``` buildspec.yml
version: 0.2

phases:
  pre_build:
    commands:
      # ブランチ名を取得
      - branch=`git branch --contains | grep -v '(no branch)' | cut -b 3-`
      # ブランチ名から配信先バケットを判断（ブランチ名が不正の場合は中断）
      - case "$branch" in
          "master"  ) bucket=<本番バケット名> ;;
          "develop" ) bucket=<開発バケット名> ;;
          *         ) exit 1 ;;
        esac
  build:
    commands:
      # ファイルのタイムスタンプをコミット時刻に変更
      - perl git-set-file-times.pl
      # 対象バケットにファイル配信
      - aws s3 sync --exact-timestamps --delete contents s3://$bucket
```

####git-set-file-times.pl
[Git公式Wikiのサンプルスクリプト](https://git.wiki.kernel.org/index.php/ExampleScripts#Setting_the_timestamps_of_the_files_to_the_commit_timestamp_of_the_commit_which_last_touched_them)を使う。


##CodeBuild
###ビルド作成
**送信元**
Gitのクローンの深さ(=depth)をFullに設定する。
![qiita01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/262401/e6ead8b0-57ea-70d1-e27c-26db973224a0.png)

**環境**
イメージはaws/codebuild/standard:1.0を選択する。　※runtime-versions未設定のため
![qiita02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/262401/4c13e5ab-f011-3de3-52b4-d82b97dbf963.png)

##Lambda
###関数作成
ランタイムは「Node.js 10.x」または「Node.js 8.10」で作成する。

``` index.js
const AWS = require('aws-sdk');
const codeBuild = new AWS.CodeBuild();

exports.handler = async (event) => {
  const param = {
    projectName: "sample-build",
    sourceVersion: event.Records[0].codecommit.references[0].commit
  };
  await codeBuild.startBuild(param).promise();
};
```

###トリガ作成
CodeCommitからも作成可能だが、ここではLambdaの「トリガーの追加」から作成する。
![qiita03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/262401/3ca2e4e3-ba16-4a0a-66d8-a86a1017793f.png)

#解説
構築の意図を記載する。

##ファイル配信について
CodeBuildでS3バケットにファイル配信するためにAWS CLIコマンドを実行する。

> aws s3 sync --exact-timestamps --delete contents s3://$bucket

--exact-timestampsはローカルファイルのタイムスタンプがS3より新しい場合に上書きするオプション、--deleteはローカルファイルが存在しなければS3からも削除するオプションである。この2つのオプションで以下の2点を実現できる。

> - 追加または変更されたファイルだけを配信する（変更のないファイルは配信しない）
> - ブランチから削除されたファイルは配信先バケットからも削除する

ただし、Gitの仕様上、ローカルファイルのタイムスタンプはチェックアウト時刻になるので、git-set-file-timesスクリプトでコミット時刻に変更しておく必要がある。このスクリプトはローカルリポジトリのコミット履歴を参照するので、CodeBuildのdepthを規定値である1（=シャロークローン）からFullに変更している。

> Gitのクローンの深さ(=depth)をFullに設定する。

なお、本稿でCodepPipelineを利用していないのは、CodepPipelineがチェックアウトするとCodeBuild環境にローカルリポジトリが作成されないためである。

##再実行について
S3バケットのファイルに何らかの不具合が生じた場合（誤ってS3のファイルを削除した等）は、CodeBuildを直接実行することで対応できる。buildspec.ymlでブランチ名から配信先バケットを判断しているので、Lambdaから呼び出されても、CodeBuildのコンソールから実行されても、問題なくファイル配信される。


以上。