Terraformの繰り返し処理(for編)

# Terraformの繰り返し処理の種類

Terraformには`for`,`for_each`,`count`の3つの繰り返し処理があります。
今回は`for`。
for式は、別の複合型の値(例えばList)を変換して複合型の値を生成します。
`count`,`for_each`はこちら。

https://qiita.com/hikaru_motomiya/items/c897b6567f387b6b7bfc

# 分類
|分類|　　|説明|
|:---|:---|:---|
|式|for|繰り返し処理を行える。|
|メタ引数|for_each|mapを利用した繰り返し処理。|
||count|単純な指定値による繰り返し処理。もっともイメージがつきやすい。|

# for文の書き方

## 出力形式

`for`では、書き方によって生成されるタイプが`list`,`set`,`tuple`,`map`,`object`に変わります。
例えば以下のようなリストがあったとして、それぞれの出力は以下の通りになります。

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


### パターン1:list,set
```terraform
output "list" {
 value = [for s in var.list : s] 
}
```

```terminal
Changes to Outputs:
  + list       = [
      + "apple",
      + "orange",
      + "banana",
    ]
```

`list`,`set`で出力したい場合は`[]`で囲う必要があります。

もちろん結果から特定の値を参照することもできます。

```terraform 
locals {
  list = [for s in var.list : s] 
}
output "list-locals" {
  value = local.list[0]
}
```

```terminal
Changes to Outputs:
 + list-locals = "apple"
```

### パターン2:tuple,map,object

```terraform
output "map" {
 value = {for s in var.list : s => s}
}
```

``` terminal
Changes to Outputs:
  + map        = {
      + apple  = "apple"
      + banana = "banana"
      + orange = "orange"
    }
```

一方`tuple`,`map`,`object`で出力したい場合は`{}`で囲うのと、`=>`を入れてあげる必要があります。

## 入力形式
`index`を利用したい場合は、以下のように複数シンボルを利用します。

``` terraform
output "list-index" {
 value = [for s,v in var.list : "${s}_${v}"] 
}

output "tuple-index" {
 value = {for s,v in var.list : s => v}
}

```

``` terminal
Changes to Outputs:
+ list-index  = [
      + "0_apple",
      + "1_orange",
      + "2_banana",
    ]
+ tuple-index = {
      + "0" = "apple"
      + "1" = "orange"
      + "2" = "banana"
    }
```



# for文でできること

さて、ここまではfor文の入出力についてお話しました。
実際どう使うの？っていうとこになりますが、__複合型から別の複合型に変える__ ことができると、いろいろなことが実現できます。

## リストを特定の書式に変更する

forを書いてそれを書式変換の関数をかませれば、簡単に変換できちゃいます。

```terraform
# すべて大文字に変換
output "list-upper" {
 value = [for s in var.list : upper(s)] 
}
# すべて小文字に変換(最初から小文字だけど)
output "list-lower" {
 value = [for s in var.list : lower(s)] 
}
# 最初の文字だけ大文字に変換
output "list-title" {
 value = [for s in var.list : title(s)] 
}
```

```terminal
Changes to Outputs:
 + list-lower  = [
      + "apple",
      + "orange",
      + "banana",
    ]
  + list-title  = [
      + "Apple",
      + "Orange",
      + "Banana",
    ]
  + list-upper  = [
      + "APPLE",
      + "ORANGE",
      + "BANANA",
    ]
```

## mapを作る
これは例に書いた通りですが、簡単にlistからmapが生成できちゃいます。
んで、生成した`map`を何に使うかというと…

## for_eachを回す

はい、`for_each`に使います。
for_eachは`map`であることが前提なので、`list`をループで処理することはできません。(`toset`してあげれば別ですが)
そんな時に`for`で`map`を生成してあげれば…一網打尽ってわけですね(？)
以下はちょっと複雑だけどAzureでサブネットを生成する場合に`list`から`map`を生成して`for_each`で回してます。

```terraform
variable "location" {
  type    = string
  default = "japaneast"
}
resource "azurerm_resource_group" "rg" {
  name     = "rg-test"
  location = var.location
}
# vNetを作成する
resource "azurerm_virtual_network" "vnet" {
  name                = "vnet-test-01"
  location            = var.location
  resource_group_name = azurerm_resource_group.rg.name
  address_space       = ["10.0.0.0/16"]
}
variable "subnet_address" {
  type = list(string)
  default = [
    "10.0.0.0/26",
    "10.0.0.64/26",
    "10.0.0.128/26",
    "10.0.0.192/26",
  ]
}
resource "azurerm_subnet" "subnet" {
  for_each             = { for s, v in var.subnet_address : s => v }
  name                 = "subnet-${each.key}"
  virtual_network_name = azurerm_virtual_network.vnet.name
  address_prefixes     = [each.value]
  resource_group_name  = azurerm_resource_group.rg.name
}
```

