skip to Main Content

I am trying to bypass a virtual_network_subnet_id if a subnet variable is passed or not. It seems if the subnet variable is null, terraform is still trying to resolve the value if it is true and thus failing when resolving a null index. Any idea on how to pass a null value to the virtual_network_subnet_id if the subnet variable is not present, and then if it is present obtain the subnet variable from the azurerm subnet data block?

resource "azurerm_windows_web_app" "web-app" {
  for_each                  = var.web_app
  name                      = each.key
  resource_group_name       = data.azurerm_resource_group.rg2.name
  location                  = data.azurerm_resource_group.rg2.location
  tags                      = merge(local.base_tags, { Component = each.value.component_tag })
  service_plan_id           = azurerm_service_plan.asp[each.value.asp].id
  virtual_network_subnet_id = each.value.subnet != "" ? data.azurerm_subnet.vnet-subnets[each.value.subnet].id : null


  site_config {
    ftps_state             = "FtpsOnly"
    minimum_tls_version    = 1.2
    vnet_route_all_enabled = each.value.subnet != "" ? true : false
    worker_count           = each.value.worker_count
    always_on              = each.value.always_on
    use_32_bit_worker      = each.value.use_32_bit_worker

    application_stack {
      dotnet_version = each.value.dotnet_version
    }

    ip_restriction_default_action = "Deny"

    dynamic "ip_restriction" {
      for_each = local.vpn
      content {
        ip_address = ip_restriction.value["ip_address"]
        action     = "Allow"
        priority   = ip_restriction.value["priority"]
        name       = ip_restriction.value["name"]
      }
    }
    dynamic "ip_restriction" {
      for_each = local.azure_service_tag
      content {
        name        = ip_restriction.value["name"]
        service_tag = ip_restriction.value["service_tag"]
        priority    = ip_restriction.value["priority"]
      }
    }
    #Subnets
    dynamic "ip_restriction" {
        for_each = local.subnets
        content {
          name = ip_restriction.value["subnet_name"]
          virtual_network_subnet_id = data.azurerm_virtual_network.vnet.id
          priority = ip_restriction.value["priority"]
        }
      
    }
  }

  identity {
    type = "SystemAssigned"
  }

  lifecycle {
    ignore_changes = [
      identity,
      app_settings,
      connection_string,
      https_only,
      site_config
    ]
  }

}

Error:

│ Error: Invalid index
│
│   on web_app.tf line 12, in resource "azurerm_windows_web_app" "web-app":
│   12:   virtual_network_subnet_id = each.value.subnet != "" ? data.azurerm_subnet.vnet-subnets[each.value.subnet].id : null
│     ├────────────────
│     │ data.azurerm_subnet.vnet-subnets is object with 9 attributes
│     │ each.value.subnet is null
│
│ Can't use a null value as an indexing key.

2

Answers


  1. You can do this by wrapping virtual_subnet_id with the try function, add this func for each.value.subnet which is of interest to you.
    This code sets virtual_network_subnet_id to null for each.value.subnet or an empty string, to avoid the invalid index error, which is quite common.

    See docs for the try function in terrafrom

    You can add this below that should solve the error:

    virtual_network_subnet_id = try(
      each.value.subnet != "" && each.value.subnet != null ? data.azurerm_subnet.vnet-subnets[each.value.subnet].id : null,
      null
    )
    

    Although there is a warning

    Warning: The try function is intended only for concise testing of the
    presence of and types of object attributes. Although it can
    technically accept any sort of expression, we recommend using it only
    with simple attribute references and type conversion functions as
    shown in the examples above. Overuse of try to suppress errors will
    lead to a configuration that is hard to understand and maintain.

    We could use this instead for the sake of not over using try

    virtual_network_subnet_id = (
      each.value.subnet != "" && each.value.subnet != null
    ) ? data.azurerm_subnet.vnet-subnets[each.value.subnet].id : null
    
    Login or Signup to reply.
  2. You can use a conditional expression or a try() function in this case, I will explain both scenario’s below. However, it would have been much easier if the subnet variable you supply never contains an empty string. That way it is either null or contains a valid string. You can add a validation rule to check for the existence of empty strings in the subnet attribute.

    variable "web_app" {
      description = "This is a web app"
      default = list(object({
        worker_count = optional(number)
        always_on    = optional(bool)
        subnet       = optional(string)
      }))
    
      validation {
        condition     = var.web_app.subnet == null || length(var.web_app.subnet > 0)
        error_message = "The value for web_app.subnet must either be a valid string or null"
      }
    }
    

    Using the try() function

    The most elegant solution can be achieved by using the try() function. When accessing the data resource with a key that does not exist, the error will be caught and it will fall back to the supplied null value.

    virtual_network_subnet_id = try(data.azurerm_subnet.vnet-subnets[each.value.subnet].id, null)
    

    Using a conditional expression

    One can use a simple conditional expression to check if each.value.subnet is valid. By using an OR expression, one can check whether each.value.subnet contains either an empty string or a null value.

    virtual_network_subnet_id = (
      each.value.subnet == "" || each.value.subnet == null
    ) ? null : data.azurerm_subnet.vnet-subnets[each.value.subnet].id
    

    Alternatively, one can use AND to check if the subnet does not contain an empty string or null value.

    virtual_network_subnet_id = (
      each.value.subnet != "" && each.value.subnet != null
    ) ? data.azurerm_subnet.vnet-subnets[each.value.subnet].id : null
    

    Using the can() function

    The can() function is not very useful in this context, since we work with a variable that can have optional values with a default (e.g. null). When using maps inside a locals block, this function might be useful to check for the existence of an optional attribute.

    Other alternatives

    Alternatively one can accomplish the same by checking the type() and length() of an attribute.

    virtual_network_subnet_id = (
      type(each.value.subnet) == string && length(each.value.subnet) > 0
    ) ? data.azurerm_subnet.vnet-subnets[each.value.subnet].id : null
    

    When accessing an attribute, one can also use the lookup() function. Be aware that each.value.subnet still has to be a valid key. Only the try function catches errors!

    virtual_network_subnet_id = (
      type(each.value.subnet) == string && length(each.value.subnet) > 0
    ) ? lookup(data.azurerm_subnet.vnet-subnets[each.value.subnet], "id") : null
    

    The lookup() function can also be combined with the try() function.

    virtual_network_subnet_id = try(lookup(data.azurerm_subnet.vnet-subnets[each.value.subnet], "id"), null)
    

    The last alternative tries to test whether each.value.subnet contains at least one character. The try() function catches the error in case the lenght function tries to parse a null value.

    virtual_network_subnet_id = try(length(each.value.subnet) > 0, false) ? null : data.azurerm_subnet.vnet-subnets[each.value.subnet].id
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search