CodeBuildからCloudFormationを使ってデプロイする時にテンプレートファイルの文法チェックやスタックの存在有無をチェックしてからデプロイする

# はじめに
CodeBuildからCloudFormationを使ってデプロイする際に、CloudFormationのテンプレートファイルに問題がないか確認したり、今からデプロイ(作成)しようとしているスタックが既に存在するか確認してからデプロイを実行したいケースがあります。

以前書いた以下記事では、単純にスタックが既に存在しようとなかろうと一旦スタックを削除してから、作成（デプロイ）するという流れで書きました。
[aws CodeBuildを使ってlambda(node.js)環境をテスト→デプロイする](https://qiita.com/kyuaz/items/604d11b8af1921112f99)

しかし、実際運用するとなると、冒頭と述べたように事前に問題ないか確認してからデプロイの実行をしたくなってきます。
またある程度システムの規模が大きくなったり、色々な処理を増やしていくと、CodeBuildで実行する処理を用途ごとにスクリプトファイルにまとめたり、パラメータをまとめて１箇所で定義したり、したくなってきます。

本記事では、上記2点を踏まえ、CodeBuildのbuildspec.ymlを作っていきます。

# 方針
buildspec.ymlを作成する上で、以下2つの方針を念頭に改良していきます。　

- チェックの仕組みを入れる
- スクリプトファイルを管理しやすいように分割して整理

# 前置き
## ファイルはGitHubに上がっています
今回説明に使用したプロジェクトのファイル一式は以下GitHubに上げてます。
[GitHub lambda-helloworld](https://github.com/kyuaz/lambda-helloworld)

## 全体構成図
[前回](https://qiita.com/kyuaz/items/604d11b8af1921112f99)と同様、今回も全体の構成は以下のようになります。
![68747470733a2f2f71696974612d696d6167652d73746f72652e73332e616d617a6f6e6177732e636f6d2f302f3139313837332f64303736643838332d656533362d396137662d633262312d6133663338616236643932652e706e67.png](https://qiita-image-store.s3.amazonaws.com/0/191873/504f1468-b471-952f-69c8-7d7177b77b0d.png)

# やること
[前回](https://qiita.com/kyuaz/items/604d11b8af1921112f99)と比較して、
- デプロイされる成果物は変えない
- buildspec.ymlは変える
- シェルスクリプトは変える
ことで、より再利用しやすい汎用性の高いデプロイの仕組みに改良します。

## buidspec.yml
buildspec.ymlは以下のようになります。

```yaml:buildspec.yml
version: 0.2

phases:
  install:
    commands:
      - npm install
  pre_build:
    commands:
      - sh prj_test.sh
  build:
    commands:
      - zip lambda.zip *.js *.json -r node_modules -q
  post_build:
    commands:
      - sh upload.sh
      - sh deploy.sh

```

post_buildフェーズに処理を直接書くのではなく、外部のスクリプトファイルとして持ちます。

## シェルスクリプト
シェルスクリプトは以下3つのファイルに分けます。

- upload.sh
    - テンプレートファイルおよびlambdaアーカイブファイルをアップロードする
- param.sh
    - パラメータを持つ
- deploy.sh
    - チェックしてからデプロイする

### upload.sh
アップロードをするシェルスクリプトは以下になります。

```bash:upload.sh
#!/bin/bash

. ./param.sh

echo "S3_DEPLOY_DIR: ${S3_DEPLOY_DIR}"
echo "S3_TEMPLATE_DIR: ${S3_TEMPLATE_DIR}"
aws s3 cp lambda.zip $S3_DEPLOY_DIR
aws s3 cp template.yaml $S3_TEMPLATE_DIR
aws s3 ls $S3_TEMPLATE_DIR

```

まずparam.shを読み込んでパラメータを読み込みます。
[CodeBuildではsourceコマンドは使えない](https://stackoverflow.com/questions/44810237/cannot-run-source-in-aws-codebuild)ため、代わりに"."コマンドを使って外部ファイルを読み込んでいます。後は、アップロード先のS3のパスを変数に置き換えます。

### param.sh  
パラメータを変数として定義しているシェルスクリプトは以下になります。

```bash:param.sh
#!/bin/bash

S3_BUCKET_NAME="sample-bucket"
S3_PRJ_NAME="helloworld"
S3_DEPLOY_DIR="s3://${S3_BUCKET_NAME}/${S3_PRJ_NAME}/deploy/"
S3_TEMPLATE_DIR="s3://${S3_BUCKET_NAME}/${S3_PRJ_NAME}/template/"
STACK_NAME="helloworld"
TEMPLATE_URL="https://s3-ap-northeast-1.amazonaws.com/${S3_BUCKET_NAME}/${S3_PRJ_NAME}/template/template.yaml"

```

param.shでは、upload.shおよびdeploy.shでパラメータとして変数化できるものを、変数として定義します。

### deploy.sh
デプロイするシェルスクリプトは以下になります。

```bash:deploy.sh
#!/bin/bash

. ./param.sh

echo "TEMPLATE_URL: ${TEMPLATE_URL}"

aws cloudformation validate-template --template-url $TEMPLATE_URL 2>&1 1>/dev/null | grep ValidationError
if [ $? = 0 ]; then
    echo "cloudformation template validate error"
    exit 1
fi

aws cloudformation describe-stacks --stack-name $STACK_NAME 2>&1 1>/dev/null | grep error
if [ $? = 0 ]; then
    echo "stack doesn't exist. creating new stack."
    aws cloudformation create-stack --stack-name $STACK_NAME --template-url $TEMPLATE_URL --tags Key=COST,Value=cf-helloworld --region ap-northeast-1
    echo "waiting stack create complete..."
    aws cloudformation wait stack-create-complete --stack-name $STACK_NAME
else
    echo "stack exists. updating the stack."
    aws cloudformation update-stack --stack-name $STACK_NAME --template-url $TEMPLATE_URL --tags Key=COST,Value=cf-helloworld --region ap-northeast-1
    echo "waiting stack update complete..."
    aws cloudformation wait stack-update-complete --stack-name $STACK_NAME
fi

aws cloudformation describe-stacks --stack-name $STACK_NAME

```

deploy.shでは以下の処理を実施しています。

- パラメータを変数に置き換え
- テンプレートファイルのチェック
    - aws cloudformation validate-templateでテンプレートファイルの文法チェックができます
    - エラーがある場合、標準エラー出力にValidationErrorを含む文字列が出力されます
    - そのためここでは標準エラー出力をgrepにパイプしてValidationErrorがあればエラーとして扱っています
- スタックの存在有無のチェック
    - aws cloudformation describe-stacksでスタック名を指定してスタック情報を取得できます
    - ただしスタックが存在しない場合、標準エラー出力にerrorを含む文字列が出力されます
    - そのためここでは標準エラー出力をgrepにパイプしてerrorがあればエラーとして扱っています
- スタックが存在しなかったら、スタック新規作成
    - create-stackでスタックを新規作成します
- スタックが存在したら、スタックを更新
    - update-stackでスタックを更新します

※ --tagsオプションは任意です。必須ではありません。

以上で、buildspec.ymlおよびシェルスクリプトの記述が完成しました。

# まとめ
[前回](https://qiita.com/kyuaz/items/604d11b8af1921112f99)の構成を改良して、lambdaをデプロイする際のパラメータをparam.shファイルに外だしして、シェルスクリプトやbuildspec.ymlの汎用性を高めました。これでparam.shだけ変更すれば、スクリプトファイルやbuildspec.ymlはある程度使いまわせる構成に改良できました。

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