Unable to loop over list using for_each - terraform

I want to reserve an IP and then use it. If I create a separate google_compute_address block for each IP, it works well. But since I want to make the code as dry and optimized as possible, I am learning how to loop and use for_each
My main.tf looks like this
module "nat" {
source = "../../modules/nat"
reserved_ips = [
{
name = "gke-frontend-prod-lb"
ip = "10.238.232.10"
},
{
name = "gke-frontend-test-lb"
ip = "10.238.232.11"
}
]
}
As you can see, I would like to form a list of reserved IPs having name and IP.
Now lets look at my module
My variables.tf looks like
variable "reserved_ips" {
type = list(object({
name = string
ip = string
}))
description = <<EOF
Reserved IPs.
EOF
}
And the main.tf of my module looks like
locals {
ips = {
# for_each needs transform to map
for ip in var.reserved_ips : "${ip.name}" => "${ip.ip}"
}
}
resource "google_compute_address" "gke-frontend" {
for_each = local.ips
name = "${each.value.name}"
subnetwork = "mysubnet"
address_type = "INTERNAL"
address = "${each.value.ip}"
}
But running the code gives me
Error: Unsupported attribute
on ../../modules/nat/main.tf line 11, in resource "google_compute_address" "gke-frontend":
11: name = "${each.value.name}"
|----------------
| each.value is "10.238.232.10"
This value does not have any attributes.
Error: Unsupported attribute
on ../../modules/nat/main.tf line 11, in resource "google_compute_address" "gke-frontend":
11: name = "${each.value.name}"
|----------------
| each.value is "10.238.232.11"
This value does not have any attributes.
Error: Unsupported attribute
on ../../modules/nat/main.tf line 14, in resource "google_compute_address" "gke-frontend":
14: address = "${each.value.ip}"
|----------------
| each.value is "10.238.232.10"
This value does not have any attributes.
Error: Unsupported attribute
on ../../modules/nat/main.tf line 14, in resource "google_compute_address" "gke-frontend":
14: address = "${each.value.ip}"
|----------------
| each.value is "10.238.232.11"
This value does not have any attributes.
Im confused as to what am I missing here exactly.