出力
```terminal
  # azurerm_resource_group.rg will be created
  + resource "azurerm_resource_group" "rg" {
      + id       = (known after apply)
      + location = "japaneast"
      + name     = "rg-test"
    }

  # azurerm_subnet.subnet["0"] will be created
  + resource "azurerm_subnet" "subnet" {
      + address_prefixes                               = [
          + "10.0.0.0/26",
        ]
      + enforce_private_link_endpoint_network_policies = (known after apply)
      + enforce_private_link_service_network_policies  = (known after apply)
      + id                                             = (known after apply)
      + name                                           = "subnet-0"
      + private_endpoint_network_policies_enabled      = (known after apply)
      + private_link_service_network_policies_enabled  = (known after apply)
      + resource_group_name                            = "rg-test"
      + virtual_network_name                           = "vnet-test-01"
    }

  # azurerm_subnet.subnet["1"] will be created
  + resource "azurerm_subnet" "subnet" {
      + address_prefixes                               = [
          + "10.0.0.64/26",
        ]
      + enforce_private_link_endpoint_network_policies = (known after apply)
      + enforce_private_link_service_network_policies  = (known after apply)
      + id                                             = (known after apply)
      + name                                           = "subnet-1"
      + private_endpoint_network_policies_enabled      = (known after apply)
      + private_link_service_network_policies_enabled  = (known after apply)
      + resource_group_name                            = "rg-test"
      + virtual_network_name                           = "vnet-test-01"
    }

  # azurerm_subnet.subnet["2"] will be created
  + resource "azurerm_subnet" "subnet" {
      + address_prefixes                               = [
          + "10.0.0.128/26",
        ]
      + enforce_private_link_endpoint_network_policies = (known after apply)
      + enforce_private_link_service_network_policies  = (known after apply)
      + id                                             = (known after apply)
      + name                                           = "subnet-2"
      + private_endpoint_network_policies_enabled      = (known after apply)
      + private_link_service_network_policies_enabled  = (known after apply)
      + resource_group_name                            = "rg-test"
      + virtual_network_name                           = "vnet-test-01"
    }

  # azurerm_subnet.subnet["3"] will be created
  + resource "azurerm_subnet" "subnet" {
      + address_prefixes                               = [
          + "10.0.0.192/26",
        ]
      + enforce_private_link_endpoint_network_policies = (known after apply)
      + enforce_private_link_service_network_policies  = (known after apply)
      + id                                             = (known after apply)
      + name                                           = "subnet-3"
      + private_endpoint_network_policies_enabled      = (known after apply)
      + private_link_service_network_policies_enabled  = (known after apply)
      + resource_group_name                            = "rg-test"
      + virtual_network_name                           = "vnet-test-01"
    }

  # azurerm_virtual_network.vnet will be created
  + resource "azurerm_virtual_network" "vnet" {
      + address_space       = [
          + "10.0.0.0/16",
        ]
      + dns_servers         = (known after apply)
      + guid                = (known after apply)
      + id                  = (known after apply)
      + location            = "japaneast"
      + name                = "vnet-test-01"
      + resource_group_name = "rg-test"
      + subnet              = (known after apply)
    }

Plan: 6 to add, 0 to change, 0 to destroy.
```
まぁでもこれははっきり言って`count`でやっていることと同じなので、あまりよいとは言えないですね。最初から`count`で実装したほうが楽にできます。

## mapのlistをfor_eachで回す

__これが一番利用するかもしれません。__
`for_each`は`map`でないとダメなのは前述した通りですが、`map`を`list`にして(`tuple`にして)ループさせたいことってありますよね？
そういう場合に利用します。以下は同じようにサブネットを作る例。

