Terraform v0.9からv0.13まで駆け足でバージョンアップした話

# はじめに

久々に触ったTerraformを `v0.9`から `v0.13` まで駆け足でバージョンアップしたので、どの辺にハマりどころがあったかメモとして残しておこうと思います。

# 実行環境について

- `tfenv`を使って `.terraform-version` の書き換えによりバージョンアップする
- `tflint`を使って記法をチェックする
- tfファイル内のbackend設定は以下とし、 `terraform init` 時の引数でbackendの `バケット名`、 `キー名` 、 `リージョン名` を渡す

```bash
terraform {
  backend "s3" {
    dynamodb_table = "terraform-state-lock"
  }
}
```

# Terraformのバージョンアップに関するまとめ

Terraformのバージョンアップについて、こちらで簡単にまとめます。
**`v0.11`系までの段階的なバージョンアップについては過去に実績があった**ので、以下の通り段階的にバージョンアップを行ないました。

* `v0.9.8` → `v0.9.11`
    * まず同バージョンの最新に上げる
    * サクッと上げられる
* `v0.9.11` → `v0.10.8`
    * `apply` 方式に一部変更あり
    * サクッと上げられる
* `v0.10.8` → `v0.11.14`
    * `apply` 時、適用するか確認のメッセージが出力される様になる
    * サクッと上げられる
