gitのpre-commitでterraform書式チェックを自動化する

## はじめに

terraformでインフラ運用している中で、credentials情報をローカルに持てない等のセキュリティポリシーが理由でローカル（コーディング環境）からterraform実行できないケースがあると思います。  
そんなとき、書式ミスや変数参照ミスなどをterraform実行環境にデプロイしてみないとわからないのは、非効率ですよね。仮にデプロイをパイプライン等で自動化していても、パイプライン実行の待ち時間が無駄ですし、タイポなどのしょうもないミスでcommit履歴が汚れるのは微妙です。。  

そこで、gitの[pre-commit機能](https://git-scm.com/docs/git-commit)を利用してcommit前に`terraform fmt/init/validate`を自動実行するようにしてみました。これによりコマンドが成功した場合のみcommitされ、失敗した場合は、commitされずにエラー内容が出力されます。

うまく動かない等があれば、コメントいただければと思います。

## 実行例

### 成功例

```
$ git commit -m "test"
Directory: terraform/ terraform fmt: Passed
Directory: terraform/ terraform init: Passed
Directory: terraform/ terraform validate: Passed
Directory: terraform/environment/iam/ terraform fmt: Passed
Directory: terraform/environment/iam/ terraform init: Passed
Directory: terraform/environment/iam/ terraform validate: Passed
Directory: terraform/environment/lambda/ terraform fmt: Passed
Directory: terraform/environment/lambda/ terraform init: Passed
Directory: terraform/environment/lambda/ terraform validate: Passed
[master 9b47d9e] test
 8 files changed, 77 insertions(+), 14 deletions(-)
 create mode 100644 terraform/environment/iam/dev.tfvars
 create mode 100644 terraform/environment/iam/prd.tfvars
 create mode 100644 terraform/environment/iam/variables.tf
 create mode 100644 terraform/environment/lambda/variables.tf
```

### 失敗例

```
$ git commit -m "test"
Directory: terraform/ terraform fmt: Passed
Directory: terraform/ terraform init: Passed
Directory: terraform/ terraform validate: Passed
Directory: terraform/environment/iam/ terraform fmt: Passed
Directory: terraform/environment/iam/ terraform init: Passed
Directory: terraform/environment/iam/ terraform validate: Passed
Directory: terraform/environment/lambda/ terraform fmt: Passed
Directory: terraform/environment/lambda/ terraform init: Passed

Error: Missing required argument

  on main.tf line 16, in module "get_lambda":
  16: module "get_lambda" {

The argument "memory_size" is required, but no definition was found.


Error: Unsupported argument

  on main.tf line 23, in module "get_lambda":
  23:   memory_sze            = 256

An argument named "memory_sze" is not expected here. Did you mean
"memory_size"?

Directory: terraform/environment/lambda/ terraform validate: Failed
```

# 実行環境

- WSL (Ubuntu 18.04)
- git version 2.17.1
- Terraform v0.12.19

# 準備から実行まで

### 準備1. terraformインストール

使用しているversionに合わせたterraformを[こちら](https://www.terraform.io/downloads.html)からインストールします。
（すでにインストール済みの場合は不要）

### 準備2. `.gitignore`に`.terraform/`を追加

ローカルで`terraform init`するので、`.terraform/`配下にpluginやmoduleがインストールされます。
これらはgit管理する必要がないので`.gitignore`ファイルに以下を追加します。
（すでに記載済みの場合は不要）

```text:.gitignore

.terraform/
```

### 準備3. `pre-commit`ファイルを配置

`.git/hooks`に`pre-commit`というファイル名で以下の内容を配置する

```bash:.git/hooks/pre-commit
#!/bin/bash -u

work_dir=`pwd`

# 戻り値チェック
check_return_code() {
    cmd_name="$1"
    dir_name="$2"
    rc=$3
    if [ ${rc} -eq 0 ];then
        echo "Directory: ${dir_name} terraform ${cmd_name}: Passed"
    else
        echo "Directory: ${dir_name} terraform ${cmd_name}: Failed"
        exit 1
    fi
}

# terraform実行ディレクトリを抽出する.
# .tfファイルを含むディレクトリ名を変数target_dirに格納. .tfstate及びmoduleは除外.
target_file=$(git diff --cached --name-only --diff-filter=AMRC | grep -E ".tf$|.tfvars$")
if [ -n "${target_file}" ]; then
    target_dir=$(echo ${target_file} | xargs dirname | sort -u)
else
    echo "No change in TF file."
    exit 0
fi

# terraform init/validate
for dir in ${target_dir}
do
    cd ${work_dir}/${dir} || exit 1

    # terraform fmt
    fmt_file=$(terraform fmt)
    check_return_code fmt ${dir} $?

    # fmtにより変更されたファイルをstageする.
    if [ -n "${fmt_file}" ];then
        cd ${work_dir}
        for file in ${fmt_file}
        do
            git add ${dir}/${file}
        done
        cd ${dir}
    fi

    # terraform init
    terraform init -backend=false 1>/dev/null
    check_return_code init ${dir} $?

    # terraform validate
    terraform validate 1>/dev/null
    check_return_code validate ${dir} $?

    #exit 1
done

```

### 実行

上記実施後にcommitすると、実行例のように`terraform fmt/init/validate`が変更対象のディレクトリごとに実行されます。

### 注意点

tf version 0.11以前でtfvarsファイルを使用している場合、terraform validateでエラーになります。
その場合、-var-fileオプションでtfvarsを明示的に指定する必要があります。

## 参考

- https://git-scm.com/docs/git-commit
- https://www.terraform.io/docs/commands/index.html
- [git-commit前にsyntaxチェックする
](https://qiita.com/ymko/items/9209aa8ed811db795024)
- [git commit前にaddしたファイルのみrubocopで検査する](https://qiita.com/patorash/items/40d978138a04abc0e850)