ステートバケットでtfstateを管理しているときに更新競合したらどうなるか確認した

# はじめに
Terraformをチームで開発する際に、リポジトリ管理＋ステートバケットを使って管理するのが良いとされているが、何も考えずにtfstateを競合させた場合に何が起きるかを確認した。

# 構成
もともと以下の構成をしているTerraformのリポジトリがあるとする。

```
.
├── 00_main.tf
└── 01_resource1.tf
```

```HCL:00_main.tf
terraform {
  backend "s3" {
    bucket = "terraform-test"
    key    = "test/terraform.tfstate"
    region = "ap-northeast-1"
  }
```

```HCL:01_resource1.tf
resource "aws_s3_bucket" "terraform_test_1" {
  bucket = "terraform-test-1"
  acl    = "private"
}
```

このリポジトリで、メンバAとBが上記状態をチェックアウトした後に

- メンバAが以下のリソースをapply

```HCL:02_resource2.tf
resource "aws_s3_bucket" "terraform_test_2" {
  bucket = "terraform-test-2"
  acl    = "private"
}
```

- その後、メンバBがリポジトリを更新しないで以下のリソースをapply

```HCL:03_resource2.tf
resource "aws_s3_bucket" "terraform_test_3" {
  bucket = "terraform-test-3"
  acl    = "private"
}
```

としたらどうなるか。

# 実験結果
もしかしたらTerraformが良い感じにtfstateの更新日時とかで管理してくれるかと思ったがそんなことはなかった。

メンバAのterraform planでは当然、```terraform_test_2```のバケットのaddだけができた。

```
Terraform will perform the following actions:

  # aws_s3_bucket.terraform_test_2 will be created
  + resource "aws_s3_bucket" "terraform_test_2" {
      + acceleration_status         = (known after apply)
      + acl                         = "private"
      + arn                         = (known after apply)
      + bucket                      = "terraform-test-2"
      + bucket_domain_name          = (known after apply)
      + bucket_regional_domain_name = (known after apply)
      + force_destroy               = false
      + hosted_zone_id              = (known after apply)
      + id                          = (known after apply)
      + region                      = (known after apply)
      + request_payer               = (known after apply)
      + website_domain              = (known after apply)
      + website_endpoint            = (known after apply)

      + versioning {
          + enabled    = (known after apply)
          + mfa_delete = (known after apply)
        }
    }

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

しかし、メンバBのterraform planでは、以下のようになってしまった。

```
Terraform will perform the following actions:

  # aws_s3_bucket.terraform_test_2 will be destroyed
  - resource "aws_s3_bucket" "terraform_test_2" {
      - acl                         = "private" -> null
      - arn                         = "arn:aws:s3:::terraform-test-2" -> null
      - bucket                      = "terraform-test-2" -> null
      - bucket_domain_name          = "terraform-test-2.s3.amazonaws.com" -> null
      - bucket_regional_domain_name = "terraform-test-2.s3.ap-northeast-1.amazonaws.com" -> null
      - force_destroy               = false -> null
      - hosted_zone_id              = "Z2M4EHUR26P7ZW" -> null
      - id                          = "terraform-test-2" -> null
      - region                      = "ap-northeast-1" -> null
      - request_payer               = "BucketOwner" -> null

      - versioning {
          - enabled    = false -> null
          - mfa_delete = false -> null
        }
    }

  # aws_s3_bucket.terraform_test_3 will be created
  + resource "aws_s3_bucket" "terraform_test_3" {
      + acceleration_status         = (known after apply)
      + acl                         = "private"
      + arn                         = (known after apply)
      + bucket                      = "terraform-test-3"
      + bucket_domain_name          = (known after apply)
      + bucket_regional_domain_name = (known after apply)
      + force_destroy               = false
      + hosted_zone_id              = (known after apply)
      + id                          = (known after apply)
      + region                      = (known after apply)
      + request_payer               = (known after apply)
      + website_domain              = (known after apply)
      + website_endpoint            = (known after apply)

      + versioning {
          + enabled    = (known after apply)
          + mfa_delete = (known after apply)
        }
    }

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

うーむ、これは危険。ちゃんとterraform planの内容を確認しないと、他人の作ったリソースをあっさり消してしまう可能性があるということか……。

しかも、リソースに```prevent_destroy = true```すれば消えないかと思いきや、あくまでも「prevent_destroy = trueが書かれたファイルを明示的にdestroyで消せない」だけであり、そもそも.tfファイルが存在しない場合は容赦なくdestoryの対象になってしまうようだ。

もちろん、メンバAが正しく```git push```をして、メンバBが適宜```git pull```してくれればこの悲劇は回避されるが、結局、人依存の運用にしてしまうのはイケてないなぁ……。エンタープライズ版にすると、この問題は回避されるのだろうか。

# 結論
そうか、そもそも人力で```terraform apply```するということ自体がナンセンスということか。

複数人でリポジトリ管理するのであれば、しっかりとブランチ戦略を作り、検証の済んだIaCをmasterブランチにプルリクして、然るべきメンバがレビュー承認をすることで初めて商用環境向けのIaCのデプロイパイプラインが起動して、その中で間違いがないかを確認するフローを作り上げるべきなんだ。

[実践Terraform　AWSにおけるシステム設計とベストプラクティス](https://www.amazon.co.jp/dp/B07XT7LJLC/)がすべてを物語っていた。

ということで、中途半端に人力でterraform applyをする環境を作ってはいけない。
せいぜい検証環境や開発環境までにしておけ、ということだ。