The issue is that your ips local converts the list to a map(string) (i.e. a map with string values)
locals {
ips = {
# for_each needs transform to map
for ip in var.reserved_ips : "${ip.name}" => "${ip.ip}"
}
}
Notice that on the right-side of => you have "${ip.ip}".
When for_each loops over a map it assigns each.key to each key (a string) and each.value to each corresponding value in the map (in this case "${ip.ip} is also a string).
So, I think what you want in this case is something like the following
# ...
name = each.key
# ...
address = each.value
# ...

Related

Dynamic resources for_each output in terraform module

Terraform v1.0.0
Provider: aws v3.49.0
I created dynamic AWS subnets resources with a for_each from a module.
The resources creation is working fine, however being able to output dynamically created resources is not working and cannot find proper documentation for it.
The subnet module is
resource "aws_subnet" "generic" {
vpc_id = var.vpc_id
cidr_block = var.cidr_block
map_public_ip_on_launch = var.public_ip_on_launch
tags = {
Name = var.subnet_tag_name
Environment = var.subnet_environment
}
}
With simple module output defined
output "subnet_id" {
value = aws_subnet.generic.id
}
Then from root module, I am creating a for_each loop over a list variable to create multiple dynamic resources from the module
module "subnets" {
source = "../modules/networking/subnet"
for_each = var.subnets
vpc_id = "vpc-09d6d4c17544f3a49"
cidr_block = each.value["cidr_block"]
public_ip_on_launch = var.public_ip_on_launch
subnet_environment = var.subnet_environment
subnet_tag_name = each.value["subnet_tag_name"]
}
When I run this without defining outputs in the root module, things get created normally. The problem comes when I try to define the outputs
output "subnets" {
value = module.subnets.*.id
description = "Imported VPC ID"
}
It comes up with this error
│ Error: Unsupported attribute
│
│ on output.tf line 2, in output "subnets":
│ 2: value = module.subnets.*.id
│
│ This object does not have an attribute named "id".
I tried different output definitions. Would appreciate guidance on how to properly define outputs of instances dynamically created with a for_each module.
Per the Terraform documentation, the "splat" operator (*) can only be used with lists, and since you're using for_each your output will be a map.
You need to use map/list comprehension to achieve what you want.
For an output that is a map of key/value pairs (note that I've changed the output description to something that makes more sense):
output "subnets" {
value = {
for k, v in module.subnets:
k => v.subnet_id
}
description = "Subnet IDs"
}
For a list that only contains the subnet IDs:
output "subnets" {
value = [
for k, v in module.subnets:
v.subnet_id
]
description = "Subnet IDs"
}

Terraform : Using for_each in module

I am using terraform version 0.14.3.
I have a module for creating an Azure Network Interface Card, as below:
resource "azurerm_network_interface" "nic" {
name = var.nic_name
location = var.location
resource_group_name = var.rg_name
ip_configuration {
name = var.ipconfig_name
subnet_id = var.subnet_id
private_ip_address_allocation = "Dynamic"
}
}
Its output is defined as :
output "nic_id" {
value = azurerm_network_interface.nic.id
}
I am calling this module in this parent module:
module "NIC" {
source = "./NIC"
for_each = var.nics
nic_name = each.value.nic_name
location = "eastus2"
rg_name = "abc-test-rg"
ipconfig_name = each.value.ipconfig_name
subnet_id = <subnet_id>
}
output "nic_ids" {
value = [for k in module.NIC.nic_id : k.id]
}
The NIC values are defined as below:
nics = {
nic1 = {
nic_name = "abc-nic-1"
ipconfig_name = "nic-1-ipconfig"
}
}
I want to loop around the NIC output IDs, and want them displayed.
When I run above code, I get below error in terraform plan :
Error: Unsupported attribute
on main.tf line 15, in output "nic_ids":
15: value = [for k in module.NIC.nic_id : k.id]
|----------------
| module.NIC is object with 1 attribute "nic1"
This object does not have an attribute named "nic_id".
How do I get around it ?
Your module "NIC" block has for_each set, and so the module.NIC symbol elsewhere in the module is a mapping from instance keys to output objects, rather than just a single output object as for a singleton module.
Terraform's error message is attempting to draw attention to that with the following message:
module.NIC is object with 1 attribute "nic1"
Notice that nic1 here is a key from your var.nics, and not one of the output values defined in your module.
Assuming that the nic_id output you showed here is the only one defined in that module, the module.NIC value would be shaped something like this:
{
nic1 = {
nic_id = "eni-e5aa89a3"
}
}
It sounds like you instead want to produce a value shaped like this:
{
nic1 = "eni-e5aa89a3"
}
If so, a suitable expression to get that result would be the following:
output "nic_ids" {
value = { for k, nic in module.NIC : k => nic.nic_id }
}
The above means: produce a mapping with one element for each instance of the NIC module, whose key is the module instance key and whose value is the nic_id output value.
Alternatively, if it doesn't matter which id belongs to which instance then you could produce an unordered set of ids, like this:
output "nic_ids" {
value = toset([for nic in module.NIC : nic.nic_id])
}
In this case the for expression only defines a local symbol nic, which represents the module instance object, because it doesn't do anything with the instance key. The toset here is to represent that the IDs are not in any particular order: that isn't strictly necessary but I think it's a good practice to make sure that any other Terraform code depending on that value doesn't inadvertently depend on the current arbitrary ordering of the ids, which might change in future if you add or remove elements in var.nics.

Terraform error with dynamic block in for_each

I'm trying to instantiate an azure storage_share map using the azuremrm resource storage_share. By design, I need to be able to instantiate more than one storage share with the same block; each of those shares may or may not have an "acl" section.
I was thinking of solving this issue using a for_each in conjuction with a dynamic block, as in the related SE question:
Main.tf
resource "azurerm_storage_share" "storage_share" {
for_each = var.storage_share_map
name = each.key
storage_account_name = azurerm_storage_account.sa.name
quota = each.value.quota
dynamic "acl" {
for_each = each.value.acl
content {
id = acl.value.id
access_policy {
permissions = acl.value.access_policy.permissions
start = acl.value.access_policy.start
expiry = acl.value.access_policy.expiry
}
}
}
The variable would be defined as:
variable "storage_share_map" {
type = map(object({
quota = number,
acl = object({
id = string,
access_policy = object({
expiry = string,
permissions = string,
start = string
})
}),
}))
default = {}
}
and later parametrized in my tests as:
storage_share_map = {
my-share-2 = {
quota = 123,
acl = {
id = "a-id",
access_policy = {
expiry = "ISO8061 UTC TIME"
permissions = "rwdl"
start = "ISO8601 UTC TIME"
},
},
}
However, when testing, terraform returns the following output:
Error: Unsupported attribute
on .terraform\modules\sa\main.tf line 83, in resource "azurerm_storage_share" "storage_share":
83: id = acl.value.id
|----------------
| acl.value is object with 3 attributes
This object does not have an attribute named "id".
Error: Unsupported attribute
on .terraform\modules\sa\main.tf line 83, in resource "azurerm_storage_share" "storage_share":
83: id = acl.value.id
|----------------
| acl.value is "a-id"
This value does not have any attributes.
Error: Unsupported attribute
on .terraform\modules\sa\main.tf line 86, in resource "azurerm_storage_share" "storage_share":
86: permissions = acl.value.access_policy.permissions
|----------------
| acl.value is object with 3 attributes
This object does not have an attribute named "access_policy".
Error: Unsupported attribute
on .terraform\modules\sa\main.tf line 86, in resource "azurerm_storage_share" "storage_share":
86: permissions = acl.value.access_policy.permissions
|----------------
| acl.value is "a-id"
This value does not have any attributes.
As I understand it, the issue here is that the for_each inside the dynamic block is either malformed or misbehaving: acl.value appears to be both valued as the string "a-id" and carrying three attribute (?).
Terraform version 0.12.26
Azurerm version 2.26.0
Any insight would be appreciated.
Related question:
Dynamic block with for_each inside a resource created with a for_each
By iterating in the dynamic block with for_each = each.value.acl, you are iterating over the values in the object type. It appears you really want to iterate over the acl themselves. You would need to adjust your type to:
variable "storage_share_map" {
type = map(object({
quota = number,
acl = list(object({
...
}))
})),
}
You can tell from the error messages that currently it is iterating over id and then access_policy, and failing to find the two requested attributes for each, which is why you have 2*2=4 errors.
You can adjust your input correspondingly to:
storage_share_map = {
my-share-2 = {
quota = 123,
acl = [{
id = "a-id",
access_policy = {
expiry = "ISO8061 UTC TIME"
permissions = "rwdl"
start = "ISO8601 UTC TIME"
},
}],
}
and this will achieve the behavior you desire.
Note that Terraform 0.12 has issues sometimes with nested object type specifications, so omitting the acl with [] may result in crashing under certain circumstances.
Please use square brackets for each.value.acl.
Azure storage share block should look like:
resource "azurerm_storage_share" "storage_share" {
for_each = var.storage_share_map
name = each.key
storage_account_name = azurerm_storage_account.sa.name
quota = each.value.quota
dynamic "acl" {
for_each = [each.value.acl]
content {
id = acl.value.id
access_policy {
permissions = acl.value.access_policy.permissions
start = acl.value.access_policy.start
expiry = acl.value.access_policy.expiry
}
}
}
}

Invalid Index on data lookup on multiple vault auth backend

I am writing a terraform module creates a single entity with multiple aliases. I'm an unable to lookup aliases auth backend. am I missing something. Any help greatly appreciated.
data "vault_auth_backend" "b" {
provider = vault.this
for_each = {
for alias in var.entity.aliases :
alias.type => alias
}
path = each.value.auth_path
}
resource "vault_identity_entity_alias" "alias" {
provider = vault.this
for_each = {
for alias in var.entity.aliases :
alias.name => alias
}
name = each.key
mount_accessor = lookup(data.vault_auth_backend.b[each.key], "accessor", null)
canonical_id = vault_identity_entity.entity.id
}
Terraform Plan output:
Error: Invalid index
on .terraform/modules/vault_dba_entity/main.tf line 31, in resource "vault_identity_entity_alias" "alias":
31: mount_accessor = lookup(data.vault_auth_backend.b[each.key], "accessor", null)
|----------------
| data.vault_auth_backend.b is object with 2 attributes
| each.key is "ldap-team-foo"
The given key does not identify an element in this collection value.
Error: Invalid index
on .terraform/modules/vault_dba_entity/main.tf line 31, in resource "vault_identity_entity_alias" "alias":
31: mount_accessor = lookup(data.vault_auth_backend.b[each.key], "accessor", null)
|----------------
| data.vault_auth_backend.b is object with 2 attributes
| each.key is "aws-team-foo"
Your for_each blocks are not the same: in the vault_auth_backend you are using the type of the alias as the key, while in the vault_identity_entity_alias you are using its name. Then you try to look up in the vault_auth_backend using the name, which won't work because that uses type for its key.
Change the vault_auth_backend to use alias.name => alias instead of alias.type => alias.

Getting unsupported attribute error in terraform when using for_each expression

Using for_each expression in Terraform v0.12.6 to dynamically generate inline blocks of vnet subnets (Azure). I have list variable 'subnets' defined, with two subnets 'sub1' and 'sub2' as below
variable "subnets" {
default = [
{
name = "sub1"
prefix = "1.1.1.1/32"
},
{
name = "sub2"
prefix = "2.2.2.2/32"
},
]
}
then iterate over list variable inside "azurerm_virtual_network" block to create dynamic blocks of subnets
dynamic "subnet" {
for_each = [for s in var.subnets : {
name = s.name
prefix = s.prefix
}]
content {
name = subnet.name
address_prefix = subnet.prefix
}
}
}
Getting i.e. first one is Error: Unsupported attribute
on main.tf line 42, in resource "azurerm_virtual_network" "vnet":
42: name = subnet.name
This object does not have an attribute named "name".
The iterator object created for a dynamic block has two attributes:
key: the map key or list index of the current element
value: the value of the current element
In this case, the collection being used for repetition is a list of objects, so subnet.key will be integer indices 0, 1, ... and subnet.value will be the object associated with that index.
To get the result you were looking for, you'll need to access the object attributes on subnet.value instead:
dynamic "subnet" {
for_each = [for s in var.subnets : {
name = s.name
prefix = s.prefix
}]
content {
name = subnet.value.name
address_prefix = subnet.value.prefix
}
}
}
It seems like var.subnets is already compatible with the object structure the content block expects, so it may be possible to simplify this further by accessing it directly:
dynamic "subnet" {
for_each = var.subnets
content {
name = subnet.value.name
address_prefix = subnet.value.prefix
}
}
}
As long as var.subnets is a list of objects already, this should produce an identical result.

Resources