Reputation: 329
I am trying to understand the "for" expressions in TF when they use the pair of temporary symbols like k,v. My goal here is to understand the construct so that I can put this knowledge into a PowerPoint file and explain it to others. I'll do my best to explain what I think is happening in plain English, but would like your input to see if my understanding is correct or not. I'm learning TF, so please bear with me here.
Below is the code - inherited from someone else.
Variable Declaration
variable "private_dns_zones" {
type = map(object({
dns_zone_name = string
resource_group_name = string
tags = map(string)
vnet_links = list(object({
zone_to_vnet_link_name = string
vnet_name = string
networking_resource_group = string
zone_to_vnet_link_exists = bool
vnet_link_rg_name = string
}))
zone_exists = bool
registration_enabled = bool
}))
description = "Map containing Private DNS Zone Objects"
default = {}
}
MAIN - Cut off to limit the scope of this
locals {
rgs_map = {
for i, n in var.private_dns_zones : "${i}" => {
name = n.resource_group_name
}
}
private_dns_zones = {
for dz_k, dz_v in var.private_dns_zones :
dz_k => dz_v if(coalesce(dz_v.zone_exists, false) == false && dz_v.dns_zone_name != null)
}
zone_to_vnet_links_distinct = distinct(flatten([
for dz_k, dz_v in var.private_dns_zones : [
for vnet in coalesce(dz_v.vnet_links, []) : {
dns_zone_key = dz_k #This would be 'zone1'?
dns_zone_name = dz_v.dns_zone_name
zone_to_vnet_link_name = vnet.zone_to_vnet_link_name
vnet_name = vnet.vnet_name
networking_resource_group = vnet.networking_resource_group
vnet_link_rg_name = vnet.vnet_link_rg_name
registration_enabled = dz_v.registration_enabled
} if(coalesce(vnet.zone_to_vnet_link_exists, false) == false && vnet.vnet_name != null)
] if(dz_v.zone_exists == false && dz_v.dns_zone_name != null)
]))
zone_to_vnet_links = {
for vnet_link in local.zone_to_vnet_links_distinct :
"${vnet_link.dns_zone_key}_${vnet_link.zone_to_vnet_link_name}" => vnet_link
}
}
TFVARS
private_dns_zones = {
zone1 = {
dns_zone_name = "privatelink.vaultcore.azure.net"
resource_group_name = "Terraform1"
tags = {
iac = "Terraform"
syntax = "zone1"
}
zone_exists = false
vnet_links = [
{
zone_to_vnet_link_name = "vaultcore-vnet-eastus2-01"
vnet_name = "vnet-eastus2-01"
networking_resource_group = "Terraform1"
zone_to_vnet_link_exists = false
vnet_link_rg_name = "Terraform1"
}
]
registration_enabled = false
}
}
"a 'for' expression can optionally declare a pair of temporary symbols in order to use the key or index of each item too"
So, for the locals rgs_map seen above:
for i, n in var.private_dns_zones : "${i} => {name = n.resource_group_name}
My interpretation: i refers to the key or attribute name. So, this means "zone1" as seen in the tfvars file?
for zone1, loop through the object and create a new object (as depicted by the {} brackets) that uses 'zone1' as its index, and create one argument per index that has 'name' with the value set to the value of the resource_group_name in the tfvars file? Is this explanation correct?
Representation of this: IS THIS ACCURATE??
rgs_map = {
zone1 = {
name = value of resource_group_name in tfvars file
}
}
Next One locals private_dns_zones for dz_k, dz_v : dz_k refers to 'zone1' again?
Interpretation: zone1 => dz_v (does dz_v refer to the whole object? Not clear on this.) create a new local object if zone_exists is false and the dns_zone_name is not null??
Representation of this: IS THIS ACCURATE??
private_dns_zones = {
zone1 = {
name = dns_zone_name
resource_group_name = resource_group_name
etc.
}
}
Thanks. Just want some confirmation that my understanding of these is correct.
Upvotes: 0
Views: 1981
Reputation: 1
not too sure if this is possible but in the scenario above
[for item in ["abc", "bcd", "cde"] : upper(s)]
The value of item in this case will be "abc", "bcd" and "cde".
Having and index and an iterator while iterating through a list:
[for index, item in ["abc", "bcd", "cde"] : upper(s)]
The value of item will be "abc", "bcd" and "cde", meanwhile the value of index will be 0, 1 and 2.
I'm trying to get this to work without having an index number associated to my module
Upvotes: 0
Reputation: 16775
First things first:
TFVARS
or .tfvars
files are optional. The reason why we have them is to store the input. If we remove them, Terraform will ask us for the input when you do the plan
.
variables
are data structures which accept and store input provided by us using .tfvar
files or typing the input in from the console. In your case the structure of private_dns_zones
from the TFVARS
files provides input to the variable "private_dns_zones"
.
for
expressions:
They behave differently based on what they iterate on. For example: Having on variable as the iterator while iterating through a list:
[for item in ["abc", "bcd", "cde"] : upper(s)]
The value of item
in this case will be "abc"
, "bcd"
and "cde"`.
Having and index and an iterator while iterating through a list:
[for index, item in ["abc", "bcd", "cde"] : upper(s)]
The value of item
will be "abc"
, "bcd"
and "cde", meanwhile the value of index
will be 0
, 1
and 2
.
Iterating through a map
:
{ for key, value in var.private_dns_zones : "${key}" => {name = n.resource_group_name} }
In case of map
and objects
we don't have indexes. We have keys and values. The value key
will be zone1
and the value of value
will be the whole object of:
{
dns_zone_name = "privatelink.vaultcore.azure.net"
resource_group_name = "Terraform1"
tags = {
iac = "Terraform"
syntax = "zone1"
}
zone_exists = false
vnet_links = [
{
zone_to_vnet_link_name = "vaultcore-vnet-eastus2-01"
vnet_name = "vnet-eastus2-01"
networking_resource_group = "Terraform1"
zone_to_vnet_link_exists = false
vnet_link_rg_name = "Terraform1"
}
]
registration_enabled = false
}
for zone1, loop through the object and create a new object (as depicted by the {} brackets) that uses 'zone1' as its index, and create one argument per index that has 'name' with the value set to the value of the resource_group_name in the tfvars file? Is this explanation correct?
No, not entirely. The for
expression will result in an object, but objects don't have indexes, they have keys and values. More correctly would be, this for expression creates an object of which keys are they keys from the var.private_dns_zones
and the values are the resource_group_names
to which each key (zone1
) maps to.
So:
rgs_map = {
for i, n in var.private_dns_zones : "${i}" => {
name = n.resource_group_name
}
}
Evaluates to:
rgs_map = {
"zone1" = {
"name" = "Terraform1"
}
}
In case of the second for
expression, the idea is the same.
private_dns_zones = {
for dz_k, dz_v in var.private_dns_zones :
dz_k => dz_v if(coalesce(dz_v.zone_exists, false) == false && dz_v.dns_zone_name != null)
}
This uses the coalesce
function for additional logic on what to include in the object, but other than this is the idea is similar. The output of this is:
private_dns_zones = {
"zone1" = {
"dns_zone_name" = "privatelink.vaultcore.azure.net"
"registration_enabled" = false
"resource_group_name" = "Terraform1"
"tags" = {
"iac" = "Terraform"
"syntax" = "zone1"
}
"vnet_links" = [
{
"networking_resource_group" = "Terraform1"
"vnet_link_rg_name" = "Terraform1"
"vnet_name" = "vnet-eastus2-01"
"zone_to_vnet_link_exists" = false
"zone_to_vnet_link_name" = "vaultcore-vnet-eastus2-01"
},
]
"zone_exists" = false
}
}
nterpretation: zone1 => dz_v (does dz_v refer to the whole object? Not clear on this.) create a new local object if zone_exists is false and the dns_zone_name is not null??
This is somewhat correct. The object will be created anyway, but it will contains data only about zones for which zone_exists
is false and dns_zone_name
is not null. Keep in mind, you could have multiple zones. If no zone has false for zone_exists
and not null for dns_zone_name
, than the private_dns_zones
will be an empty object: private_dns_zones = {}
.
Some suggestions for debugging Terraform code:
You can use output
in order to see to what does an expression evaluate to:
output "private_dns_zones" {
value = local.private_dns_zones
}
After an apply
the value of private_dns_zones
will be displayed in your terminal.
You can use the terraform console
command to start a new REPL and evaluate terraform commands on the fly.
Upvotes: 2