個人開発のTerraformをv0.12にアップグレードした

# 概要
友人（@kobayashi-m42）と一緒に運用しているサービス [Mindexer(ミンデクサー)](https://www.mindexer.net/) で利用しているTerraformをv0.12にアップグレードした時の対応内容をまとめた記事です。

# Mindexer(ミンデクサー)について

[AWS + Laravel + Vue.js でQiitaのストックを整理するサービスを作りました！【個人開発】](https://qiita.com/kobayashi-m42/items/14e137727ffda3bf79e7)
にてサービス概要を解説していますので、こちらも合わせて見ていただけますと幸いです🐱
バックエンド、フロントエンドの技術についてもソースコード付きで解説しています。

# 対象読者
- TerraformとAWSを運用レベルで利用している方

# ソースコードを見たい方へ

てっとり早く変更時の差分を見たい方は [こちら](https://github.com/nekochans/qiita-stocker-terraform/pull/107/files) をご覧ください。

アップグレード時を実行した際の差分になります。

大部分の修正は後で説明する `terraform 0.12upgrade` コマンドによる物です。

# 事前準備

[Upgrading to Terraform v0.12](https://www.terraform.io/upgrade-guides/0-12.html) に書かれている内容に従いアップグレードの事前準備を行います。

## バージョン0.11.4までのアップグレードを完了させておく

[Upgrading to Terraform v0.12](https://www.terraform.io/upgrade-guides/0-12.html) に書かれている通り、まずは `0.11.4` までアップグレードを完了させます。

今どのバージョンを利用しているかにもよりますが、いきなり `0.12` にアップグレードするとトラブルが起きる可能性があるので、こうしておいたほうが無難です。

また `0.11.4` には `0.12` との互換性チェックコマンドが実行出来るようになるので、それを利用する為にもまずは `0.11.4` に上げておくのが無難です。

## バージョン0.11.4で `terraform apply` を実行しておく

`terraform apply` を実行しておき、差分がない状態にしておきます。

これもトラブルを避ける為にやっておいたほうが無難です。

## `terraform 0.12checklist` で互換性をチェックする

`0.11.4` には `0.12checklist` というサブコマンドが実装されています。

`tfstate` を管理しているディレクトリ配下に移動して以下のコマンドを実行します。

`terraform 0.12checklist`

[Mindexer](https://www.mindexer.net/) では以下のディレクトリ単位で `tfstate` を管理しているので、以下のディレクトリで実行しました。

```
- providers/aws/environments/10-network
- providers/aws/environments/11-acm
- providers/aws/environments/12-ecr
- providers/aws/environments/20-bastion
- providers/aws/environments/21-api
- providers/aws/environments/22-frontend
- providers/aws/environments/23-rds
```

下記のような結果が表示されればupgradeの準備は完了です。

```
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
```

もしアップグレードの必要があるProviderがあった場合はメッセージの指示に従いアップグレードを実施します。

[Mindexer](https://www.mindexer.net/) では [terraform-provider-aws](https://github.com/terraform-providers/terraform-provider-aws) を利用していたのでこのバージョンを最新に更新しました。

# `0.12` にアップグレードを行う

Terraformのバージョンを `0.12` にアップグレードします。

# `terraform 0.12upgrade` を実施する

upgrade用のコマンドを実行する事でソースコードの修正を行う事が出来ます。

`tfstate` を管理している以下のディレクトリで `terraform 0.12upgrade` を実行します。

```
- providers/aws/environments/10-network
- providers/aws/environments/11-acm
- providers/aws/environments/12-ecr
- providers/aws/environments/20-bastion
- providers/aws/environments/21-api
- providers/aws/environments/22-frontend
- providers/aws/environments/23-rds
```

```
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 the current directory?
  Only 'yes' will be accepted to confirm.

  Enter a value:
```

`yes` を押すと書き換えが完了します。

同一プロジェクトからmoduleをインポートしている場合、そのmodule内のコードは書き換えられないので、そのような場合は各moduleに対しても実行してあげる必要があります。

```
terraform 0.12upgrade ../../../../modules/aws/vpc/
terraform 0.12upgrade ../../../../modules/aws/acm/
terraform 0.12upgrade ../../../../modules/aws/ecr/
terraform 0.12upgrade ../../../../modules/aws/api/
terraform 0.12upgrade ../../../../modules/aws/bastion/
terraform 0.12upgrade ../../../../modules/aws/frontend/
terraform 0.12upgrade ../../../../modules/aws/rds/
```

# `terraform init` の実行しエラーになった箇所を修正する

`tfstate` を管理している以下のディレクトリで `terraform init` を実行します。

```
- providers/aws/environments/10-network
- providers/aws/environments/11-acm
- providers/aws/environments/12-ecr
- providers/aws/environments/20-bastion
- providers/aws/environments/21-api
- providers/aws/environments/22-frontend
- providers/aws/environments/23-rds
```

`terraform 0.12upgrade` による書き換えだけでは修正出来なかった部分があるので、この部分を地道に修正していきます。

# 対応内容

以下で解説するのは、`terraform 0.12upgrade` で書き換え出来なかった部分になります。

## インデント崩れや空行の修正

`terraform 0.12upgrade` を実行した時にインデント崩れや余計な空行が追加されたりする現象が発生しました。

`terraform fmt` を実行しても修正されなかったので、この部分は自力で修正しました。

## The terraform.env attribute was deprecated in v0.10 and removed in v0.12.

`terraform.env` を使っている箇所を `terraform.workspace` に修正しました。

ちなみにこれは `v0.10` の時点で非推奨になっている機能だったので本当はもう少し早く直しておくべき内容でした。

```
Error: Invalid "terraform" attribute

  on ../../../../modules/aws/vpc/main.tf line 2, in resource "aws_vpc" "vpc":
   2:   cidr_block           = lookup(var.vpc, "${terraform.env}.cidr", var.vpc["default.cidr"])

The terraform.env attribute was deprecated in v0.10 and removed in v0.12. The
"state environment" concept was rename to "workspace" in v0.12, and so the
workspace name can now be accessed using the terraform.workspace attribute.
```

## Error: Ambiguous attribute key

[Mindexer](https://www.mindexer.net/) では `terraform.workspace` を使ってステージング環境と本番環境等の環境を分けています。

下記のように `terraform.workspace` の値を利用して各環境毎に環境変数を設定するというテクニックを利用していました。

```tf
variable "vpc" {
  type = "map"

  default = {
    default.az_1a      = "ap-northeast-1a"
    default.az_1c      = "ap-northeast-1c"
    default.az_1d      = "ap-northeast-1d"
    default.name       = "prod-qiita-stocker-vpc"
    stg.name           = "stg-qiita-stocker-vpc"
    default.cidr       = "10.1.0.0/16"
    stg.cidr           = "10.3.0.0/16"
  }
}

resource "aws_vpc" "vpc" {
  cidr_block           = "${lookup(var.vpc, "${terraform.env}.cidr", var.vpc["default.cidr"])}"
  enable_dns_support   = "true"
  enable_dns_hostnames = "true"

  tags {
    Name = "${lookup(var.vpc, "${terraform.env}.name", var.vpc["default.name"])}"
  }
}
```

`terraform plan` を実行すると以下のようなエラーが大量に出ました。

```
Error: Ambiguous attribute key

  on ../../../../modules/aws/vpc/variable.tf line 5, in variable "vpc":
   5:     default.az_1a      = "ap-northeast-1a"

If this expression is intended to be a reference, wrap it in parentheses. If
it's instead intended as a literal name containing periods, wrap it in quotes
to create a string literal.
```

対処方法は簡単でmap.defaultに使われているキー名を "" で囲うだけです。

以下のように修正する事でアップグレード前と同様に利用出来るようになりました。

```tf
variable "vpc" {
  type = map(string)

  default = {
    "default.az_1a"      = "ap-northeast-1a"
    "default.az_1c"      = "ap-northeast-1c"
    "default.az_1d"      = "ap-northeast-1d"
    "default.name"       = "prod-qiita-stocker-vpc"
    "stg.name"           = "stg-qiita-stocker-vpc"
    "default.cidr"       = "10.1.0.0/16"
    "stg.cidr"           = "10.3.0.0/16"
  }
}

resource "aws_vpc" "vpc" {
  cidr_block           = lookup(var.vpc, "${terraform.workspace}.cidr", var.vpc["default.cidr"])
  enable_dns_support   = "true"
  enable_dns_hostnames = "true"

  tags = {
    Name = lookup(var.vpc, "${terraform.workspace}.name", var.vpc["default.name"])
  }
}
```

## `terraform fmt` の仕様が変更された？

以前までのバージョン（`0.11.4` まで）ではプロジェクトルートで `terraform fmt` を実行するだけで全ての `.tf` ファイルをFormatterにかける事が出来たのですが、同じ事をやる為には `-recursive` オプションを付けて実行する必要があるようになっています。

`terraform fmt -recursive`


# おわりに
今回の `0.12` へのバージョンアップは破壊的な変更が多く実質的なメジャーバージョンアップだったと言えます。

とは言え公式ドキュメントに丁寧に移行方法が記載されていたり、 `terraform 0.12upgrade` 等の補助用のコマンドが用意されていたので比較的スムーズにアップグレードを実施出来ました。

以上になります。最後まで読んで頂きありがとうございます。