Looking for someone to assist in helping me understand the use of For in Terraform. I don’t come from a programming background and so far the only thing I’m really struggling with in Terraform is managing the inputs/outputs of various resources. I remember enough of my programming classwork to know what all the basic data types are, string, boolean, etc. Where I start to get confused is when we begin to combine those into various constructs like maps, objects, maps of lists, maps of objects, etc. Most of the Terraform documentation seems to be written at a level that would be easy to grasp if you came from another language or programming background but… that isn’t me.
Here’s the documentation for For and creating an object…
[for k, v in var.map : length(k) + length(v)]
For a map or object type, like above, the k
symbol refers to the key or attribute name of the current element.
Ok, what is v? Assuming it means value, what if I just want to return k,v or k and not apply length to them? What if I want multiple attributes? Is For even what I’m looking for here as it appears to be applying grouping/filtering/conditional logic to results vs just returning data? I think I know how to do what I’m trying to do using outputs, but we can’t pass those to a child module from a parent module…
Use Case: I’ve got a project where I have created a map of objects using tfvars, passed that down to a networking module, and use it to create 8 subnets. All that works great.
Networking Module
variables.tf
variable "subnets" {
type = map(object({
name = string
address_prefix = list(string)
service_delegations = bool
virtual_network_name = string
resource_group_name = string
}))
}
main.tf
resource "azurerm_subnet" "subnets" {
for_each = var.subnets
name = each.key
address_prefixes = each.value.address_prefix
virtual_network_name = each.value.virtual_network_name
resource_group_name = each.value.resource_group_name
}
I then pass that same variable to a child module of the networking module to create a set of route tables, one for each subnet. This also works great (although it took awhile for me to get the nested for_each syntax right). Note that I used the subnet names and a prefix to create the route table names…
Firewall Child Module
main.tf
resource "azurerm_route_table" "default_routes" {
for_each = var.subnets
name = "rt-${each.value.name}"
location = var.resourcegroup_location
resource_group_name = each.value.resource_group_name
dynamic "route" {
for_each = each.value.address_prefix
content {
name = each.value.name
address_prefix = route.value
next_hop_type = "VnetLocal"
}
}
route {
name = "Default"
address_prefix = "0.0.0.0/0"
next_hop_type = "VirtualAppliance"
next_hop_in_ip_address = var.lb_internal_ipaddress
}
}
So now I need to do my associations, I need the subnet ID and the route table ID. Oops, the subnets object map obviously doesn’t have that as Azure creates it. Where I’ve needed the subnet ID before in the project I’ve used an output but that won’t work here, as the module is a child module of the module creating the subnets. So I need to construct an object (I assume, because creating a map of strings doesn’t work because a string is a scary primitive and can’t be used as an attribute reference). I need the subnet ID and the corresponding subnet name. Then I need to match that subnet ID to the route table ID using rt-subnet name. Having some difficulty with that syntax as well, I can’t seem to use a string to preface a lookup value i.e. azurerm_route_table.default_routes["rt-"each.value.name].id
Association code
resource "azurerm_subnet_route_table_association" "subnet_associations" {
for_each = var.subnet_associations
subnet_id = each.value.id
route_table_id = azurerm_route_table.default_routes[each.value.name].id
}
Child module call
module "firewall" {
source = "./modules/Fortigate/Active-Active-ELB-ILB"
subnets = var.subnets
subnet_associations = {for subnet in azurerm_subnet.subnets: subnet.name => subnet.id}
Any assistance is greatly appreciated.