Skip to content

Continue with Optional Attributes

GlennChia edited this page Jul 7, 2021 · 1 revision

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 filter block feature flag pattern remains the same since it is still a single optional block
  • The dimension block adopts a slightly different pattern. 2 dynamic blocks are declared to handle this since we want to condition on the name that is injected.
  • The first block handles the default case of using a valid name that is part of the Terraform registry documentation. This is done by first destructuring var.settings.filter.dimensions to get the key and value where value.name can be used to get the name attribute that was defined in configuration.tfvars. In this example, value.name translates to ResourceGroupName.
  • The second block handles the case where the name in configuration.tfvars was resource_group_key. Here it uses this name to reference the id attribute from the resource_groups variable that was passed into this resource.

Repeat similar patterns for the tag and not blocks

Clone this wiki locally