```terraform
variable "location" {
  type    = string
  default = "japaneast"
}
resource "azurerm_resource_group" "rg" {
  name     = "rg-test"
  location = var.location
}
# vNetを作成する
resource "azurerm_virtual_network" "vnet" {
  name                = "vnet-test-01"
  location            = var.location
  resource_group_name = azurerm_resource_group.rg.name
  address_space       = ["10.0.0.0/16"]
}
# 各サブネットに対するパラメータのmapを作成する
locals  {
  subnet = [{
    name             = "subnet-0",
    address_prefixes = "10.0.0.0/26"
    }, {
    name             = "subnet-1",
    address_prefixes = "10.0.0.64/26"
    }, {
    name             = "subnet-2",
    address_prefixes = "10.0.0.128/26"
    }, {
    name             = "subnet-3",
    address_prefixes = "10.0.0.192/26"
  }]
}
resource "azurerm_subnet" "subnet" {
# 作成したmapを呼び出して処理
  for_each             = { for s in local.subnet : s.name => s }
  name                 = each.value.name
  virtual_network_name = azurerm_virtual_network.vnet.name
  address_prefixes     = [each.value.address_prefixes]
  resource_group_name  = azurerm_resource_group.rg.name
}
```
出力
```terminal
  # azurerm_resource_group.rg will be created
  + resource "azurerm_resource_group" "rg" {
      + id       = (known after apply)
      + location = "japaneast"
      + name     = "rg-test"
    }

  # azurerm_subnet.subnet["subnet-0"] will be created
  + resource "azurerm_subnet" "subnet" {
      + address_prefixes                               = [
          + "10.0.0.0/26",
        ]
      + enforce_private_link_endpoint_network_policies = (known after apply)
      + enforce_private_link_service_network_policies  = (known after apply)
      + id                                             = (known after apply)
      + name                                           = "subnet-0"
      + private_endpoint_network_policies_enabled      = (known after apply)
      + private_link_service_network_policies_enabled  = (known after apply)
      + resource_group_name                            = "rg-test"
      + virtual_network_name                           = "vnet-test-01"
    }

  # azurerm_subnet.subnet["subnet-1"] will be created
  + resource "azurerm_subnet" "subnet" {
      + address_prefixes                               = [
          + "10.0.0.64/26",
        ]
      + enforce_private_link_endpoint_network_policies = (known after apply)
      + enforce_private_link_service_network_policies  = (known after apply)
      + id                                             = (known after apply)
      + name                                           = "subnet-1"
      + private_endpoint_network_policies_enabled      = (known after apply)
      + private_link_service_network_policies_enabled  = (known after apply)
      + resource_group_name                            = "rg-test"
      + virtual_network_name                           = "vnet-test-01"
    }

  # azurerm_subnet.subnet["subnet-2"] will be created
  + resource "azurerm_subnet" "subnet" {
      + address_prefixes                               = [
          + "10.0.0.128/26",
        ]
      + enforce_private_link_endpoint_network_policies = (known after apply)
      + enforce_private_link_service_network_policies  = (known after apply)
      + id                                             = (known after apply)
      + name                                           = "subnet-2"
      + private_endpoint_network_policies_enabled      = (known after apply)
      + private_link_service_network_policies_enabled  = (known after apply)
      + resource_group_name                            = "rg-test"
      + virtual_network_name                           = "vnet-test-01"
    }

  # azurerm_subnet.subnet["subnet-3"] will be created
  + resource "azurerm_subnet" "subnet" {
      + address_prefixes                               = [
          + "10.0.0.192/26",
        ]
      + enforce_private_link_endpoint_network_policies = (known after apply)
      + enforce_private_link_service_network_policies  = (known after apply)
      + id                                             = (known after apply)
      + name                                           = "subnet-3"
      + private_endpoint_network_policies_enabled      = (known after apply)
      + private_link_service_network_policies_enabled  = (known after apply)
      + resource_group_name                            = "rg-test"
      + virtual_network_name                           = "vnet-test-01"
    }

  # azurerm_virtual_network.vnet will be created
  + resource "azurerm_virtual_network" "vnet" {
      + address_space       = [
          + "10.0.0.0/16",
        ]
      + dns_servers         = (known after apply)
      + guid                = (known after apply)
      + id                  = (known after apply)
      + location            = "japaneast"
      + name                = "vnet-test-01"
      + resource_group_name = "rg-test"
      + subnet              = (known after apply)
    }

Plan: 6 to add, 0 to change, 0 to destroy.
```
今回はサブネットに利用するパラメータが少ないのであれですが、これがもっとパラメーターのあるリソースの場合で想像してください。すごく捗ることがイメージできると思います。

ちょっとだけ解説すると、
`{ for s in local.subnet : s.name => s }`は`subnet-1 = {name = "subnet-0",address_prefixes = "10.0.0.0/26"}`…というMapを生成しています。これがループで繰り返され、subnet-1,subnet-2…と処理されるわけですね。

はい、これでメジャーな使い方は以上です！
`for`はほかにもフィルターを設定するとかの機能もあるので、活用していきましょう。

# 参考

https://developer.hashicorp.com/terraform/language/expressions/for