Terraform: Cannot use dynamic inside block defined with ConfigModeAttr

Created on 6 Aug 2019  路  18Comments  路  Source: hashicorp/terraform

Hello,

Not sure if this was reported already, but I am having trouble following page at: https://www.hashicorp.com/blog/hashicorp-terraform-0-12-preview-for-and-for-each
when it comes to iterating lists that are declared in a locals block or those output by a module.


Suppose we have:

locals {
 #ip_whitelist = "${module.ip_whitelist.list}"  -- also not working
  ip_whitelist = ["127.0.0.1/32", "192.168.0.0/16"]
}

resource "azurerm_container_registry" "redacted" {
    name                =   "redacted"
    resource_group_name =   "${module.redacted.resource_group_name}"
    location            =   "${module.redacted.location}"
    admin_enabled       =   false
    sku                 =   "Premium"
    tags                =   "${var.tags}"

    georeplication_locations    =   ["East US"]

    network_rule_set        {
        default_action  =   "Deny"

        # dynamic ip_rule block
        dynamic "ip_rule" {
            for_each = local.ip_whitelist   # <--- error here?
            content {
                action  =   "Allow"
                ip_range    =   local.ip_whitelist  # <--- what do you put here when iterating?
            }
        }

        # static ip_rule block
        ip_rule   {
            action      =   "Allow"
            ip_range    =   "192.168.0.0/16"
        }
    }
}

This produces the following when running terraform plan/apply:

Error: Unknown variable

  on poc_acr.tf line 31, in resource "azurerm_container_registry" "redacted":
  31:             for_each = local.ip_whitelist

There is no variable named "local".

Same happens if I were to use module.ip_whitelist.list directly


Terraform version output:

Terraform v0.12.5
+ provider.azurerm v1.32.0
+ provider.external v1.2.0
+ provider.local v1.3.0
+ provider.random v2.1.2
bug config confirmed v0.12

Most helpful comment

After some guesswork and fiddling, I found this oddly works:

locals {
    acr_combined_wl = concat(module.ip_whitelist.ip_whitelist,module.core_westus.ase_outbound_addresses,module.core_eastus.ase_outbound_addresses) # combined list of all IPs in CIDR format
}

resource "azurerm_container_registry" "redacted" {
    name                =   "redacted"
    resource_group_name =   "${module.core_westus.resource_group_name}"
    location            =   "${module.core_westus.location}"
    admin_enabled       =   false
    sku                 =   "Premium"
    tags                =   "${var.tags}"

    georeplication_locations    =   ["East US"]

    network_rule_set       {
        default_action  =   "Deny"

        ip_rule =   [
            for ip in local.acr_combined_wl: {
                action  = "Allow"
                ip_range = ip
            }
        ]
    }
}

This seems to iterate and provide correct ip_rule entries.

All 18 comments

Here's another try based on official documentation.

module "ip_whitelist" {
    source  =   "../../modules/ip_whitelist"
}

resource "azurerm_container_registry" "redacted" {
    name                =   "redacted"
    resource_group_name =   "${module.core_westus.resource_group_name}"
    location            =   "${module.core_westus.location}"
    admin_enabled       =   false
    sku                 =   "Premium"
    tags                =   "${var.tags}"

    georeplication_locations    =   ["East US"]

    network_rule_set        {
        default_action  =   "Deny"

        dynamic "ip_rule" {
            for_each = module.ip_whitelist.ip_whitelist  # the output var here is a *list*

            content {
                action      =   "Allow"
                ip_range    =   ip_rule.value
            }
        }

    }
}

位 terraform apply -target azurerm_container_registry.redacted
Acquiring state lock. This may take a few moments...

Error: Unknown variable

  on poc_acr.tf line 24, in resource "azurerm_container_registry" "redacted":
  24:             for_each = module.ip_whitelist.ip_whitelist

There is no variable named "module".

Tried upgrading to v0.12.6 just now and still seeing the same error. FYI, running on Windows.

At this point I feel this is more of a bug, rather than a question as I am seeing the same error message if it's a module output, a local variable or a "variable" (as in var)

After some guesswork and fiddling, I found this oddly works:

locals {
    acr_combined_wl = concat(module.ip_whitelist.ip_whitelist,module.core_westus.ase_outbound_addresses,module.core_eastus.ase_outbound_addresses) # combined list of all IPs in CIDR format
}

resource "azurerm_container_registry" "redacted" {
    name                =   "redacted"
    resource_group_name =   "${module.core_westus.resource_group_name}"
    location            =   "${module.core_westus.location}"
    admin_enabled       =   false
    sku                 =   "Premium"
    tags                =   "${var.tags}"

    georeplication_locations    =   ["East US"]

    network_rule_set       {
        default_action  =   "Deny"

        ip_rule =   [
            for ip in local.acr_combined_wl: {
                action  = "Allow"
                ip_range = ip
            }
        ]
    }
}

This seems to iterate and provide correct ip_rule entries.

Hi @CryptonZylog,

Thanks for reporting the issue. That particular version of the azurerm provider is using a special feature in network_rule_set and ip_rule to allow setting the blocks as individual attributes. It appears as though this is interfering with the for_each evaluation and error reporting.

