Github Actions で GCPに向けてTerraform を実行する

# tldr
Github Actions で Terraformを実行してみました。

# 前提となること
## Backend
Terraformでバックエンドをする際に知っておくべきことは以下の2点です。
（※私はGCSにしたので、providerが違うと他に気をつけることがあるかもしれません。）

1. ストレージは予め作成しておく必要がある。  
バックエンドに指定するストレージは `apply` で一緒に作成することはできません。予め作っておく必要があります。
2. アクセス権の付与の順番  
以下の記事によるとTerraformはmoduleが先に読み込まれるため、クレデンシャルはBackendで指定する必要があります。そこで指定するとproviderでクレデンシャルの指定はいらないそうです。providerで指定してbackendで指定しないとデフォルトサービスアカウントが使われます。なので特殊な場合を除いてあまりやらないと思います。ただ後述するように、環境変数としてクレデンシャルを渡すので今回はどちらもパスを設定する必要はありません。

参考：
https://nickkou.me/2019/04/error-configuring-the-backend-gcs-storage-newclient-failed-dialing-google-could-not-find-default-credentials/
https://www.terraform.io/docs/github-actions/actions/plan.html

## HCL -> yaml への文法の変更
Github Actionsワークフローの記法は2019年9月21日時点でyamlに変更されています（まだ使えるがdeprecatedになる）。そのため、以下の記事に従い、migration toolsを実行して `HCL -> yaml` へ変換が必要になります。

https://help.github.com/en/articles/migrating-github-actions-from-hcl-syntax-to-yaml-syntax#about-the-new-yaml-syntax-for-github-actions

# はじめます
## 
まずはTerraformが提供している公式のワークフローの[サンプル](https://www.terraform.io/docs/github-actions/remote-backend.html)をコピペします。

``` hcl
workflow "Terraform Cloud" {
  resolves = "terraform-plan"
  on = "pull_request"
}

action "filter-to-pr-open-synced" {
  uses = "actions/bin/filter@master"
  args = "action 'opened|synchronize'"
}

action "terraform-fmt" {
  uses = "hashicorp/terraform-github-actions/fmt@<latest tag>"
  needs = "filter-to-pr-open-synced"
  secrets = ["GITHUB_TOKEN"]
}

action "terraform-init" {
  uses = "hashicorp/terraform-github-actions/init@<latest tag>"
  needs = "terraform-fmt"
  secrets = ["GITHUB_TOKEN", "TF_ACTION_TFE_TOKEN"]
  env = {
    TF_ACTION_TFE_HOSTNAME = "app.terraform.io"
  }
}

action "terraform-validate" {
  uses = "hashicorp/terraform-github-actions/validate@<latest tag>"
  needs = "terraform-init"
  secrets = ["GITHUB_TOKEN"]
}

action "terraform-plan" {
  uses = "hashicorp/terraform-github-actions/plan@<latest tag>"
  needs = "terraform-validate"
  secrets = ["GITHUB_TOKEN", "TF_ACTION_TFE_TOKEN"]
  env = {
    TF_ACTION_TFE_HOSTNAME = "app.terraform.io"
  }
}
```

やっていることは単純でプルリクに対して `fmt`, `init`, `validate`, `plan` を実行しています。
それぞれの実際のスクリプトは `uses` で指定しています。

# yamlへ書き換え
まずはこれをyamlに書き換えます。
[ここ](https://github.com/actions/migrate/releases)から最新のバイナリをダウンロードしてパスを通します。

変換したいHCLのワークフローファイルを `リポジトリルート/.github/main.workflow` に置きます。
リポジトリルートで `migrate-actions` を実行します。

すると `リポジトリルート/.github/workflows/pull_request.yml` に変換後のファイルが作成されます。
こんな感じです。

``` yaml
on: pull_request
name: Terraform Cloud
jobs:
  filter-to-pr-open-synced:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@master
    - name: filter-to-pr-open-synced
      uses: actions/bin/filter@master
      with:
        args: action 'opened|synchronize'
    - name: terraform-fmt
      uses: hashicorp/terraform-github-actions/fmt@<latest tag>
      env:
        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
    - name: terraform-init
      uses: hashicorp/terraform-github-actions/init@<latest tag>
      env:
        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        TF_ACTION_TFE_HOSTNAME: app.terraform.io
        TF_ACTION_TFE_TOKEN: ${{ secrets.TF_ACTION_TFE_TOKEN }}
    - name: terraform-validate
      uses: hashicorp/terraform-github-actions/validate@<latest tag>
      env:
        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
    - name: terraform-plan
      uses: hashicorp/terraform-github-actions/plan@<latest tag>
      env:
        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        TF_ACTION_TFE_HOSTNAME: app.terraform.io
        TF_ACTION_TFE_TOKEN: ${{ secrets.TF_ACTION_TFE_TOKEN }}
```

# 必要事項を埋めていく
あとは[Getting Started](https://www.terraform.io/docs/github-actions/getting-started/index.html)を読みながら必要な部分を埋めていくだけです。

# クレデンシャルの設定
今回のProviderはGoogleなので、サービスアカウントのシークレットをクレデンシャルに設定します。サービスアカウントを作成し、Terraformに必要なロールを付与します（ここはどんなことをやるかによってそれぞれだと思います）。ダウンロードしたサービスアカウントのシークレットであるjsonファイルをgithubのリポジトリ設定 -> Secretsで 名前を `GOOGLE_CREDENTIALS` として追加します。あとはワークフローに環境変数として渡すだけです。

# 最終的なファイル
最終的なファイルはこれになりました。
ディレクトリやコマンドフラグなど、若干の違いはあると思います。

``` yaml
on: pull_request
name: Terraform
jobs:
  filter-to-pr-open-synced:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@master
    - name: filter-to-pr-open-synced
      uses: actions/bin/filter@master
      with:
        args: action 'opened|synchronize'
    - name: terraform-fmt
      uses: hashicorp/terraform-github-actions/fmt@v0.4.0
      env:
        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        TF_ACTION_WORKING_DIR: ./main
    - name: terraform-init
      uses: hashicorp/terraform-github-actions/init@v0.4.0
      env:
        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        GOOGLE_CREDENTIALS: ${{ secrets.GOOGLE_CREDENTIALS }}
        TF_ACTION_WORKING_DIR: ./main
    - name: terraform-validate
      uses: hashicorp/terraform-github-actions/validate@v0.4.0
      env:
        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        TF_ACTION_WORKING_DIR: ./main
    - name: terraform-plan
      uses: hashicorp/terraform-github-actions/plan@v0.4.0
      with:
        args: '-var-file=./config.tfvars'
      env:
        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        TF_ACTION_WORKING_DIR: ./main
        TF_ACTION_WORKSPACE: default
        GOOGLE_CREDENTIALS: ${{ secrets.GOOGLE_CREDENTIALS }}
```

あとはこれを `リポジトリルート/.github/workflows/pull_request.yml` に置いてプルリクを作れば終わりです。ワークフローが実行されて、結果がコメントとして貼り付けられます。

# まとめとこれから
やってみると意外に簡単でした。あとは `apply`をissueへのコメント（`/apply`とか）をイベントとして発火させたいですね。