I need to deploy a list of GCP compute instances. How do I loop for_each through the "vms" in a list of objects like this:
"gcp_zone": "us-central1-a",
"image_name": "centos-cloud/centos-7",
"vms": [
{
"hostname": "test1-srfe",
"cpu": 1,
"ram": 4,
"hdd": 15,
"log_drive": 300,
"template": "Template-New",
"service_types": [
"sql",
"db01",
"db02"
]
},
{
"hostname": "test1-second",
"cpu": 1,
"ram": 4,
"hdd": 15,
"template": "APPs-Template",
"service_types": [
"configs"
]
}
]
}
ベストアンサー1
私は Terraform でイテレータを頻繁に使用していますが、いつも頭を悩ませていました。そこで、最も一般的なイテレータ パターンを 5 つ特定しました (コード例は以下にあります)。これにより、多くの優れたモジュールを構築できました (ソース)。
- 文字列のリストに for_each を使用する
- オブジェクトのリストで for_each を使用する
- for_each を使用して 2 つのリストを結合する
- ネストされたブロック内での for_each の使用
- for_eachを条件文として使用する
と文字列のリストを使用するfor_each
のが最も理解しやすいですが、toset()
関数を常に使用できます。オブジェクトのリストで作業する場合は、map
キーが一意の値である に変換する必要があります。別の方法としては、Terraform 構成内にマップを配置します。個人的には、構成にマップではなくオブジェクトのリストを配置する方がすっきりしていると思います。キーには通常、マップ内の一意のアイテムを識別する以外の目的はないため、マップは動的に構築できます。また、特により複雑なモジュールを構築する場合は、イテレータを使用して、リソースまたはリソース ブロックを条件付きでデプロイします。
1. 文字列のリストに for_each を使用する
locals {
ip_addresses = ["10.0.0.1", "10.0.0.2"]
}
resource "example" "example" {
for_each = toset(local.ip_addresses)
ip_address = each.key
}
2. オブジェクトのリストに対して for_each を使用する
locals {
virtual_machines = [
{
ip_address = "10.0.0.1"
name = "vm-1"
},
{
ip_address = "10.0.0.1"
name = "vm-2"
}
]
}
resource "example" "example" {
for_each = {
for index, vm in local.virtual_machines:
vm.name => vm # Perfect, since VM names also need to be unique
# OR: index => vm (unique but not perfect, since index will change frequently)
# OR: uuid() => vm (do NOT do this! gets recreated everytime)
}
name = each.value.name
ip_address = each.value.ip_address
}
3. for_eachを使用してデカルト積2つのリスト
locals {
domains = [
"https://example.com",
"https://stackoverflow.com"
]
paths = [
"/one",
"/two",
"/three"
]
}
resource "example" "example" {
# Loop over both lists and flatten the result
urls = flatten([
for domain in local.domains : [
for path in local.paths : {
domain = domain
path = path
}
]
]))
}
4. ネストされたブロックで for_each を使用する
# Using the optional() keyword makes fields null if not present
variable "routes" {
type = list(
name = string
path = string
config = optional(object({
cache_enabled = bool
https_only = bool
}))
default = []
}
resource "example" "example" {
name = ...
dynamic "route" {
for_each = {
for route in var.routes :
route.name => route
}
content {
# Note: <top_level_block>.value.<object_key>
name = route.value.name
}
dynamic "configuration" {
# Note: <top_level_block>.value.<optional_object_key>
for_each = route.value.config != null ? [1] : []
content {
cache_enabled = route.value.config.cache_enabled
https_only = route.value.config.https_only
}
}
}
5. for_eachを条件文として使用する(特にダイナミックブロック)
variable "deploy_example" {
type = bool
description = "Indicates whether to deploy something."
default = true
}
# Using count and a conditional, for_each is also possible here.
# See the next solution using a for_each with a conditional.
resource "example" "example" {
count = var.deploy_example ? 0 : 1
name = ...
ip_address = ...
}
variable "enable_logs" {
type = bool
description = "Indicates whether to enable something."
default = false
}
resource "example" "example" {
name = ...
ip_address = ...
# Note: dynamic blocks cannot use count!
# Using for_each with an empty list and list(1) as a readable alternative.
dynamic "logs" {
for_each = var.enable_logs ? [] : [1]
content {
name = "logging"
}
}
}