The try function combined with for_each in Terraform offers a great approach to handling multiple variations in data structures within Terraform. In this blog post, we will look at using both these features to develop more resilient and adaptable Terraform configurations and will also include an example of this usage
Quick overview of try and for_each
tryFunction: Allows you to attempt to evaluate a list of expressions and returns the first expression that does not produce an error. It’s particularly useful in handling optional attributes that may or may not be present in your data structures – read further herefor_eachArgument: Used to iterate over usually a map or set of strings, for example: creating one instance of a Terraformresourcefor each time – read further here
Combining both.. what are the benefits?
- Increasing configuration flexibility – Certainly a huge benefit! Combining both will make your terraform configurations more dynamic and even more adaptable to various scenarios that you will use Terraform for. As I mentioned initially – even more multiple variations within your data structures
- Simplification of terraform code – Your Terraform configurations will be more adaptable and somewhat dynamic to multiple scenarios without requiring additional changes to your terraform code
- Better error handling – Preventing your Terraform from failing due to missing optional attributes or variations in data structures
An example of using both
Consider the scenario where you want to deploy a Network Security Group (NSG) ruleset in Terraform, these rules will certainly vary from specific IP address, multiple IP destinations, ports or port ranges – so many variations are possible! Ofcourse in each rule you cannot define all as your Terraform configuration will fail – a number of these arguments are optional and this is where using the try function within for_each in Terraform becomes so useful!
azurerm_network_security_rule can be found here that shows the number of optional values available
Terraform locals
The locals block is where you define the network security rules. Each rule is a map with key-value pairs representing the rule’s properties. Mandatory arguments include name, priority, direction, access, protocol and the rest are optional properties such as (only a few of the available options, check the linked Terraform doc for more properties) :
source_port_ranges– (Optional) List of source ports or port ranges. This is required ifsource_port_rangeis not specified.destination_port_range– (Optional) Destination Port or Range. Integer or range between0and65535or*to match any. This is required ifdestination_port_rangesis not specified.– (Optional) List of destination ports or port ranges. This is required if
destination_port_rangesdestination_port_rangeis not specified.source_address_prefix– (Optional) CIDR or source IP range or * to match any IP. Tags such asVirtualNetwork,AzureLoadBalancerandInternetcan also be used. This is required ifsource_address_prefixesis not specified.source_address_prefixes– (Optional) List of source address prefixes. Tags may not be used. This is required ifsource_address_prefixis not specified.
locals {
nsgrules = {
rdp = {
name = "rdp"
priority = 100
direction = "Inbound"
access = "Allow"
protocol = "Tcp"
source_port_range = "*"
destination_port_range = "3389"
source_address_prefix = "VirtualNetwork"
destination_address_prefix = "*"
},
AD = {
name = "AD"
priority = 110
direction = "Inbound"
access = "Allow"
protocol = "Tcp"
source_port_range = "*"
destination_port_ranges = [53,389,636]
source_address_prefixes = ["10.100.52.39/32", "10.100.52.49/32"]
destination_address_prefixes = ["10.100.52.10/32", "10.100.62.10/32"]
}
}
}
Create Network Security Group (NSG) and Resource Group
Prior to deploying any NSG rules, we need a NSG and Azure resource group to be created:
resource "azurerm_resource_group" "tamopsrg" {
name = "tamopsrg"
location = "uksouth"
}
resource "azurerm_network_security_group" "tamopsnsg" {
name = "tamopsnsg"
location = azurerm_resource_group.example.location
resource_group_name = azurerm_resource_group.example.name
}
NSG rule creation using Terraform
The azurerm_network_security_rule resource is where the for_each loop and try function are used. The for_each loop iterates over each entry in the locals.nsgrules map, creating a security rule for each
The try function is used for optional attributes like source_address_prefix(es) and destination_address_prefix(es). If these attributes are not present in the rule definition, try returns null, effectively omitting them from the resource definition.
resource "azurerm_network_security_rule" "testrules" {
for_each = local.nsgrules
name = each.value.name
priority = each.value.priority
direction = each.value.direction
access = each.value.access
protocol = each.value.protocol
source_port_range = each.value.source_port_range
destination_port_range = each.value.destination_port_range
source_address_prefixes = try(each.value.source_address_prefixes, null)
source_address_prefix = try(each.value.source_address_prefix, null)
destination_address_prefixes = try(each.value.destination_address_prefixes, null)
destination_address_prefix = try(each.value.destination_address_prefix, null)
resource_group_name = azurerm_resource_group.tamopsrg.name
network_security_group_name = azurerm_network_security_group.tamopsnsg.name
}
Please note: Azure NSG rules are processed in order of priority. Ensure that your rules have unique priorities and are ordered
Some additional thoughts to finish
- Use
trySparingly: Whiletryis powerful, overuse can mask errors that should be addressed directly. Use it when you genuinely expect and can safely handle missing data or errors - Clear Fallback Values: Ensure that the fallback values provided to
trydo not inadvertently introduce incorrect configurations. In many cases,nullis appropriate, but this may vary depending on the context - Document Your Use of
try: When usingtry, especially in complex configurations, document its purpose and the scenarios you’re addressing. This will help maintain clarity for yourself and others.
The combination of both try and each is truly awesome! 🙂 and offers a great solution to deal with variability and potential errors in your Terraform configurations.