Terraformの繰り返し処理の種類
Terraformにはfor,for_each,countの3つの繰り返し処理があります。
今回はfor。
for式は、別の複合型の値(例えばList)を変換して複合型の値を生成します。
count,for_eachはこちら。
分類
| 分類 | 説明 | |
|---|---|---|
| 式 | for | 繰り返し処理を行える。 | 
| メタ引数 | for_each | mapを利用した繰り返し処理。 | 
| count | 単純な指定値による繰り返し処理。もっともイメージがつきやすい。 | 
for文の書き方
出力形式
forでは、書き方によって生成されるタイプがlist,set,tuple,map,objectに変わります。
例えば以下のようなリストがあったとして、それぞれの出力は以下の通りになります。
variable "list" {
  type = list(string)
  default = [
    "apple",
    "orange",
    "banana"
  ]
}
パターン1:list,set
output "list" {
 value = [for s in var.list : s] 
}
Changes to Outputs:
  + list       = [
      + "apple",
      + "orange",
      + "banana",
    ]
list,setで出力したい場合は[]で囲う必要があります。
もちろん結果から特定の値を参照することもできます。
locals {
  list = [for s in var.list : s] 
}
output "list-locals" {
  value = local.list[0]
}
Changes to Outputs:
 + list-locals = "apple"
パターン2:tuple,map,object
output "map" {
 value = {for s in var.list : s => s}
}
Changes to Outputs:
  + map        = {
      + apple  = "apple"
      + banana = "banana"
      + orange = "orange"
    }
一方tuple,map,objectで出力したい場合は{}で囲うのと、=>を入れてあげる必要があります。
入力形式
indexを利用したい場合は、以下のように複数シンボルを利用します。
output "list-index" {
 value = [for s,v in var.list : "${s}_${v}"] 
}
output "tuple-index" {
 value = {for s,v in var.list : s => v}
}
Changes to Outputs:
+ list-index  = [
      + "0_apple",
      + "1_orange",
      + "2_banana",
    ]
+ tuple-index = {
      + "0" = "apple"
      + "1" = "orange"
      + "2" = "banana"
    }
for文でできること
さて、ここまではfor文の入出力についてお話しました。
実際どう使うの?っていうとこになりますが、複合型から別の複合型に変える ことができると、いろいろなことが実現できます。
リストを特定の書式に変更する
forを書いてそれを書式変換の関数をかませれば、簡単に変換できちゃいます。
# すべて大文字に変換
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)] 
}
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で回してます。
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
}
出力
  # 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にして)ループさせたいことってありますよね?
そういう場合に利用します。以下は同じようにサブネットを作る例。
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
}
出力
  # 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はほかにもフィルターを設定するとかの機能もあるので、活用していきましょう。
参考