I don't see those fields in the master branch of the provider any longer, so they may be phasing them out entirely. We'll see if we can get terraform to output a better error here, or or at least hint that attribute syntax may be required.

I don't see those fields in the master branch of the provider any longer, so they may be phasing them out entirely.

Which fields are you thinking will be phased out? The workaround I have discovered or the layout of network_rule_set->ip_rule as documented here? Just want to know whether the "workaround" I have is what is supposed to work and not something that will go away shortly.

We'll see if we can get terraform to output a better error here, or or at least hint that attribute syntax may be required.

Thank you!

Those fields don't appear in the master branch of the provider. I'm not sure what the plan is for that resource in general. Your "workaround" is correct for this version of the provider, because those fields are used as attributes, therefore can use the expression syntax.

I am also facing a similar issue with aws resource. Here is the sample code:

locals {
  lifecycle_glacier_rule_enabled = var.glacier_transition_days == -1 ? [] : [""]
  lifecycle_expiry_rule_enabled  = var.expiration_days == -1 ? [] : [""]
}

locals {
  transition_map = {
    GLACIER = {
      enabled         = local.lifecycle_glacier_rule_enabled
      transition_days = var.glacier_transition_days
    }
  }
}

resource "aws_s3_bucket" "bucket" {
  bucket = var.bucket_name
  acl    = "log-delivery-write" #required to enable logging

  versioning {
    enabled = true
  }

  dynamic "lifecycle_rule" {
    # for_each = local.lifecycle_glacier_rule_enabled
    for_each = [for storageclass in local.transition_map : {  # error line, no variable named 'local'
      class = lifecycle_rule.key
      days  = storageclass.value
      }
    if storageclass.enabled.value == true]

    content {
      id      = "auto_glacier"
      enabled = true

      transition {
        storage_class = lifecycle_rule.value.class
        days          = lifecycle_rule.value.day
      }
    }
  }

Same issue is faced if you use replace local with var

Terraform v0.12.6

@test-in-prod not knowing your particular use case -- a possible workaround to use of the ACR network filtering is to provision a standalone vnet with a single subnet that is attached to your ACR with the service endpoint as defined here

That means that ACR accepts connections only from that subnet. You can then quite easily configure NSGs on the subnet to provide access via network peering.

I believe what @teamterraform was pointing towards is that the ACR network filtering is currently in Preview and may indeed change before it goes GA.

Hope that helps.

Same issue with the VMware vSphere provider

data "vsphere_datacenter" "datacenter" {
  name = "dc"
}

data "vsphere_virtual_machine" "template" {
  name          = "centos7"
  datacenter_id = data.vsphere_datacenter.datacenter.id
}

locals {
disks = { 
      for disk in data.vsphere_virtual_machine.template.disks : index(data.vsphere_virtual_machine.template.disks, disk) => disk
    }
} # data.vsphere_virtual_machine.template.disks is a list of maps

resource "vsphere_virtual_machine" "vsphere_vm" {
...
  dynamic "disk" {
    for_each = local.disks
    iterator = "template_disk"
    content {
      label            = "disk${template_disk.key}"
      unit_number      = template_disk.key
      size             = template_disk.value["size"]
      eagerly_scrub    = template_disk.value["eagerly_scrub"]
      thin_provisioned = template_disk.value["thin_provisioned"]
    }
  }
...
}

Error returned from terraform plan:

Error: Unknown variable

There is no variable named "local".

If I try feeding the for disk in data.vsphere_virtual_machine.template.disks .. iteration directly to for_each instead of using a separate local.disks value, the error returned is There is no variable named "data".

Issue only appears when iterating over a list inside inside a child module. When modules are not used, everything works without a problem.

Terraform v0.12.12
+ provider.vsphere 1.13

Hi,

Also I am running into the same problem, but using the resource aws_alb_listener as it can be in the code:

resource "aws_alb_listener" "web_app_elb_listener_http" {
  load_balancer_arn = local.web_elb_arn
  port              = 80
  protocol          = "HTTP" 

  dynamic default_action {    
    for_each = lookup( [ var.web_elb_listener_http_actions ], "def_action", [])
      content {
        type = def_action.value.type     
        dynamic fixed_response {
          for_each = lookup(def_action.value, "fixed_response", [])
            content {
              content_type = fixed_response.value.content_type
              status_code  = fixed_response.value.status_code
              message_body = lookup(fixed_response.value, "message_body", "" )
            }      
          }
      }
   }

}

when doing terraform plan I get :

 terraform plan
Error: Unknown variable

  on ../../../modules/cert_domain/elb_listener.tf line 23, in resource "aws_alb_listener" "web_app_elb_listener_http":
  23:     for_each = lookup( [ var.web_elb_listener_http_actions ], "def_actiona", [])

There is no variable named "var".


Error: Reference to undeclared resource

  on ../../../modules/cert_domain/elb_listener.tf line 27, in resource "aws_alb_listener" "web_app_elb_listener_http":
  27:           for_each = lookup(def_action.value, "fixed_response", [])

A managed resource "def_action" "value" has not been declared in
cert_domain_creation.

The variable declaration:

variable "web_elb_listener_http_actions" {
  description = "Here we configure the default action for the http listener"
  default = {
    def_action =[
    {    
        type  = "fixed-response"
        fixed_response = [
          {
            content_type = "text/plain"
            message_body = "Fixed response content"
            status_code  = "202"
          }
        ]
      }
    ]
  }
}

version is

Terraform v0.12.17
+ provider.aws v2.41.0

Ohh I realized of my error it is working as it should... sorry for the mistake... it is working like this:

resource "aws_alb_listener" "web_app_elb_listener_http" { 
  load_balancer_arn = local.web_elb_arn
  port              = 80
  protocol          = "HTTP"


  default_action {          
    type = var.web_elb_listener_http_actions.def_action.type
    dynamic fixed_response {        
      for_each = [ lookup(var.web_elb_listener_http_actions.def_action, "fixed_response", {}) ]
        content {
          content_type = fixed_response.value.content_type
          status_code  = fixed_response.value.status_code
          message_body = lookup(fixed_response.value, "message_body", "" )
      }
    }    
  }    
}

And the variable:

variable "web_elb_listener_http_actions" {
  description = "Here we configure the default action for the http listener"
  default ={ 
    def_action = {    
      type  = "fixed-response"
      fixed_response = {
        content_type = "text/plain"
        message_body = ""
        status_code  = "202"
         }
       }
    } 
}

I realized of my error ... so I am not in this bug...

I am getting the same error , is there a fix in any version ?
There is no variable named "var".

resource "azurerm_container_registry" "container_registry" {
for_each = var.acr
name = lookup(each.value, "acr_name", null) == null ? lower(format("%s-acr", each.key)) : lower(format("%s", each.value["acr_name"]))
resource_group_name = each.value["resource_group_name"]
location = each.value["location"]
sku = lookup(each.value, "sku", "Standard")
admin_enabled = lookup(each.value, "admin_enabled", true)
georeplication_locations = each.value["georeplication_locations"]

network_rule_set {

 default_action = "Deny"

 dynamic "ip_rule" {
   for_each = var.acr
    content {
      action  =   "Allow"
      ip_range    = each.value["ip_rule"]
    }
  }
}

}

acr = {
acr_2 = {
acr_name = "testingACR2"
resource_group_name = "ctc-sandbox-testing-rg-cc"
admin_enabled = "true"
georeplication_locations = ["East US",]
location = "canada central"
sku = "Premium"
subnet_id = ["some_id_1234",]
ip_rule = ["192.168.99.0/24",]
}
}

Same issue on helm provider using set

  dynamic "set" {
    for_each = [local.tag_keys]
    iterator = "tag"
    content {
      name  = "podLabels[${index(local.tag_keys, tag.key)}]"
      value = "${tag.key}=${lookup(var.tags, tag.key)}"
    }
  }

Error

Error: Unknown variable                                                                                                                                                                       

  on ../modules/external_dns/main.tf line 54, in resource "helm_release" "external_dns":                                                                                                      
  54:     for_each = [local.tag_keys]                                                                                                                                                         

There is no variable named "local".


Error: Invalid expression

  on ../modules/external_dns/main.tf line 55, in resource "helm_release" "external_dns":
  55:     iterator = "tag"
A single static variable reference is required: only attribute access and
indexing with constant keys. No calculations, function calls, template
expressions, etc are allowed here.

We were getting this as well on the Azure Container Registry resource, and this is because the ip_rule is an attribute and not a block, which is why "dynamic" doesn't work. The documentation doesn't match the code, but I think the code is actually wrong in this case. It's a provider bug I believe, but there is also a bug in Terraform that makes the error very confusing.

@test-in-prod has the correct solution. Using an attribute declaration instead of a block declaration.

resource "azurerm_container_registry" "redacted" {
    name                =   "redacted"
    resource_group_name =   "${module.core_westus.resource_group_name}"
    location            =   "${module.core_westus.location}"
    admin_enabled       =   false
    sku                 =   "Premium"
    tags                =   "${var.tags}"

    georeplication_locations    =   ["East US"]

    network_rule_set       {
        default_action  =   "Deny"

+       ip_rule =   [
+           for ip in local.acr_combined_wl: {
+               action  = "Allow"
+               ip_range = ip
+           }
+       ]
    }
}

The problem seems to be related to iterator. I found that when I avoided using iterator (and used the default iterator name instead, i.e. the name of the dynamic block), the error went away.

So the error message is definitely wrong.

on ../dlm.tf line 11, in resource "aws_dlm_lifecycle_policy" "this":
11: for_each = var.schedules

There is no variable named "var".

I renamed the issue to more precisely match the original problem statement.
I ran through a number of the additional configurations presented here, and most seem to be user error, or unrelated issues which have been fixed since 0.12. The initial report contains sufficient information to reproduce the issue; if you have questions concerning different configurations, it's better to use the community forum where there are more people ready to help.

There's also the issue of the error message itself being incorrect. Unless this has all been fixed in v0.13 (I haven't tried it yet).

Was this page helpful?
0 / 5 - 0 ratings