[Terraform]GithubActionsでTerraformリポジトリのCIを行う

# 概要
[Github Actions](https://github.co.jp/features/actions)を使って、Terraformリポジトリの継続的インテグレーションが行えるようになるまでの設定手順をハンズオンライクに記載します。
Githubに機密情報を預けたくないという方には向きません。

# 前提
- Githubでtfファイルを管理している
- AWSアカウントを保持している


# CIでチェックする内容
[こちらの記事]()を参照ください。

# Github Actionsの設定
完成形は[こちら](https://github.com/mintak21/terraform-old)を参照ください。

## 特徴
CodeBuildでの設定と比べ以下のような特徴があります。

○　設定方法が容易
○　並列実行も可能
△　AWSやTerraform CloudのCredentialをGithubに記録する必要がある

CredentialsをGithub上に管理する必要がある点が引っかかる方は、CodeBuildによるCIをおすすめします。AWS上で実行するので、AWSのCredentialsは不要であり、その他のCredentialsは[SSM Parameter-Store](https://docs.aws.amazon.com/ja_jp/kms/latest/developerguide/services-parameter-store.html)でKMSを用いて機密データを保管することが可能です。

## ディレクトリ構成
リポジトリのディレクトリ構成は以下のようにします。

```bash
├── .github/
│  └── workflows/
│     └── terraform_ci.yml # Actionsの設定ファイル
├── environments/          # CIの対象ファイル群
│  └── aws/
│     ├── development/
│     └── practice/
└── scripts/
   └── ci/                 # スクリプト群
```


## 設定ファイルの記述
GithubActionsはワークフロー実行のファイルを記載するだけで設定は完了となります。
しかもTerraform CI/CDやAWS/GCP等のパブリッククラウドへのデプロイなどワークフローテンプレートが用意されているため、これをベースに多少カスタマイズするだけで使用開始することができます。
今回もGithubで用意されているテンプレートをベースにカスタマイズしていきましょう。
テンプレートを取得するためには、リポジトリの`Actions`タブを押下し、`Terraform`を選択します。
<img width="1237" alt="スクリーンショット 2020-07-06 0.11.51.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/552343/9cde4f7c-8347-5fa1-4295-dc0ec79b36ae.png">
テンプレートが表示されるため、いったん右上の`Start Commit`でファイルを作成しましょう。


### テンプレートの設定概要
ここで作成したテンプレートは以下の前提で作られています。

- `master`ブランチへの`push`およびPR作成・更新時にワークフローが実行される
- リポジトリのルートディレクトリに対象のtfファイルが存在する
- ステートファイルを[Terraform Cloud](https://app.terraform.io/)で管理している、また`plan`等のコマンドをTerraform Cloud上で実行する
- `fmt`と`plan`を実行する
- `master`ブランチへの`push`を契機に、applyを実行する

今回、この前提とは異なる部分に対してカスタマイズを行っていきます。すなわち、

- AWSのcredentialsを読み込む
- コマンドは`Github`上で実行する
- `apply`は実行しない
- `validate` `tflint`も実行する
- `environments`ディレクトリ以下の各環境別にCIを実行する

の変更を加えていきます。

### AWSのcredentialsを読み込む
まずリポジトリにAWSとTerraformCloud（ステート管理で使用している場合）のcredentialsを登録します。
リポジトリの Settings → Secretsより、以下の環境変数名で登録します。

| シークレット名称 | 概要 |
| :--- | :--- |
| AWS_ACCESS_KEY_ID | AWSのアクセスキーID |
| AWS_SECRET_ACCESS_KEY | AWSのシークレットアクセスキー |
| TF_API_TOKEN | Terraform Cloudの認証トークン |

このままではAWS認証情報は読み込まれないので、`checkout`stepの次にcredentialsをセットするstepを追加します。

```yml:terraform_ci.yml
- name: Configure AWS Credentials
  uses: aws-actions/configure-aws-credentials@v1
  with:
    aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
    aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
    aws-region: ap-northeast-1
```

### コマンドは`Github`上で実行する
これは[Terraform Cloud](https://app.terraform.io/)でステート管理している場合に限ります。（S3やGCSで管理している場合は必然的に`Github Actions`上でコマンドが実行されるため、気にする必要がありません。）
Settings → General より Execution Modeを`Local`に設定します。（デフォルトだと`Remote`）

<img width="935" alt="スクリーンショット 2020-07-06 0.27.38.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/552343/7edb332c-af22-9bc3-4a5b-5d22188dd6ea.png">

### `apply`は実行しない
CIにフォーカスするという意味でです。単純に以下のapplyの行を削除、またはコメントアウトするだけです。

```yml:terraform_ci.yml
- name: Terraform Apply
  if: github.ref == 'refs/heads/master' && github.event_name == 'push'
  run: terraform apply -auto-approve
```

### `validate` `tflint`も実行する
validateは`terraform validate`で実行可能なので、これは（`init`より下に）stepとして追加するだけで完了です。

```yml:terraform_ci.yml
- name: Terraform Validate
  id: validate
  run: terraform validate -no-color
```

`tflint`は`terraform`のコマンドではないため、[reviewdog/action-tflint](https://github.com/reviewdog/action-tflint)を利用します。こちらは誤りのある部分をPRにレビューコメントしてくれるという機能もついています。せっかくなので利用する設定にしてみます。
<img width="922" alt="スクリーンショット 2020-07-06 0.38.26.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/552343/7ddc3774-9dec-ded7-8bbc-ba168194dbbc.png">


コマンドが`init`前提のため`init`より下にstepを追加します。

```yml:terraform_ci.yml
- name: TFLint
  uses: reviewdog/action-tflint@master
  with:
    github_token: ${{ secrets.github_token }}
    reporter: github-pr-review                 # PRにコメントする
    fail_on_error: true
```

### `environments`ディレクトリ以下の各環境別にCIを実行する
Github Actionsに用意されている`strategy.matrix`を利用して、複数ディレクトリのCIを実現します。
なお、この場合、ワークフローは`matrix`に設定した数分だけ（並列して）実行されます。
**matrix部分が直書きのため、対象環境が増えた場合に書き足す必要があります。**この部分をうまく解決する方法を思いつきませんでした。。。
特定のディレクトリのみ実施したい場合は、`strategy.matrix`は必要なく、`working-directory`を対象ディレクトリに設定すればよいです。

```yml:terraform_ci.yml
terraform-ci:
  name: Terraform
  strategy:
    matrix:
      env: [practice, development]  # ここに対象環境名を羅列

  runs-on: ubuntu-latest

  defaults:
    run:
      shell: bash
      working-directory: ./environments/aws/${{ matrix.env }}  # このディレクトリでこれ以下のコマンドを実行していく
```

### 変更をpush
ここまででカスタマイズは完了です。設定ファイルの変更をコミットしてみましょう。
Actionsタブにてワークフローが実行されていることを確認できたら、正常に稼働しています！


## Options1. CIの結果をPRにコメントする
上記までの設定でCIが実行されるようになりましたが、その結果はActionsタブの実施結果→対象stepを見る必要があります。しかしPR上でその確認まで済ませたいというケースもあるでしょう。そこで、CI実行結果をPRにコメントとして記載するような設定を追加してみます。また、**一度のワークフローですべてのチェックを行うことができる**点も魅力です。
ただし、**`fmt`等に失敗しても、CIの実行ステータス自体は`Complete`となる**点に注意してください。（うまく実行結果をチェックする機構をとれば可能かもしれません。）トレードオフで導入有無を選択ください。

### 各コマンドで失敗しても、ワークフローを中断しない
すべての結果を一度に見たいため、ワークフローを最後まで走らせる必要があります。そこで`fmt``validate``plan`にエラー時も次へ進む設定を追記します。また、stepのidも振っておきましょう。

```yml:terraform_ci.yml
- name: Terraform Validate
  id: validate                      # ここを追記
  run: terraform validate -no-color
  continue-on-error: true           # ここを追記
```

### コメント通知の実装
[公式Readme](https://github.com/hashicorp/setup-terraform#usage)の`Outputs can be used in subsequent steps to comment on the pull request`の例をほぼそのまま利用します。
`steps.<STEP>.outcome`はそのstepの実行結果（success、failure、cancelled、skippedのいずれか）、`steps.<STEP>.outputs.stdout`はそのコマンドの実行結果標準出力を表しています。

```yml:terraform_ci.yml
# 一番最後のstepとして定義
- uses: actions/github-script@0.9.0
  if: github.event_name == 'pull_request'  # PR作成・更新時だけ実行する
  env:
    PLAN: "terraform\n${{ steps.plan.outputs.stdout }}"
  with:
    github-token: ${{ secrets.GITHUB_TOKEN }}
    script: |
      const output = `## \`${{ matrix.env }}\`
      #### Terraform Format and Style 🖌\`${{ steps.fmt.outcome }}\`
      #### Terraform Initialization ⚙️\`${{ steps.init.outcome }}\`
      #### Terraform Validation 🤖${{ steps.validate.outputs.stdout }}
      #### Terraform Plan 📖\`${{ steps.plan.outcome }}\`
      
      <details><summary>Show Plan</summary>
      
      \`\`\`${process.env.PLAN}\`\`\`
      
      </details>
      
      *Pusher: @${{ github.actor }}, Action: \`${{ github.event_name }}\`, Workflow: \`${{ github.workflow }}\`*`;
        
      github.issues.createComment({
        issue_number: context.issue.number,
        owner: context.repo.owner,
        repo: context.repo.repo,
        body: output
      })
```
次のようにPRに実行結果がコメントされるようになります。
<img width="916" alt="スクリーンショット 2020-07-06 1.16.55.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/552343/5f796630-38be-8333-1a9f-7fa39e3ca349.png">

## Options2. `strategy.matrix`を使わずに複数ディレクトリのCI
これはCodeBuild実行時に使用した方法と同様で、ラッパースクリプトを作成し、その中で対象のディレクトリを列挙、コマンドを実行するといった流れです。以下のような特徴があります。

- ワークフローは単一のものとなる
- 対象ディレクトリが増えた場合も、CI設定の変更は不要
- ラッパースクリプトを作成する必要がある
- `tfnotify`を利用する場合、`pull_request`イベントではうまく発火されない（これは設定が悪いのかもしれません）


なお、ラッパースクリプトは[こちら]()の記事で作成したものとほぼ同様なので参照ください。本記事では差分のみを記載します。

### GithubActions設定ファイル作成
基本の流れは変わりません。以下の点を変更する程度となります。

- `strategy.matrix`は使用しない
- 各stepではラッパースクリプトを実行する

```yml:terraform_ci2.yml
runs-on: ubuntu-latest
defaults:
  run:
    shell: bash
    working-directory: .
############## 中略 ##############
- name: Install Dependent Packages
    run: ./scripts/ci/install_packages.sh

- name: Terraform Format Check
    run: ./scripts/ci/format.sh
############## 以下同様 ##############
```


### `tfnotify`をGithubActions対応とする
1行目の`ci`の記述を変更するのみです。それ以外は、そのままで使いまわしが可能です。

```yml:terraform_ci2.yml
ci: github-actions
```

また、これは`tfnotify`側の問題なのかこちらの設定が悪いのかが不明ですが、`pull_request`実行の場合だとうまくPRにコメントがつきませんでした。また、`label`設定もうまく機能しませんでした。こちら情報ありましたら提供願います。:pray:

```yml:terraform_ci2.yml
on:
  push:
  # どうもGithub-Actions上でtfnotifyはpushでないと動かない様子
  #   branches:
  #     - master
  #     - development
  # pull_request:
```

PRにnotifyはされるようになるので、最低限やりたいことはクリアできていると思います。
<img width="922" alt="スクリーンショット 2020-07-06 1.41.07.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/552343/dde1643e-c483-e4e6-9b92-ceec83daa950.png">

# References
- [GitHub Actionsのワークフロー構文](https://docs.github.com/ja/actions/reference/workflow-syntax-for-github-actions)
- [setup-terraform](https://www.terraform.io/docs/github-actions/setup-terraform.html)