LoginSignup
14
5

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

Last updated at Posted at 2023-01-23

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してあげれば別ですが)
そんな時にformapを生成してあげれば…一網打尽ってわけですね(?)
以下はちょっと複雑だけど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_eachmapでないとダメなのは前述した通りですが、maplistにして(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はほかにもフィルターを設定するとかの機能もあるので、活用していきましょう。

参考

14
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
14
5