Terraform is a great tool for writing Infrastructure as Code. However, there are times that it can be frustrating, especially when using loops to generate multiple resources, and then generating something like an output resource. Add in maps and custom objects and you have the makings of this:

In this article I am going to take you through the process of provisioning multiple resources using a complex map object and then generating an output that loops through all items and generates a custom map.
As always, this is just one way of doing things and I encourage you to do what works for you.
Scenario: Provision an Azure Load Balancer with multiple rules.
In this scenario we will provision an Azure Load Balancer as a resource. We will then provision 2 rules for the Load Balancer.
Step 1. Directory structure
Create the following file structure:
azure_lb
├── input.tfvars
├── load-balancer.tf
├── meta.tf
├── outputs.tf
└── variables.tf
Step 2. The meta.tf file
This file is simply metadata that I like to configure.
terraform {
required_version = ">=0.12.29"
}
provider "azurerm" {
features {}
version = "=2.20.0"
}
data "azurerm_subnet" "this" {
name = var.subnet_name
resource_group_name = var.resource_group_name
virtual_network_name = var.vnet_name
}
The data block is used to get the Subnet ID which will be used later in the code.
Step 3. The variables.tf file
Add the following variables:
variable "resource_group_name" {
description = "The name of the resource group."
type = string
}
variable "subnet_name" {
description = "The subnet name."
type = string
}
variable "vnet_name" {
description = "The Vnet name."
type = string
}
variable "lb_rules" {
description = "List of Load Balancer rules."
type = map(object({
name = string
frontend_port = number
backend_port = number
protocol = string
}))
}
Let’s look at the lb_rules variable. So this is a map of objects. Here is an example on how we can create multiple lb_rules objects:
lb_rules = {
"rule1" = {
name = "rule_1"
frontend_port = 80
backend_port = 80
protocol = "Tcp"
},
"rule2" = {
name = "rule_2"
frontend_port = 443
backend_port = 443
protocol = "Tcp"
}
}
In the above example, rule_1 and rule_2 are two list items of type map.
Step 4. The load-balancer.tf file
Add the following code to this file:
resource "azurerm_lb" "this" {
location = "Australia East"
name = "lb-test"
resource_group_name = var.resource_group_name
frontend_ip_configuration {
name = "lb-test-ip-config"
subnet_id = data.azurerm_subnet.this.id
private_ip_address_allocation = "Dynamic"
}
}
resource "azurerm_lb_rule" "this" {
for_each = var.lb_rules
backend_port = each.value.backend_port
frontend_port = each.value.frontend_port
name = each.value.name
protocol = each.value.protocol
loadbalancer_id = azurerm_lb.this.id
resource_group_name = var.resource_group_name
frontend_ip_configuration_name = azurerm_lb.this.frontend_ip_configuration[0].name
}
The azurerm_lb block is fairly straightforward. It creates the Load Balancer resource, gives it a Front-end IP Configuration and provisions it in the variable defined Resource Group.
The azurerm_lb_rule block is where we are going to use the for_each statement to create the Load Balancer rules.
The for_each loop will iterate over “rule_1” and “rule_2” items. We then reference the map values using each.value.<key>.
Step 5. The outputs.tf file
I want to show how we can only output specific items from a block. Copy the following bit of code into the file:
output "lb_rules_map" {
value = [for lb in azurerm_lb_rule.this : map("rule_name", lb.name, "rule_id", lb.id)]
}
output "lb_rules_all" {
value = azurerm_lb_rule.this
}
The lb_rules_all output will display all information for each Load Balancer rule, which may not be always ideal.
In order to obtain specific information, we can use a for loop to iterate over the multiple azurerm_lb_rule resources and create a custom map as your output values. In this case we have created the following lb_rules_map output:
lb_rules_map = [
{
"rule_id" = "/subscriptions/blah-blah-blah/resourceGroups/azure-test/providers/Microsoft.Network/loadBalancers/lb-test/loadBalancingRules/rule_1"
"rule_name" = "rule_1"
},
{
"rule_id" = "/subscriptions/blah-blah-blah/resourceGroups/azure-test/providers/Microsoft.Network/loadBalancers/lb-test/loadBalancingRules/rule_2"
"rule_name" = "rule_2"
},
]
Step 6. The input.tfvars file
Update the parameters as per your requirements. I am assuming that Resource Group, Subnet and Vnets have already been provisioned.
resource_group_name = "azure-test"
subnet_name = "subnet-1"
vnet_name = "azure-test-vnet"
lb_rules = {
"rule1" = {
name = "rule_1"
frontend_port = 80
backend_port = 80
protocol = "Tcp"
},
"rule2" = {
name = "rule_2"
frontend_port = 443
backend_port = 443
protocol = "Tcp"
}
}
And as always, happy Terraforming.