Terraform for_each 構文いろいろ

# はじめに

今更ですが、 Terraform のループ構文である for_each を自分用備忘メモとしてまとめました。

## 基本的な使い方

for_each は、リストやマップを元にリソースを繰り返し生成する場合に使います。
for_each を指定すると、Terraform はリソースをループし、指定されたキーごとにリソースを作成します。

```hcl
variable "buckets" {
  type = list(string)
  default = [
    "test-bucket-1",
    "test-bucket-2",
    "test-bucket-3"
  ]
}

resource "aws_s3_bucket" "toset" {
  for_each = toset(var.buckets)
  bucket = each.key
}
```

```hcl
# plan 結果

  # aws_s3_bucket.toset["test-bucket-1"] will be created
  + resource "aws_s3_bucket" "toset" {
      + bucket                      = "test-bucket-1"
...

  # aws_s3_bucket.toset["test-bucket-2"] will be created
  + resource "aws_s3_bucket" "toset" {
      + bucket                      = "test-bucket-2"
...

  # aws_s3_bucket.toset["test-bucket-3"] will be created
  + resource "aws_s3_bucket" "toset" {
      + bucket                      = "test-bucket-3"
...

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

## マップでの使い方

マップを使うと、キーと値のペアでリソースを生成できます。

```hcl
variable "bucket_map" {
  type = map(string)
  default = {
    "test-bucket-1" : "dev"
    "test-bucket-2" : "stg"
    "test-bucket-3" : "prod"
  }
}

resource "aws_s3_bucket" "map" {
  for_each      = var.bucket_map
  
  bucket = "${each.key}-${each.value}"
}
```

```hcl
# plan 結果

  # aws_s3_bucket.map["test-bucket-1"] will be created
  + resource "aws_s3_bucket" "map" {
      + bucket                      = "test-bucket-1-dev"
...

  # aws_s3_bucket.map["test-bucket-2"] will be created
  + resource "aws_s3_bucket" "map" {
      + bucket                      = "test-bucket-2-stg"
...

  # aws_s3_bucket.map["test-bucket-3"] will be created
  + resource "aws_s3_bucket" "map" {
      + bucket                      = "test-bucket-3-prod"
...

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

## リストでのリソース生成（インデックス利用）

リストを使う場合、`tolist()` で明示的に set 型から list 型に変換し、リストのインデックスを使ってユニークな ID を付けることができます。
以下では、インデックス `idx` を利用してリソース ID をユニークにしています。

```hcl
variable "buckets" {
  type    = list(string)
  default = ["apple", "banana", "orange"]
}

resource "aws_s3_bucket" "list" {
  for_each = { for idx, name in tolist(var.buckets) : idx => name }
  
  bucket = "test-${each.value}-bucket-${each.key}"
}
```

```hcl
# plan 結果

  # aws_s3_bucket.list["0"] will be created
  + resource "aws_s3_bucket" "list" {
      + bucket                      = "test-apple-bucket-0"
...

  # aws_s3_bucket.list["1"] will be created
  + resource "aws_s3_bucket" "list" {
      + bucket                      = "test-banana-bucket-1"
...

  # aws_s3_bucket.list["2"] will be created
  + resource "aws_s3_bucket" "list" {
      + bucket                      = "test-orange-bucket-2"
...

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

## 入れ子マップでの使い方

複雑なオブジェクトやマップの入れ子になったデータ構造を使うこともできます。
以下では、bucket_configs のそれぞれのオブジェクトから `environment` と `force_destroy` を抽出し、リソースに設定しています。

```hcl
variable "bucket_configs" {
  type = map(object({
    environment   = string
    force_destroy = bool
  }))
  default = {
    "apple"   = { environment = "dev", force_destroy = true }
    "banana"  = { environment = "stg", force_destroy = true }
    "orange"  = { environment = "prod", force_destroy = false }
  }
}
resource "aws_s3_bucket" "nest_map" {
  for_each      = var.bucket_configs
  
  bucket        = "test-${each.key}-bucket-${each.value.environment}"
  force_destroy = each.value.force_destroy
}
```

```hcl
# plan 結果

  # aws_s3_bucket.nest_map["apple"] will be created
  + resource "aws_s3_bucket" "nest_map" {
      + bucket                      = "test-apple-bucket-dev"
      + force_destroy               = true
...

  # aws_s3_bucket.nest_map["banana"] will be created
  + resource "aws_s3_bucket" "nest_map" {
      + bucket                      = "test-banana-bucket-stg"
      + force_destroy               = true
...

  # aws_s3_bucket.nest_map["orange"] will be created
  + resource "aws_s3_bucket" "nest_map" {
      + bucket                      = "test-orange-bucket-prod"
      + force_destroy               = false
...

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

## 条件付き for_each

フィルタを使って条件付きで for_each を使うこともできます。
以下では `deploy` が `true` のリソースのみが作成されます。

```hcl
variable "bucket_configs" {
  type = map(object({
    environment   = string
    force_destroy = bool
    deploy        = bool
  }))
  default = {
    "apple"   = { environment = "dev", force_destroy = true, deploy = true }
    "banana"  = { environment = "stg", force_destroy = true, deploy = true }
    "orange"  = { environment = "prod", force_destroy = false, deploy = false }
  }
}

resource "aws_s3_bucket" "nest_map2" {
  for_each = { for key, bucket in var.bucket_configs : key => bucket if bucket.deploy }
  
  bucket        = "test-${each.key}-bucket-${each.value.environment}"
  force_destroy = each.value.force_destroy
}
```

```hcl
# plan 結果

  # aws_s3_bucket.nest_map2["apple"] will be created
  + resource "aws_s3_bucket" "nest_map2" {
      + bucket                      = "test-apple-bucket-dev"
      + force_destroy               = true
...

  # aws_s3_bucket.nest_map2["banana"] will be created
  + resource "aws_s3_bucket" "nest_map2" {
      + bucket                      = "test-banana-bucket-stg"
      + force_destroy               = true
...

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