Using Templatefiles to generate a variable

Hi there,

I seem to have an issue and hoping someone can point me in the right direction when it comes to using templatefile to create an map(object) variable.

I have 3 environments. I am trying to create deploy to all 3 environments (in the same sub) for each GW listener we have (around 10)

Main TF looks like this:

data "azurerm_application_gateway" "gw" {
  name                = var.application_gateway_name
  resource_group_name = data.azurerm_resource_group.rg.name
}

locals {
  https_listener_names = [for listener in data.azurerm_application_gateway.gw.http_listener : listener.name]
  waf_rules_json = templatefile("./template.j2.tpl", {
    listeners          = local.https_listener_names
    environment_domain = "domain"
  })
  waf_rules     = jsondecode(trimspace(local.waf_rules_json))
  waf_rules_map = merge([for rule in local.waf_rules : rule]...)
}

module "waf_policies" {
  source                               = "../modules/az_waf_policies"
  resource_group_name                  = data.azurerm_resource_group.rg.name
  location                             = data.azurerm_resource_group.rg.location
  application_gateway_name = data.azurerm_application_gateway.gw
  policy_mode                          = var.policy_mode
  custom_listener_rules                = local.waf_rules_map
}

A snippet of my WAF policy which is just a generic for_each

resource "azurerm_web_application_firewall_policy" "gw" {
  for_each = {
    for listener in var.gw_application_gateway_name.http_listener : listener.name => merge(
      {
        mode                  = var.policy_mode
        managed_rule_type     = "OWASP"
        rule_version          = "3.2"
        custom_rules          = []
        rule_group_exclusions = []
        rule_group_overrides  = []
      },
      lookup(var.custom_listener_rules, listener.name, {
        mode                  = var.policy_mode
        managed_rule_type     = "OWASP"
        rule_version          = "3.2"
        custom_rules          = []
        rule_group_exclusions = []
        rule_group_overrides  = []
      })
    )
  }

  name                = "${each.key}--waf-policy"
  resource_group_name = var.resource_group_name
  location            = var.location

  ...

The templatefile looks like:

{
    "custom_listener_rules": {
    %{ for listener in listeners }
    "${listener}-https-local": {
        "custom_rules": [
          {
            "action": "Allow",
            "match_conditions": [
              {
                "match_values": [
                  "domain1",
                  "domain2",
                  "domain3"
                ],
                "operator": "Contains",
                "variable_name": "RequestUri"
              },
              {
                "match_values": [
                  "https://${environment_domain}/search"
                ],
                "operator": "Equal",
                "selector": "Referer",
                "variable_name": "RequestHeaders"
              }
            ],
            "name": "Domains",
            "priority": 1,
            "rule_type": "MatchRule"
          },
        ...

When i run my terraform, i am getting a var.custom_listener_rules - No value for requred Variable. So it’s not passing it in. Any ideas?

Thanks.

Without a complete example I’m not sure exactly how you are ending up with a null value for custom_listener_rules, but the template looks like a problem nonetheless. You can’t reliably template json like that, because json has struct rules about trailing commas within objects and arrays.

If you’re templating json only to use jsondecode, why not just write the terraform expression directly?

How do you mean by write it directly? I thought that templatefile function relied on json being passed in? Thanks

I’m not sure where that idea comes from, templatefile is unrelated to json, and the docs even call out that usage as problematic and suggest encoding json directly from your hcl object if the json string is your end goal.

Your goal here doesn’t seem to have anything to do with json though, but rather you want to pass a valid object to the custom_listener_rules module variable. For that you can write an equivalent expression directly in your config file, iterating over local.https_listener_names with a for-expression.

If you’re having trouble doing this, please create a minimal complete example showing what you’ve tried and what the desired output should be from the given data, and someone should be able to demonstrate a solution.

1 Like

Oh wow. Talk about a miss understanding of the docs. Burnt a few days trying to get this to work with my tfvar for the variable I want to template in JSON for no reason.

Will give this a whirl and let you know. I appreciate your help!