* `v0.11.14` → `v0.12.28`
    * `HCL(HashiCorp Configuration Language)` の大幅な変更があるため、公式ドキュメントの[Upgrading to Terraform v0.12](https://www.terraform.io/upgrade-guides/0-12.html)に従いアップグレードを行う必要がある
    * 手動で直さないといけない部分もあるので、結構大変
* `v0.12.28` → `v0.13.2`
    * サクッと上げられる

# Terraformのバージョンアップについて

## Terraform `v0.9.8` から `v0.9.11` へのバージョンアップ

`v0.9.8`から `v0.9.11` はすんなり上がりました。

```bash
$ cat .terraform-version
0.9.11

$ terraform init \
    -backend-config=bucket=terraform.backend \
    -backend-config=key=terraform.tfstate \
    -backend-config=region=us-west-2 \
    /home/username/my-terraform/tf

$ terraform plan \
    -var-file=/home/username/my-terraform/conf/vars.tfvars \
    -refresh=true

$ terraform apply \
    -var-file=/home/username/my-terraform/conf/vars.tfvars \
    -refresh=true
```

## Terraform `v0.9.11` から `v0.10.8` へのバージョンアップ

`v0.9.11`から `v0.10.8` は一部 `plan` `apply` のやり方を変える必要がありました。

```bash
$ cat .terraform-version
0.10.8

$ terraform init \
    -backend-config=bucket=terraform.backend \
    -backend-config=key=terraform.tfstate \
    -backend-config=region=us-west-2 \
    /home/username/my-terraform/tf

$ terraform plan \
    -var-file=/home/username/my-terraform/conf/vars.tfvars \
    -refresh=true
```

ここまで実行したところ、以下のエラーとなりました。

```bash
Failed to load backend: Initialization required. Please see the error message above.
```

どうやら `v0.9` 系までは `plan` `apply` 時に `tf` ファイルのディレクトリを指定しなくとも動きましたが、 `v0.10` 系からは明示的に指定が必要になった様です。
以下の通り、引数で `tf` ファイルのディレクトリを指定してやることで上手く動きました。

```bash
$ terraform plan \
    -var-file=/home/username/my-terraform/conf/vars.tfvars \
    -refresh=true \
    /home/username/my-terraform/tf

$ terraform apply \
    -var-file=/home/username/my-terraform/conf/vars.tfvars \
    -refresh=true \
    /home/username/my-terraform/tf
```

また、一部の `iam_policy` では、明示的に `false` 設定を入れていなかったリソースに対して `false` 設定を入れる差分が出力されました。
`false` のままで問題なかったためそのまま適用しました。

```bash
 ~ aws_iam_role.test_role
      force_detach_policies: "" => "false"
```

## Terraform `v0.10.8` から `v0.11.14` へのバージョンアップ

`v0.10.8`から `v0.11.14` はすんなり上がりました。

```bash
$ cat .terraform-version
0.11.14

$ terraform init \
    -backend-config=bucket=terraform.backend \
    -backend-config=key=terraform.tfstate \
    -backend-config=region=us-west-2 \
    /home/username/my-terraform/tf

$ terraform plan \
    -var-file=/home/username/my-terraform/conf/vars.tfvars \
    -refresh=true \
    /home/username/my-terraform/tf

$ terraform apply \
    -var-file=/home/username/my-terraform/conf/vars.tfvars \
    -refresh=true \
    /home/username/my-terraform/tf
```

`v0.11` 系から、リソースに変更がある場合は `apply` 実行時に差分が出力されたあと、以下のメッセージが出力されるようになります。
差分を確認して問題がなければ `yes` を入力して `apply` を確定します。

```bash
Do you want to perform these actions?
  Terraform will perform the actions described above.
  Only 'yes' will be accepted to approve.

  Enter a value: yes
```

## Terraform `v0.11.14` から `v0.12.28` へのバージョンアップ

`v0.12` 系から `HCL` の大幅な変更があり、何もせずにそのままバージョンだけ上げるのは難しいようです。
公式ドキュメントの[Upgrading to Terraform v0.12](https://www.terraform.io/upgrade-guides/0-12.html)に従いアップグレードしていきます。

### **`v0.12` 系へのバージョンアップの流れ**

1. Terrafrom `v0.11.14` までバージョンを上げる
2. アップグレード前のチェックリスト対応をする
   1. Terraform `v0.11.14` では `terraform 0.12checklist` というヘルパーコマンドが追加されており、アップグレード前に対応すべき項目を知ることができる
   2. チェックリストに従い、 `tf` ファイルの修正を行う
3. Terraform `v0.12` へアップグレードする
4. `terraform 0.12upgrade` でモジュールのアップグレードを行う
   1. Terraform `v0.12` では `terraform 0.12upgrade` というヘルパーコマンドが追加されており、Terraform `v0.11` 用に書かれたモジュールを `v0.12` 用に更新することができ

### `terraform 0.12checklist` に関する注意事項

**数字で始まる名前のモジュール名**についてはチェックリストツールで自動検出できない様です。
[Addendum: Invalid module names](https://www.terraform.io/upgrade-guides/0-12.html#addendum-invalid-module-names)に記載の通り、Terraform `v0.11.14` リリース時点でTerraformチームが認識していなかった事象とのことです。

### 実際にバージョンアップした時の様子

#### 1. Terrafrom `v0.11.14` までバージョンを上げる

これは前段のバージョンアップで実施済みです。

```bash
$ cat .terrafrom-version
0.11.14
```

#### 2. アップグレード前のチェックリスト対応をする

怖いぐらいすんなり通りました・・・。

```bash
$ terraform 0.12checklist /home/username/my-terraform/tf
Looks good! We did not detect any problems that ought to be
addressed before upgrading to Terraform v0.12.

This tool is not perfect though, so please check the v0.12 upgrade
guide for additional guidance, and for next steps:
    https://www.terraform.io/upgrade-guides/0-12.html
```

#### 3. Terraform `v0.12` へアップグレードする

Terraform `v0.12` へアップグレードするため、`.terraform-version` を書き換えて `terraform init` していきます。

```bash
$ vi .terraform-version
$ cat .terrafrom-version
0.12.28

$ terraform init \
    -backend-config=bucket=terraform.backend \
    -backend-config=key=terraform.tfstate \
    -backend-config=region=us-west-2 \
    /home/username/my-terraform/tf
```

`terraform init` 時に以下のエラーが出力されました。

##### Error: Unexpected comma after argument

```bash
Error: Unexpected comma after argument

  on ../../home/username/my-terraform/tf/iam_policy.tf line 4, in data "aws_iam_policy_document" "sample":
   4:         effect = "Deny",

Argument definitions must be separated by newlines, not commas. An argument
definition must end with a newline.
```

ポリシードキュメントの `effect` 定義の終端に `,(カンマ)` が入っているのが駄目になった様です。
終端の `,(カンマ)` を削除して `terraform init` したところ無事通りました。

#### 4. `terraform 0.12upgrade` でモジュールのアップグレードを行う

`terraform 0.12upgrade` で `tf` ファイルをTerraform `v0.12` 向けにアップグレードします。

```bash
$ terraform 0.12upgrade /home/username/my-terraform/tf

<snip>

This command will rewrite the configuration files in the given directory so
that they use the new syntax features from Terraform v0.12, and will identify
any constructs that may need to be adjusted for correct operation with
Terraform v0.12.

We recommend using this command in a clean version control work tree, so that
you can easily see the proposed changes as a diff against the latest commit.
If you have uncommited changes already present, we recommend aborting this
command and dealing with them before running this command again.

Would you like to upgrade the module in ../../home/livesen/mb-terraform-jsen/accounts/tf?
  Only 'yes' will be accepted to confirm.

  Enter a value: yes

-----------------------------------------------------------------------------

Upgrade complete!

The configuration files were upgraded successfully. Use your version control
system to review the proposed changes, make any necessary adjustments, and
then commit.
```

無事アップグレードできたので、試しに `plan` してみます。

```bash
$ terraform plan \
    -var-file=/home/username/my-terraform/conf/vars.tfvars \
    -refresh=true \
    /home/username/my-terraform/tf
```

`-var-file` で `tfvars` ファイルから変数を読み込んでいるので、以下の通り一部の初期化していない変数で `Warning` が出力されるようです。
こちらについては `variables.tf` ファイルなどで変数を定義しておきます。

```bash
Warning: Value for undeclared variable

The root module does not declare a variable named "env_name" but a value was
found in file "/home/livesen/mb-terraform-jsen/config/playground/env.tfvars".
To use this value, add a "variable" block to the configuration.

Using a variables file to set an undeclared variable is deprecated and will
become an error in a future release. If you wish to provide certain "global"
settings to all configurations in your organization, use TF_VAR_...
environment variables to set these instead.

Warning: Value for undeclared variable

The root module does not declare a variable named "az2" but a value was found
in file "/home/livesen/mb-terraform-jsen/config/playground/env.tfvars". To use
this value, add a "variable" block to the configuration.

Using a variables file to set an undeclared variable is deprecated and will
become an error in a future release. If you wish to provide certain "global"
settings to all configurations in your organization, use TF_VAR_...
environment variables to set these instead.

Warning: Value for undeclared variable

The root module does not declare a variable named "vpc_cidr_16" but a value
was found in file
"/home/livesen/mb-terraform-jsen/config/playground/env.tfvars". To use this
value, add a "variable" block to the configuration.

Using a variables file to set an undeclared variable is deprecated and will
become an error in a future release. If you wish to provide certain "global"
settings to all configurations in your organization, use TF_VAR_...
environment variables to set these instead.

Warning: Values for undeclared variables

In addition to the other similar warnings shown, 3 other variable(s) defined
without being declared.

Releasing state lock. This may take a few moments...
```

最後に、試しに `apply` してみましたが問題なく通りました。

```bash
$ terraform apply \
    -var-file=/home/username/my-terraform/conf/vars.tfvars \
    -refresh=true \
    /home/username/my-terraform/tf
```

#### 5. おまけ その1: 一部で`terraform 0.12upgrade` が上手くいかずに手動で書き換えた設定とエラーメッセージ

実は一部のコードについては  `terraform 0.12upgrade` でアップグレードできなかったため手動で書き換えた箇所がありました。
その際に出力されたエラーメッセージについてまとめます。
エラーについては、 `plan` 時と `tflint`  で出力されたものに関してまとめています。

##### Error: Unsupported block type

```bash
Error: Unsupported block type

  on ../../home/username/my-terraform/tf/dmz_network.tf line 6, in resource "aws_vpc" "dmz":
   6:     tags {

Blocks of type "tags" are not expected here. Did you mean to define argument
"tags"? If so, use the equals sign to assign it a value.
```

tagsの記法が以下の記法でないと駄目になった様です。

```bash
tags = {
   "Name" = "Sample"
}
```

##### Incorrect attribute value type

```bash
Error: Incorrect attribute value type

  on ../../home/username/my-terraform/tf/dmz_network.tf line 84, in resource "aws_security_group" "dmz":
  84:     cidr_blocks = ["${var.vpc_network_address}", "${var.own_ips}"]

Inappropriate value for attribute "cidr_blocks": element 1: string required.
```

以前 `cidr_blocks` に設定する値は `string` と `list` 形式が混在していても問題なかったのですが、 `string` 型の `list` として `list(string)` を指定するようになったようです。

`string` 型を維持する場合は以下の様に `concat`を使って `list`の結合を行います。

```bash
cidr_blocks = concat(var.own_ips, [var.vpc_network_address])
```

今回は `var.vpc_network_address` の参照箇所がここだけだったので型を `string` 型から `list` 型に変更して以下の記載としました。

```bash
cidr_blocks = concat(var.own_ips, var.vpc_network_address)
```

##### Warning: Quoted type constraints are deprecated

変数を宣言する際、 `type` の指定は `"(ダブルクォート)"` が不要になった様です。

```bash
Warning: Quoted type constraints are deprecated

  on ../../home/username/my-terraform/tf/variables.tf line 2, in variable "env_site_url":
   2:   type    = "map"

Terraform 0.11 and earlier required type constraints to be given in quotes,
but that form is now deprecated and will be removed in a future version of
Terraform. To silence this warning, remove the quotes around "map" and write
map(string) instead to explicitly indicate that the map elements are strings.
```

以下の様に修正しました。

```bash
    type    = map
```

##### Warning: Interpolation-only expressions are deprecated

他のリソース情報を参照する際は `"${}"` で括らない記法になった様です。

```bash
Warning: Interpolation-only expressions are deprecated

  on ../../home/username/my-terraform/tf/iam.tf line 41, in resource "aws_iam_policy_attachment" "sample_policy_attach":
  41:   policy_arn = "${aws_iam_policy.sample.arn}"

Terraform 0.11 and earlier required all non-constant expressions to be
provided via interpolation syntax, but this pattern is now deprecated. To
silence this warning, remove the "${ sequence from the start and the }"
sequence from the end of this expression, leaving just the inner expression.

Template interpolation syntax is still used to construct strings from
expressions when the template includes multiple interpolation sequences or a
mixture of literal strings and interpolations. This deprecation applies only
to templates that consist entirely of a single interpolation sequence.
```

#### 6.おまけ その2: 新記法でなくとも `tflint`や `terraform init`, `terraform plan` 時に特に何もエラーが出ない記法

##### tagsで変数を使っている箇所

変数の参照で、以下の記法でも特に問題ないようです。

```bash
tags = {
    "Name"       = "AnyBatch"
    "Role"       = "Batch"
    "Env"        = "${var.env_label}"
}
```

他と記法を合わせる意味で、今回は以下の通り記載しました。
※ただし、VSCodeのシンタックスハイライトが効かなくなるので好みによりそうです

```bash
tags = {
    "Name"       = "AnyBatch"
    "Role"       = "Batch"
    "Env"        = var.env_label
}
```

##### listで変数を使っている箇所

listで記載するものについて、AWSリソースによっては特に何も指摘されないものがありました。

```bash
resource "aws_cloudfront_distribution" "promotion" {
<snip>
  aliases = ["${var.promotion_url[var.env_name]}"]
<snip>
```

こちらも他と記法を合わせる意味で、今回は以下の通り記載しました。

```bash
resource "aws_cloudfront_distribution" "promotion" {
<snip>
  aliases = [var.promotion_url[var.env_name]]
<snip>
```

その他、以下の項目でも、リスト内の変数参照に `"${}"` が付いていても特に何も指摘されませんでした。

- aws_network_interface
  - vpc_security_group_ids
- aws_instance
  - vpc_security_group_ids

# v0.12.28→v0.13.2

`v0.12.28`から `v0.13.2` へ上げる際、一部で以下の差分が出ました。

```bash
------------------------------------------------------------------------

An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
 <= read (data resources)

Terraform will perform the following actions:

  # data.aws_acm_certificate.promotion will be read during apply
  # (config refers to values not yet known)
 <= data "aws_acm_certificate" "promotion"  {
        arn         = "arn:aws:acm:us-east-1:************:certificate/********-****-****-****-************"
        domain      = "*.stg.sample.domain"
      ~ id          = "2020-09-17 06:55:01.618103996 +0000 UTC" -> "2020-09-17 06:57:05.718177187 +0000 UTC"
        most_recent = false
        tags        = {}
    }

Plan: 0 to add, 0 to change, 0 to destroy.

------------------------------------------------------------------------
```

手動で作成した `ACM`を `data source`として読み込んでいる場合、 `id`の部分が適用時のものに更新される様です。
`add` でも `change` でもない差分らしく、plan結果のカウントは全て `0` になります。
そして、一度 `apply` しても `plan` する度に差分として出力されます。
[terraform-provider-awsのコード](https://github.com/terraform-providers/terraform-provider-aws/blob/master/aws/data_source_aws_acm_certificate.go#L172)を読んでみると

```bash
d.SetId(time.Now().UTC().String())
```

と書いてありますね・・・。
`data source` を読み込む度に、読み込んだ日時に更新する実装になっている様です。
取り急ぎ、今回のバージョンアップに直接影響しないので別途対応としました。
**※2020/10/14現在、こちらのコミットにより修正されています**
https://github.com/terraform-providers/terraform-provider-aws/commit/9d9bcbd8931b628b8213541dfc61809623a911ed#diff-a71cf6c5f1fdbb1915e591e3ca8872ac05e1b7a650a61034b55b40f8da061bc3

```bash
$ cat .terraform-version
0.13.2

$ terraform init \
    -backend-config=bucket=terraform.backend \
    -backend-config=key=terraform.tfstate \
    -backend-config=region=us-west-2 \
    /home/username/my-terraform/tf

$ terraform plan \
    -var-file=/home/username/my-terraform/conf/vars.tfvars \
    -refresh=true \
    /home/username/my-terraform/tf

$ terraform apply \
    -var-file=/home/username/my-terraform/conf/vars.tfvars \
    -refresh=true \
    /home/username/my-terraform/tf
```

# おわりに

以上で 2020/10/02 時点の最新バージョン `v0.13.2` まで上げ終わりました。
実行する環境によってハマりどころが変わるとは思いますが、バージョンアップされる方の助けになればと思います。

# 参考リンク

- Terraform 0.12がリリースされたのでアップグレードしてみた
  - [https://dev.classmethod.jp/articles/terraform-upgrade-from-0-11-to-0-12/](https://dev.classmethod.jp/articles/terraform-upgrade-from-0-11-to-0-12/)
- Upgrading to Terraform v0.12
  - [https://www.terraform.io/upgrade-guides/0-12.html](https://www.terraform.io/upgrade-guides/0-12.html)
- Terraform AWS Provider Version 3 Upgrade Guide
  - [https://registry.terraform.io/providers/hashicorp/aws/latest/docs/guides/version-3-upgrade](