terraform apply 実行時の「...api error Resource.AlreadyAssociated: resource igw-XXX is already attached to network vpc-XXX...」エラーに対するトラブルシューティング

# 用語
IGW: インターネットゲートウェイ

# 注意
`terraform apply`実施時のエラーのトラブルシューティングというタイトルですが、事前に適切に、state コマンド([tfmigrate](https://github.com/minamijoyo/tfmigrate))を使えばエラーは起きないです。

# 目的
`aws_internet_gateway_attachment`リソースを導入して、`terraform apply`を実施したときに発生する下記エラーに対するトラブルシューティングを知る。
```
... api error Resource.AlreadyAssociated: resource igw-XXXXXXXXXXXXXXX is already attached to network vpc-XXXXXXXXXXXXXXX ...
```

# 結論
下記のファイルを用意して、`tfmigrate apply`コマンドを実施してから、`terraform apply`を実行する。
私は、`tfmigrate`を使っていますが、`tfmigrate` を使わずに直接下記コマンドを実行してもいいです。
``` tfmigrate/20241020234500_adopt_aws_internet_gateway_attachment.hcl
migration "state" "adopt_aws_internet_gateway_attachment" {
  actions = [
    "rm module.igw_normal.aws_internet_gateway.m_igw",
    "import module.igw_normal.aws_internet_gateway.m_igw igw-042b28c863cc8d2e6",
    "import aws_internet_gateway_attachment.igwa igw-042b28c863cc8d2e6:vpc-0eaa57c8667ee4782"
  ]
}
```

# 説明
## エラーを見てみる
下記のように、リファクタリングする。
IGW リソースに直接 VPC リソースを指定するのではなくて、`aws_internet_gateway_attachment` リソースを使った管理の方法に変更しようとした。
このとき、実際のインフラの構成は全く変わらないはずである。

**変更前のコード**
``` aws_internet_gateway.tf
module "igw_normal" {
  source = "./modules/internet_gateway"
  vpc_id = aws_vpc.tf_my_vpc.id
  name   = "MyInternetGateway"
}
```
``` modules/internet_gateway/main.tf
resource "aws_internet_gateway" "m_igw" {
  vpc_id = var.vpc_id

  tags = {
    Name = var.name
  }
}
```

**変更後のコード**
``` aws_internet_gateway.tf
module "igw_normal" {
  source = "./modules/internet_gateway"
  vpc_id = aws_vpc.tf_my_vpc.id # 使ってない
  name   = "MyInternetGateway"
}

resource "aws_internet_gateway_attachment" "igwa" {
  internet_gateway_id = module.igw_normal.internet_gateway_id
  vpc_id              = aws_vpc.tf_my_vpc.id
}
```

``` modules/internet_gateway/main.tf
resource "aws_internet_gateway" "m_igw" {
  # vpcの指定をしないようにした
  tags = {
    Name = var.name
  }
}
```

これで、`terraform apply`を実行したら、下記のエラーが出た。
```
aws_internet_gateway_attachment.igwa: Creating...
╷
│ Error: creating EC2 Internet Gateway Attachment: attaching EC2 Internet Gateway (igw-XXXXXXXXXXXXXXX) to VPC (vpc-XXXXXXXXXXXXXXX): operation error EC2: AttachInternetGateway, https response error StatusCode: 400, RequestID: XXXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX, api error Resource.AlreadyAssociated: resource igw-XXXXXXXXXXXXXXX is already attached to network vpc-XXXXXXXXXXXXXXX
│
│   with aws_internet_gateway_attachment.igwa,
│   on aws_internet_gateway.tf line 7, in resource "aws_internet_gateway_attachment" "igwa":
│    7: resource "aws_internet_gateway_attachment" "igwa" {
│
╵
Error: Process completed with exit code 1.
```

## エラーの原因
エラーの原因は、「既存の IGW には何も起きずに、`aws_internet_gateway_attachment` リソースが新規作成される」と判断されて、もともと IGW が VPC にアタッチされているのに、さらに同じ IGW が VPC にアタッチされようとしてしまった。
一つの VPC にアタッチできる IGW は一個なので、二つ目がアタッチされようとして(同じIGWであるが...)、エラーが起きてしまった。

## エラーの考察
`terraform apply`を実施すると、実際のインフラの状態 (= tfstate ファイルの状態)と、tf ファイルとの差分を調べて、インフラ構築作業が実施される。`terraform apply`は、内部的に `terraform refresh` をするので、実際のインフラの状態とtfstate ファイルの状態に齟齬があっても同期される。
ただ、ここで疑問が生じる。
今回のリファクタリングは、IGW と VPC の紐づきの管理の仕方を変更しただけで、実際のインフラに影響を及ぼすような変更は全く加えていない。つまり、tf ファイルが定義するインフラ状態に変更はないはずである。なので、tfstate ファイルに書かれている実際のインフラ状態と、tf ファイルが定義するインフラ状態の差分を見ても、差分は一切生じないはずである。なのにもかかわらず、`terraform apply`を実施すると、 「既存の IGW には何も起きずに、`aws_internet_gateway_attachment` リソースが新規作成される」と判断されてしまう。
この理由は、tfstate ファイルは、実際のインフラ状態と tf ファイルが定義するインフラ状態だけではなくて、**tf ファイルによる管理の方法**も保持する。**tf ファイルによる管理の方法** というのは、コードの書き方みたいなものであり、例えば、terraform 上でのリソース名や、今回のような定義の仕方が含まれる。これにより、今回のリファクタリングが、リソースを新規作成するものと勘違いされてしまった。

## 疑問
私は、このようなエラーに直面したとき、tfstate ファイルがなぜ、**tf ファイルによる管理の方法**まで保持するのか疑問に思ってしまった。
tfstate ファイルは、tf ファイルで定義されるインフラ状態のみを認識し、**tf ファイルによる管理の方法**までを認識する必要はないと思った。
そうすれば、今回のようなリファクタリングをした際に、`terraform apply`を実施しても、差分は全く生じずに、エラーも起きないはずである。

## トラブルシューティング
結論の章で述べたコードを用意して、`tfmigrate apply` を実施してから、`terraform apply`を実施する。
`tfmigrate apply`でやっていることを説明しておく。まず、IGW を一回取り除いて、VPC への依存を持たない IGW として再定義する。そして、`aws_internet_gateway_attachment`リソースによって、IGW と VPC を紐づけてあげる。