= Terraform Conditional Arguments and Dynamic Blocks =
**Summary**: This wiki page shows how I used terraform's conditionals in arguments and dynamic blocks to make configurations more flexible by showing a few examples. \\
**Date**: 1 January 2026 \\
{{tag>terraform}}
Conditionals and dynamic blocks are terraform features you usually don't need when you're just starting. Their power comes when you're starting to work on more complex terraform configurations or modules. Below I show a couple of examples where I used conditionals and dynamic blocks. For both features, you always need to consider if using them is really the best solution for your problem. I've often encountered that using for example default values and optional arguments can solve the same problem in a more straightforward way.
== Conditionals ==
I usually use [[https://developer.hashicorp.com/terraform/language/expressions/conditionals |terraform conditionals]] to optionally include certain arguments in a resource or a module.
=== Conditional Resource Creation ===
My most used example of using conditionals is to conditionally create resources based on a boolean variable. In the example below, taken from [[https://github.com/getshifting/getshifting/blob/main/terraform/apps/postgresql/postgresql.tf |my PostgreSQL module]], I only create a Private DNS Zone if the variable `deploy_psql_pep` is set to true:
module "avm-res-network-privatednszone" {
count = var.deploy_psql_pep == true ? 1 : 0
source = "Azure/avm-res-network-privatednszone/azurerm"
domain_name = var.privatezone
parent_id = module.avm-res-resources-resourcegroup.resource_id
tags = var.tags
enable_telemetry = false
}
=== Conditional Arguments ===
Another use case is to conditionally include certain arguments in a resource or a module. For example, in the code snippet below, I use a conditional argument to only include the `law_id` argument if the variable `deploy_psql_logs` is set to true. If it's false, the argument is set to null and will be ignored by terraform.
law_id = var.deploy_psql_logs ? module.law.id : null
This is another example where I use conditional arguments to only enable cross-region restore if the redundancy is set to GeoRedundant and the variable `cross_region_restore_enabled` is true:
cross_region_restore_enabled = var.redundancy == "GeoRedundant" && var.cross_region_restore_enabled ? true : null
=== Conditional Argument Values ===
In the example below, I set the value to a certain argument based on the value of a variable:
# Required for restore scenarios
create_mode = var.create_mode
# Set to null if empty string. Only required when using create_mode of Restore or Recovery
recover_database_id = var.recover_database_id == "" ? null : var.recover_database_id
recovery_point_id = var.recovery_point_id == "" ? null : var.recovery_point_id
== Dynamic Blocks ==
[[https://developer.hashicorp.com/terraform/language/expressions/dynamic-blocks |Dynamic blocks]] are another way of making terraform configurations more flexible. They also have different use cases.
=== Repeat for each Object in a List ===
Below is an example where dynamic blocks are used to repeat a block for each object in a list. The variable {{{backup_vault_blobstorage_policies}}} has a list of {{{policy_retention_rules}}} objects. In the resource definition, a dynamic block is used to create a {{{retention_rule}}} block for each object in that list.
# Variable
backup_vault_blobstorage_policies = [
{
policy_name = "bkpol-euw-shift"
policy_repeating_time_intervals = ["R/2025-01-01T04:00:00Z/P1D"]
policy_default_vault_retention_duration = "P5D"
policy_data_store_type = "VaultStore"
policy_retention_rules = [
{
rule_name = "Yearly"
rule_retention_duration = "P1Y"
rule_priority = 1
absolute_criteria = "FirstOfYear"
},
{
rule_name = "Monthly"
rule_retention_duration = "P1M"
rule_priority = 5
absolute_criteria = "FirstOfMonth"
},
{
rule_name = "Weekly"
rule_retention_duration = "P1W"
rule_priority = 10
absolute_criteria = "FirstOfWeek"
},
{
rule_name = "Daily"
rule_retention_duration = "P2D"
rule_priority = 15
absolute_criteria = "FirstOfDay"
}
]
}
]
# Resource
resource "azurerm_data_protection_backup_policy_blob_storage" "bkpol" {
for_each = { for policy in var.backup_vault_blobstorage_policies : policy.policy_name => policy }
name = each.key
vault_id = azurerm_data_protection_backup_vault.bvault.id
backup_repeating_time_intervals = each.value.policy_repeating_time_intervals
vault_default_retention_duration = each.value.policy_default_vault_retention_duration
dynamic "retention_rule" {
for_each = each.value.policy_retention_rules
content {
name = retention_rule.value.rule_name
priority = retention_rule.value.rule_priority
criteria {
absolute_criteria = retention_rule.value.absolute_criteria
}
life_cycle {
data_store_type = each.value.policy_data_store_type
duration = retention_rule.value.rule_retention_duration
}
}
}
}
=== Conditionally add Dynamic Blocks to a Resource ===
In the example below, the variable only defines the {{{retention_daily}}} and {{{retention_weekly}}} objects. In the resources definition, because the {{{retention_monthly}}} and {{{retention_yearly}}} objects are not defined, these rules will not be created.
# Variable
recovery_vault_policies_vms = [
{
policy_name = "rsvpol-euw-shift"
backup_frequency = "Daily"
timezone = "UTC"
instant_restore_retention_days = 7
backup_time = "02:00"
retention_daily = {
count = 8
}
retention_weekly = {
count = 2
weekdays = ["Wednesday"]
}
instant_restore_resource_group_prefix = "rg-euw-shift-azurebackup-"
}
]
# Resource
resource "azurerm_backup_policy_vm" "rsvpol_vm" {
for_each = { for policy in var.recovery_vault_policies_vms: policy.policy_name => policy }
name = each.key
resource_group_name = var.resource_group_name
policy_type = "V2"
recovery_vault_name = azurerm_recovery_services_vault.rsvault.name
timezone = each.value.timezone
instant_restore_retention_days = each.value.instant_restore_retention_days
backup {
frequency = each.value.backup_frequency
time = each.value.backup_time
}
dynamic "retention_daily" {
for_each = each.value.retention_daily != null ? [each.value.retention_daily] : []
content {
count = lookup(retention_daily.value, "count")
}
}
dynamic "retention_weekly" {
for_each = each.value.retention_weekly != null ? [each.value.retention_weekly] : []
content {
count = lookup(retention_weekly.value, "count")
weekdays = lookup(retention_weekly.value, "weekdays")
}
}
dynamic "retention_monthly" {
for_each = each.value.retention_monthly != null ? [each.value.retention_monthly] : []
content {
count = lookup(retention_monthly.value, "count")
weekdays = lookup(retention_monthly.value, "weekdays")
weeks = lookup(retention_monthly.value, "weeks")
}
}
dynamic "retention_yearly" {
for_each = each.value.retention_yearly != null ? [each.value.retention_yearly] : []
content {
count = lookup(retention_yearly.value, "count")
weekdays = lookup(retention_yearly.value, "weekdays")
weeks = lookup(retention_yearly.value, "weeks")
months = lookup(retention_yearly.value, "months")
}
}
instant_restore_resource_group {
prefix = each.value.instant_restore_resource_group_prefix
}
}
For completeness, as some of the blocks are not required, this is the variable definition:
variable "recovery_vault_policies_vms" {
description = "(Required) The list of Recovery Vault Policies. Note that the instant_restore_retention_days MUST be lower than the retention_daily count."
type = list(object({
policy_name = string
default_policy = optional(bool, false)
timezone = string
instant_restore_retention_days = number
backup_frequency = string
backup_time = string
retention_daily = optional(object({
count = number
}), null)
retention_weekly = optional(object({
count = number
weekdays = list(string)
}), null)
retention_monthly = optional(object({
count = number
weekdays = list(string)
weeks = list(string)
}), null)
retention_yearly = optional(object({
count = number
weekdays = list(string)
weeks = list(string)
months = list(string)
}), null)
instant_restore_resource_group_prefix = string
}))
}
=== Conditionally add Dynamic Blocks to a Resource II ===
In the example below another use case is displayed for combining conditionals and dynamic blocks. In the example below, Entra ID permissions need to be assigned to a storage account fileshare. For this, the file share first needs to be configured and added to [[https://learn.microsoft.com/en-us/azure/storage/files/storage-files-identity-ad-ds-enable |Active Directory]]. For that purpose, a condition is added that is dependent on the variable {{{azure_files_authentication_enabled}}}. This variable can first be set to false, and once the share is added and the required information is known, the variable can be set to true to add the block to the resource.
# Variables
azure_files_authentication_enabled = true
azure_storage_sid = "S-1-5-21-c07042a510-976c1cfc35-109e3ee4cb-59734"
# Resource
resource "azurerm_storage_account" "storage_account" {
name = var.storage_account_name
resource_group_name = var.resource_group_name
location = var.location
account_tier = var.account_tier
account_replication_type = var.replication_type
allow_nested_items_to_be_public = var.allow_nested_items_to_be_public
tags = var.tags
dynamic "azure_files_authentication" {
for_each = var.azure_files_authentication_enabled ? [1] : []
content {
directory_type = "AD"
active_directory {
domain_name = "getshifting.local"
domain_guid = "446b774a-fd85-424d-947f-6cd03c26a57d"
domain_sid = "S-1-5-21-c07042a510-67a1a4512-5974e28db3"
forest_name = "getshifting.local"
storage_sid = var.azure_storage_sid
netbios_domain_name = "getshifting.local"
}
}
}
}
//This wiki has been made possible by://