AWS S3のデプロイ自動化 ~npm-scripts~

S3でホスティングしている、Webサイトのデプロイ手順の（ワンステップ）自動化にあたり、
`npm-scripts`と、`shell-scripts`でいい感じに出来ないかなと思い、
作成した`package.json`の内容を記します

# 前提

[AWS CLI を使う](https://qiita.com/oblivion/items/9d992e97eb1c50e31d44)でプロファイルをセッティング

# package.jsonの用意
`npm`コマンドを使って`package.json`を作成

```shell
$ npm init
```

以降の`package.json`の記述は見やすいように`"scripts"`の項目だけを記述します

# generateコマンドの作成
ここでは仮に`dist`ディレクトリを作成するコマンドを用意

```json:./package.json
{
  "scripts": {
    "generate": "rm -rf dist && mkdir dist && touch dist/index.html"
  }
}
```

# confirmコマンドの作成
デプロイの際にどこの環境の処理を実行するか改めて確認する

## confirm.shの作成
`touch`コマンドで`confirm.sh`ファイルを作成

```shell
$ touch confirm.sh
```

ファイルに以下の内容を書き込む

```shell:./confirm.sh
#!/bin/bash

ENV_NAME=$1

function ConfirmExecution() {
    echo "「${ENV_NAME}」環境のデプロイを実行しますか？"
    echo "実行する場合は「${ENV_NAME}」と入力してください"
    read input

    if [ -z $input ]; then
        ConfirmExecution
    elif [ $input = ${ENV_NAME} ]; then
        echo "スクリプトを実行します。"
    else
        echo "スクリプトを終了します。"
        exit 1
    fi
}

ConfirmExecution
```

`package.json`に`confirm:stg`コマンドを以下のように追記する
引数として実行する環境名`STG`を渡すように記述します

```json:./package.json
{
  "scripts": {
    "generate": "rm -rf dist && mkdir dist && touch dist/index.html",
    "confirm:stg": "sh confirm.sh STG"
  }
}
```

# uploadコマンドの作成
`package.json`に`upload:stg`コマンドを以下のように追記する

```json:./package.json
{
  "scripts": {
    "generate": "rm -rf dist && mkdir dist && touch dist/index.html",
    "confirm:stg": "sh confirm.sh STG",
    "upload:stg": "aws s3 sync --delete --exact-timestamps dist s3://stg-xxxxxx --profile testUser"
  }
}
```

AWS CLIの`s3 sync`コマンドでローカルの`dist`ディレクトリから、
s3の`stg-xxxxxx`バケットにファイルをシンク（同期）します

その際、指定しているオプションは、それぞれ以下の内容になります

| オプション | 内容 |
| --- | --- |
| --delete | 同期元にない同期先のファイルを削除する |
| --exact-timestamps | 同期対象ファイルのタイムスタンプを確認する |
| --profile | 設定ファイルに登録されているプロファイル |

`upload:stg`コマンドの内容をまとめると
「`testUser`の権限で、ローカルの`dist`ディレクトリからs3の`stg-xxxxxx`バケットに
ファイルを同期し、無くなったものは削除する」となります

# clearコマンドの作成
`package.json`に`clear:stg`コマンドを以下のように追記する

```json:./package.json
{
  "scripts": {
    "generate": "rm -rf dist && mkdir dist && touch dist/index.html",
    "confirm:stg": "sh confirm.sh STG",
    "upload:stg": "aws s3 sync --delete --exact-timestamps dist s3://stg-xxxxxx --profile testUser",
    "clear:stg": "aws cloudfront create-invalidation --distribution-id XXXXXXXX --paths '/*' --profile testUser"
  }
}
```

AWS CLIの`cloudfront create-invalidation`コマンドで
該当のcloudfrontのキャッシュを削除します

その際、指定しているオプションは、それぞれ以下の内容になります

| オプション | 内容 |
| --- | --- |
| --distribution-id | 指定するcloudfrontディストリビューションのID |
| --paths | キャッシュクリア対象のパス（リスト） |
| --profile | 設定ファイルに登録されているプロファイル |

`clear:stg`コマンドの内容をまとめると
「`testUser`の権限で、IDがXXXXXXXXのディストリビューションで
全てのパスに該当するキャッシュをクリアする」となります

# deployコマンドの作成
`package.json`に`deploy:stg`コマンドを以下のように追記する

```json:./package.json
{
  "scripts": {
    "generate": "rm -rf dist && mkdir dist && touch dist/index.html",
    "confirm:stg": "sh confirm.sh STG",
    "upload:stg": "aws s3 sync --delete --exact-timestamps dist s3://stg-xxxxxx --profile testUser",
    "clear:stg": "aws cloudfront create-invalidation --distribution-id XXXXXXXX --paths '/*' --profile testUser",
    "deploy:stg": "run-s confirm:stg generate upload:stg clear:stg"
  }
}
```

ここで使う`run-s`コマンドはnpmモジュールの`npm-run-all`を利用する

```shell
$ npm i npm-run-all
```

この`deploy:stg`コマンドの実行により、ここまでに書いてきた、
`confirm:stg`、`generate`、`upload:stg`、`clear:stg`
を直列処理で順に実行する

# 結果
以下のコマンドを叩くことで、

```shell
$ npm run deploy:stg
```

こういう対話が行われ

```shell
「STG」環境のデプロイを実行しますか？
実行する場合は「STG」と入力してください
STG
スクリプトを実行します。
```

対象ファイルが`generate`（生成）され、
生成されたファイルを`s3`にファイルシンク（同期）し、
`cloudfront`のキャッシュをクリア（削除）する
という一連の流れをワンステップのコマンド入力で自動化できました

STG環境以外に本番で使う場合、プレビュー環境で使う場合は、それぞれ
`confirm:live`、`confirm:pre`、`deploy:live`、`deploy:pre`
という感じでそれぞれコピー、内容調整すれば良いです

上のデプロイにプラスして、最後に作業の終了を
slackに通知するコマンドを入れても良いですね

複数のプロジェクトに参加している場合は、
プロジェクトを跨いで仕事をしているうちにアップロード場所間違いや、
削除ファイル漏れなど間違いが起きかねません
デプロイの自動化が設定出来たら、安心ですね