Vegas588
Vegas588

Reputation: 329

Terraform - For expression Understanding

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

Answers (2)

user210382
user210382

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

Ervin Szilagyi
Ervin Szilagyi

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

Related Questions