Terraform 0.13へのアップグレード

# 概要

[Terraform 0.13がリリース](https://www.hashicorp.com/blog/announcing-hashicorp-terraform-0-13/) されたので、早速アップグレードしてみました。基本的な流れは [Terraform AWS Provider Version 3 Upgrade Guide](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/guides/version-3-upgrade) に書いてあるので、その通り改修すれば良いのですが、いくつか躓きそうなポイントがあったのでリストアップします。

# 検証バージョン

TerraformではAWSやDatadogのリソースを管理しており、それぞれ次のバージョンを使いました。

* Terraform: `0.13.0`
* AWS provider: `2.3.0`
* Datadog provider: `2.12.1`
* GitHub provider: `2.9.2`

# 事前準備

Terraformを0.12の最新版、各種プロバイダも最新版に更新した上でstateを更新しておくとトラブルシューティングがしやすくなります。
プロバイダにAWSを利用している場合、モジュールには [terraform-aws-modules](https://github.com/terraform-aws-modules) を利用することが多いと思いますので、事前に0.13対応バージョンに更新しておきましょう。

# アップグレードの流れ

Terraformを0.13にアップグレード後、リソースディレクトリ下で以下の操作を行います。

```
# ステートファイルの更新
$ terraform init [-reconfigure]

# TFファイルの書式を更新
$ terraform 0.13upgrade

# 差分がないことを確認
$ terraform plan
```

`plan` でエラーが出なくても `apply` 時にエラーが出ることがあるので、念の為applyしてステートを更新しておきます。

# エラーリスト

## Invalid resource instance data in state

```
% terraform apply

Error: Invalid resource instance data in state

  on main.tf line 4:
   4: resource "aws_route53_zone" "this" {

Instance aws_route53_zone.this data could not be decoded from the state:
unsupported attribute "vpc_id"
```

0.13で廃止されたパラメータがstateファイルに残っている場合に起きます。backendからstateファイルを取得し、対象パラメータ (上記例の場合は `vpc_id`) を削除して更新すれば解決します。

## aws_route53_recordを削除しようとする

```
%  terraform apply
Terraform will perform the following actions:

  # module.acm.aws_acm_certificate_validation.default must be replaced
-/+ resource "aws_acm_certificate_validation" "default" {
        certificate_arn         = "***"
      ~ id                      = "***" -> (known after apply)
      ~ validation_record_fqdns = [
          - "***",
        ] -> (known after apply) # forces replacement

      - timeouts {}
    }

  # module.acm.aws_route53_record.validation will be destroyed
  - resource "aws_route53_record" "validation" {
      - allow_overwrite = true -> null
      - fqdn            = "***" -> null
      - id              = "***" -> null
      - name            = "***" -> null
      - records         = [
          - "***",
        ] -> null
      - ttl             = 60 -> null
      - type            = "***" -> null
      - zone_id         = "***" -> null
    }

  # module.acm.aws_route53_record.validation[1] will be destroyed
  - resource "aws_route53_record" "validation" {
      - allow_overwrite = true -> null
      - fqdn            = "***" -> null
      - id              = "***" -> null
      - name            = "***" -> null
      - records         = [
          - "***",
        ] -> null
      - ttl             = 60 -> null
      - type            = "***" -> null
      - zone_id         = "***" -> null
    }

  # module.acm.aws_route53_record.validation["subdomain.***"] will be created
  + resource "aws_route53_record" "validation" {
      + allow_overwrite = true
      + fqdn            = (known after apply)
      + id              = (known after apply)
      + name            = "***"
      + records         = [
          + "***",
        ]
      + ttl             = 60
      + type            = "***"
      + zone_id         = "***"
    }

  # module.acm.aws_route53_record.validation["***"] will be created
  + resource "aws_route53_record" "validation" {
      + allow_overwrite = true
      + fqdn            = (known after apply)
      + id              = (known after apply)
      + name            = "***"
      + records         = [
          + "***",
        ]
      + ttl             = 60
      + type            = "CNAME"
      + zone_id         = "Z3K9WE75AB6672"
    }
```

`domain_validation_options` の型がListからSet型に変更されたために起きる問題です。[Upgrade Guide](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/guides/version-3-upgrade#resource-aws_acm_certificate) にも書かれてはいますが、具体的にはACMに紐付くドメインレコード単位で `state mv`を用いてIDを変更すればOK。数が多いとそこそこ大変。

```
% terraform state mv 'module.acm.aws_route53_record.validation[0]' 'module.acm.aws_route53_record.validation["***"]'
% terraform state mv 'module.acm.aws_route53_record.validation[1]' 'module.acm.aws_route53_record.validation["subdomain.***"]'
```

## Failed to install provider from shared cache

```
% terraform init -reconfigure
Initializing modules...

Initializing the backend...

Successfully configured the backend "s3"! Terraform will automatically
use this backend unless the backend configuration changes.

Initializing provider plugins...
- Finding terraform-providers/datadog versions matching "2.12.1"...
- Using terraform-providers/datadog v2.12.1 from the shared cache directory

Error: Failed to install provider from shared cache

Error while importing terraform-providers/datadog v2.12.1 from the shared
cache directory: after linking
registry.terraform.io/terraform-providers/datadog from provider cache at
/app/terraform/plugins it is still not detected in the target directory; this
is a bug in Terraform.
```

Datadogプロバイダを初期化中に起きました。プロバイダ名が `hashicorp/datadog` から `terraform-providers/datadog` に変わった関係かもしれません。ローカルに作成された `.terraform` ディレクトリを削除したら直りました。

## Could not load plugin

```
$ terraform apply
Error: Could not load plugin


Plugin reinitialization required. Please run "terraform init".

Plugins are external binaries that Terraform uses to access and manipulate
resources. The configuration provided requires plugins which can't be located,
don't satisfy the version constraints, or are otherwise incompatible.

Terraform automatically discovers provider requirements from your
configuration, including providers used in child modules. To see the
requirements and constraints, run "terraform providers".

Failed to instantiate provider "registry.terraform.io/-/aws" to obtain schema:
unknown provider "registry.terraform.io/-/aws"
```

Workspaceを利用したディレクトリで発生。Workspaceごとにstateファイルのバージョンが異なることが原因のようです。init、planに問題がなければapplyしてstateファイルを更新すれば解決します。