Reputation: 5139
Terraform newbie here.
I'd like to iterate a list using for_each
, but it seems like the key and value are the same:
provider "aws" {
profile = "default"
region = "us-east-1"
}
variable "vpc_cidrs" {
default = ["10.0.0.0/16", "10.1.0.0/16"]
}
resource "aws_vpc" "vpc" {
for_each = toset(var.vpc_cidrs)
cidr_block = each.value
enable_dns_hostnames = true
tags = { Name = "Company0${each.key}" }
}
I'd like the tag Name to be "Name" = "Company01"
and "Name" = "Company02"
but according to terraform apply
, I get:
"Name" = "Company010.0.0.0/16"
and "Name" = "Company010.1.0.0/16"
What am I missing?
Upvotes: 37
Views: 115955
Reputation: 874
In foreach you can use something like this:
provider "aws" {
profile = "default"
region = "us-east-1"
}
variable "vpc_cidrs" {
default = ["10.0.0.0/16", "10.1.0.0/16"]
}
resource "aws_vpc" "vpc" {
for_each = toset(var.vpc_cidrs)
cidr_block = each.value
enable_dns_hostnames = true
tags = { Name = "Company0${index(keys(var.vpc_cidrs), each.key)}" }
}
You get the keys with:
keys(var.vpc_cidrs)
and after that you find the key in the keys with:
index(keys(var.vpc_cidrs), each.key)
I am using this for index and priority as well e.g.:
resource "azurerm_network_security_rule" "rules" {
for_each = var.nw_rules
name = each.value["name"]
priority = 1100 + index(keys(var.nw_rules), each.key)
direction = "Inbound"
access = "Allow"
protocol = each.value["protocol"]
source_port_range = each.value["source_port_range"]
destination_port_range = each.value["destination_port_range"]
...
}
Upvotes: 0
Reputation: 22088
One of the comments was asking about how to achieve this use an index when using for_each
inside a dynamic block.
This can be done by refering to <name-of-dynamic-block>.key
(key is the index).
I'll take a use case where I have one S3 bucket that have multiple folders inside it, and each folder has a dedicated cloudfront distribution pointing to it.
Let's say I have 2 folders inside the s3 bucket: name1 and name2.
So I'll have to create an aws_s3_bucket_policy
that looks like this:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "",
"Effect": "Allow",
"Principal": {
"AWS": "<arn-of-cloudfront-distribution-1>"
},
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::some-bucket/name1/*"
},
{
"Sid": "",
"Effect": "Allow",
"Principal": {
"AWS": "<arn-of-cloudfront-distribution-2>"
},
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::some-bucket/name2/*"
}
]
}
(*) The creation of the cloudfront distributions was removed for brevity.
So I have a list of folders that needs to be created inside my s3 bucket:
variable "all_s3_folders" {
type = list(string)
default = []
description = "All folders inside the bucket"
}
For each folder I created a cloudfront distribution (removed for brevity) and an cloudfront origin access identity (see oai) resources.
We will create those resources based on a count
on the number of folders:
resource "aws_cloudfront_origin_access_identity" "oai" {
count = length(var.all_s3_folders)
comment = "some-comment"
}
Now we need to add each folder (+ its relevant cloudfront distrobution and oai) as an entry in the bucket policy.
I want to use dynamic block with for_each
inside it but without relying on count.index
.
But I'll still have to iterate over the aws_cloudfront_origin_access_identity
resources with an index.
So,
In order to use an index when using for_each
inside a dynamic block you just refer to <name-of-dynamic-block>.key
- here it's statement.key
data "aws_iam_policy_document" "my-doc" {
dynamic "statement" {
for_each = var.folders_array
content {
effect = "Allow"
actions = [
"s3:GetObject"
]
resources = [
"${aws_s3_bucket.my_s3_bucket.arn}/${statement.value}/*" # <--Using statement value
]
principals {
type = "AWS"
identifiers = [some_cloudfront_resource[statement.key].id] # <--Using statement key
}
}
}
}
Upvotes: -1
Reputation: 521
You could use the count
function and use the index to do this:
provider "aws" {
profile = "default"
region = "us-east-1"
}
variable "vpc_cidrs" {
type = list(string)
default = ["10.0.0.0/16", "10.1.0.0/16"]
}
resource "aws_vpc" "vpc" {
count = length(var.vpc_cidrs)
cidr_block = var.vpc_cidrs[count.index]
enable_dns_hostnames = true
tags = { Name = "Company0${count.index}" }
}
Upvotes: 5
Reputation: 3117
There is also another way of achieving the wanted result without using index()
:
change the following three lines:
for_each = { for idx, cidr_block in var.vpc_cidrs: cidr_block => idx}
cidr_block = each.key
tags = { Name = format("Company%02d", each.value + 1) }
for_each
will use a map of cidr_block
to index
mapping as returned by for
cidr_block
can then just use the each.key
valuetags
also use format()
on each.value
to have a two digit with leading zeros of the index
Full example will be:
provider "aws" {
profile = "default"
region = "us-east-1"
}
variable "vpc_cidrs" {
default = ["10.0.0.0/16", "10.1.0.0/16"]
}
resource "aws_vpc" "vpc" {
for_each = { for idx, cidr_block in var.vpc_cidrs: cidr_block => idx}
cidr_block = each.key
enable_dns_hostnames = true
tags = { Name = format("Company%02d", each.value + 1) }
}
Upvotes: 39
Reputation: 5139
Found an easy solution using the index function:
tags = { Name = "Company0${index(var.vpc_cidrs, each.value) + 1}" }
Upvotes: 53
Reputation: 3811
When for_each
is used with a set, each.key
and each.value
are the same.
To generate strings like "Company01", "Company02", etc., you need the index of each CIDR block in the list. One way to do this is to create a local map using a for
expression like:
locals {
vpc_cidrs = {for s in var.vpc_cidrs: index(var.vpc_cidrs, s) => s}
}
resource "aws_vpc" "vpc" {
for_each = local.vpc_cidrs
cidr_block = each.value
enable_dns_hostnames = true
tags = { Name = "Company0${each.key}" }
}
As a bonus, you can use the format function to build the zero-padded name string:
resource "aws_vpc" "vpc" {
...
tags = { Name = format("Company%02d", each.key) }
}
Upvotes: 15