Continue with Optional Attributes - ivaramme/terraform-azurerm-caf GitHub Wiki
This section explains the optional attributes that illustrate some of the patterns described in the Specific Design Patterns guide
Example: Add optional filter block
From the Terraform Registry documentation, each resource supports only one optional filter block
Within a filter block, there are other nested blocks
This example illustrates how to add a single filter block that could have multiple optional dimension blocks
Start by implementing the optional filter block without handling the criteria for multiple optional dimension blocks. This means starting with a single dimension block in examples/consumption_budget/100-consumption-budget-rg/configuration.tfvars
filter = {
dimensions = {
explicit_name = {
name = "ResourceGroupName"
values = [
"example",
]
}
}
}
Then in the resource, add a dynamic block to handle the optional filter. This pattern is described in Single optional block. Essentially, this is handled by the null check and the feature flag that uses [], and [1] conditionally
dynamic "filter" {
for_each = try(var.settings.filter, null) == null ? [] : [1]
content {
dynamic "dimension" {
for_each = var.settings.filter.dimensions
content {
name = dimension.value.name
values = dimension.value.values
}
}
}
}
Example: Add optional multiple dimension blocks
Building on the previous example, first start by adding more dimension blocks to the examples in examples/consumption_budget/100-consumption-budget-rg/configuration.tfvars. In the snippet below, this filter block contains 2 dimensions. The first uses the given name that is part of the valid list in the Terraform registry documentation, ResourceGroupName. The second uses the resource_group_key name to look up the resource group ID from an object of resource groups.
filter = {
dimensions = {
explicit_name = {
name = "ResourceGroupName"
operator = "In"
values = [
"example",
]
},
resource_group_key = {
# lz_key = "examples"
name = "resource_group_key"
values = [
"test",
]
}
}
}
In this case, the resource will need a map of resource group keys to its attribtues (like the ID). This map is provided by the local.combined_objects_resource_groups and is explained in Integrating remote and local state. Note that the client_config is also passed as an additional variable because this is used to lookup a landingzone_key that allows remote resource groups to be referenced.
module "consumption_budgets_resource_groups" {
source = "./modules/consumption_budget/resource_group"
# truncated
client_config = local.client_config
resource_groups = local.combined_objects_resource_groups
settings = each.value
}
Then modify the modules/consumption_budget/variables.tf to accept these new variables
variable "client_config" {
description = "Client configuration object"
}
# truncated
variable "resource_groups" {
description = "Map of resource group keys to resource group attributes"
}
These variables are used in modules/consumption_budget/resource_group_budget.tf
dynamic "filter" {
for_each = try(var.settings.filter, null) == null ? [] : [1]
content {
dynamic "dimension" {
for_each = {
for key, value in try(var.settings.filter.dimensions, {}) : key => value
if lower(value.name) != "resource_group_key"
}
content {
name = dimension.value.name
operator = try(dimension.value.operator, "In")
values = dimension.value.values
}
}
dynamic "dimension" {
for_each = {
for key, value in try(var.settings.filter.dimensions, {}) : key => value
if lower(value.name) == "resource_group_key"
}
content {
name = "ResourceId"
operator = try(dimension.value.operator, "In")
values = try(flatten([
for key, value in var.resource_groups[try(dimension.value.lz_key, var.client_config.landingzone_key)] : value.id
if contains(dimension.value.values, key)
]), [])
}
}
}
}
- The
filterblock feature flag pattern remains the same since it is still a single optional block - The
dimensionblock adopts a slightly different pattern. 2dynamicblocks are declared to handle this since we want to condition on thenamethat is injected. - The first block handles the default case of using a valid
namethat is part of the Terraform registry documentation. This is done by first destructuringvar.settings.filter.dimensionsto get the key and value wherevalue.namecan be used to get thenameattribute that was defined inconfiguration.tfvars. In this example,value.nametranslates toResourceGroupName. - The second block handles the case where the
nameinconfiguration.tfvarswasresource_group_key. Here it uses this name to reference theidattribute from theresource_groupsvariable that was passed into this resource.
Repeat similar patterns for the tag and not blocks