Conditions in Terraform are well-known and can provide in combination with the for_each argument a lot of flexibility. In today’s blog post I walk you through an example storage module I have created to showcase the topic.
The module consists of three resources a resource group, a lock, and a storage account. As I am using conditions with for_each for the resource group and the lock I can decide whether my storage account gets created in a new resource group and the resource group is delete protected by the lock or not.
resource "azurerm_resource_group" "rg" { for_each = var.resource_group == true ? toset([var.resource_group_name]) : toset([]) name = var.resource_group_name location = var.location } resource "azurerm_management_lock" "lock" { for_each = var.lock == true && var.resource_group == true ? toset([var.resource_group_name]) : toset([]) name = "rg-level" scope = azurerm_resource_group.rg[var.resource_group_name].id lock_level = "CanNotDelete" }
Depending on my settings the for_each argument receives a set with one element, the resource group name, or an empty set. Some can argue for that use case you have the count argument which is valid and correct. But the count argument is index-based where for_each is key-based. The key-based approach always provides you with the same resource address in the Terraform state where the address with the index-based approach can change if you delete the resource. This leads to unpredictable behavior.
... module "storage1" { source = "./modules/storage" resource_group_name = "rg12345" location = "northeurope" storage_account_name = "azcdmdn12345" lock = true } module "storage2" { source = "./modules/storage" resource_group = false resource_group_name = module.storage1.resource_group_name location = "westeurope" storage_account_name = "azcdmdn67890" identity = true } ...
In the code sample above I create the first storage account within a new resource group which is protected by a lock. The second storage account gets provisioned to the same resource group as the first one. Hence, the variable resource_group is set to false.
Furthermore, the second storage account needs a managed identity and here we see the great combination of conditions with the for_each argument.
So, let us have a look at the storage account resource.
resource "azurerm_storage_account" "storage" { name = var.storage_account_name resource_group_name = var.resource_group == true ? azurerm_resource_group.rg[var.resource_group_name].name : var.resource_group_name location = var.resource_group == true ? azurerm_resource_group.rg[var.resource_group_name].location : var.location account_tier = "Standard" account_replication_type = "LRS" dynamic "identity" { for_each = var.identity == true ? toset([var.storage_account_name]) : toset([]) content { type = "SystemAssigned" } } }
When you want a storage account with a managed identity then you use the identity block within the resource. The only way of making a block within a resource configurable via a condition is the use of the dynamic block which requires the for_each argument.
... dynamic "identity" { for_each = var.identity == true ? toset([var.storage_account_name]) : toset([]) content { type = "SystemAssigned" } } ...
This is the true power of conditions with for_each. Making blocks in a resource configurable without the need to specify two different storage account resources for different configuration options.
You find the code samples in my GitHub repository.
-> https://github.com/neumanndaniel/terraform/tree/master/conditions-for-each-demo