Revathi Joshi
Posted on October 12, 2023
What is a for_each error?
"Terraform's for_each attribute allows you to create a set of similar resources based on the criteria you define."
"When you need to create a set of similar instances, each assigned to a different security group. Terraform cannot parse aws_security_group..id in this attribute because the splat expression () only interpolates list types, while the for_each attribute is reserved for map types.
A local value can return a map type.
Define the local value in your main.tf file. This converts the list of security groups to a map.
Please visit my GitHub Repository for Terraform articles on various topics being updated on constant basis.
Let’s get started!
Objectives:
1. Login to AWS Management Console
2. Create infrastructure for resources block
3. Under terraform_files resources directory - Create 4 files - main.tf
, variables.tf
, outputs.tf
and terrafprm.tfvars
.
4. Initialize Your Working Directory
5. Fix the for_each Error
6. Deploy Your Resources
Pre-requisites:
- AWS user account with admin access, not a root account.
- Cloud9 IDE with AWS CLI.
Resources Used:
Terraform documentation.
Terraform documentation for AMI.
Troubleshoot Terraform - Correct a for_each error
learn-terraform-troubleshooting
Steps for implementation to this project:
1. Login to AWS Management Console
- Make sure you're in the N. Virginia (us-east-1) region
2. Create infrastructure for resources block
- Let’s create the following organizational structure as shown below.
3. Under terraform_files resources directory - Create 4 files - main.tf
, variables.tf
, outputs.tf
and terrafprm.tfvars
.
1. main.tf
substitute vpc_id with your own VPC
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 4.23"
}
}
required_version = ">= 0.14.9"
}
provider "aws" {
region = var.region
}
data "aws_ami" "linux" {
most_recent = true
owners = ["amazon"]
filter {
name = "name"
values = ["amzn2-ami-hvm-*-x86_64-gp2"]
}
filter {
name = "virtualization-type"
values = ["hvm"]
}
}
resource "aws_instance" "web_app" {
for_each = aws_security_group.*.id
ami = data.aws_ami.linux.id
availability_zone = var.az_1a
instance_type = var.instance_type
vpc_security_group_ids = [each.id]
user_data = <<-EOF
#!/bin/bash
echo "Hello, World" > index.html
nohup busybox httpd -f -p 8080 &
EOF
tags = {
Name = "${var.name}-mywebapp"
}
}
resource "aws_security_group" "sg_ping" {
name = "Allow Ping"
vpc_id = "<DUMMY VALUE>"
}
resource "aws_security_group" "sg_8080" {
name = "Allow 8080"
vpc_id = "<DUMMY VALUE>"
}
resource "aws_security_group_rule" "sg_ping" {
type = "ingress"
from_port = -1
to_port = -1
protocol = "icmp"
security_group_id = aws_security_group.sg_ping.id
source_security_group_id = aws_security_group.sg_8080.id
}
resource "aws_security_group_rule" "sg_8080" {
type = "ingress"
from_port = 8080
to_port = 8080
protocol = "tcp"
security_group_id = aws_security_group.sg_8080.id
source_security_group_id = aws_security_group.sg_ping.id
}
- 2. variables.tf
variable "region" {
description = "region"
}
variable "name" {
description = "Value of the Name tag for the EC2 instance"
}
variable "az_1a" {
description = "availability zone 1"
type = string
default = "us-east-1a"
}
variable "instance_type" {
description = "Value of the Name tag for the EC2 instance type"
type = string
default = "t2.micro"
}
- 3. outputs.tf
output "instance_id" {
description = "ID of the EC2 instance"
value = [for instance in aws_instance.web_app: instance.id]
}
output "instance_public_ip" {
description = "Public IP address of the EC2 instance"
value = [for instance in aws_instance.web_app: instance.public_ip]
}
output "instance_name" {
description = "Tags of the EC2 instance"
value = [for instance in aws_instance.web_app: instance.tags.Name]
}
- 4. terrafprm.tfvars
name = "rev"
region = "us-east-1"
4. Initialize Your Working Directory
cd terraform_files
- Terraform format
terraform fmt
- Initiate the working directory
terraform init
- Validate the configuration
terraform validate
- get a number of errors in the configuration file
5. Fix the for_each Error
Errors
-
in main.tf - Review the details of all the error messages
- 1. Correct the variable interpolation error - On line 33, there is an invalid for_each attribute reference to the aws_security group
- The error is being produced because the * expression in the aws_security_group.*.id value is not supported by the for_each attribute.
Error: Invalid reference
│
│ on main.tf line 33, in resource "aws_instance" "web_app":
│ 33: for_each = aws_security_group.*.id
│
│ A reference to a resource type must be followed by at least one attribute access, specifying the resource name.
-
in main.tf - Review the details of the Invalid "each" attribute error message
- 2. On line 37, an invalid "each" object that is missing the vpc_security_group_ids attribute
- The error is being produced because the [each.id] value is dependent on the for_each attribute
Error: Invalid "each" attribute
│
│ on main.tf line 37, in resource "aws_instance" "web_app":
│ 37: vpc_security_group_ids = [each.id]
│
1. Fixing the errors
- at the bottom of main.tf, declare local variables for the security groups being created in the configuration file:
locals {
security_groups = {
sg_ping = aws_security_group.sg_ping.id,
sg_8080 = aws_security_group.sg_8080.id,
}
}
2. Fixing the errors
On line 33, replace the aws_security_group.*.id value with local.security.groups
replace
for_each = aws_security_group.*.id
- with
for_each = local.security_groups
3. Fixing the errors
On line 37, replace the [each.id] value with [each.value]
replace
vpc_security_group_ids = [each.id]
- with
vpc_security_group_ids = [each.value]
4. Fixing the errors
On line 44, for the tag Name attribute
replace the value
${var.name}-mywebapp
- with
${var.name}-mywebapp-${each.key}
- main.tf looks like this
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 4.23"
}
}
required_version = ">= 0.14.9"
}
provider "aws" {
region = var.region
}
data "aws_ami" "linux" {
most_recent = true
owners = ["amazon"]
filter {
name = "name"
values = ["amzn2-ami-hvm-*-x86_64-gp2"]
}
filter {
name = "virtualization-type"
values = ["hvm"]
}
}
resource "aws_instance" "web_app" {
for_each = local.security_groups
ami = data.aws_ami.linux.id
availability_zone = var.az_1a
instance_type = var.instance_type
vpc_security_group_ids = [each.value]
user_data = <<-EOF
#!/bin/bash
echo "Hello, World" > index.html
nohup busybox httpd -f -p 8080 &
EOF
tags = {
Name = "${var.name}-mywebapp-${each.key}"
}
}
resource "aws_security_group" "sg_ping" {
name = "Allow Ping"
vpc_id = "vpc-0da931f5deb73c9e2"
}
resource "aws_security_group" "sg_8080" {
name = "Allow 8080"
vpc_id = "vpc-0da931f5deb73c9e2"
}
resource "aws_security_group_rule" "sg_ping" {
type = "ingress"
from_port = -1
to_port = -1
protocol = "icmp"
security_group_id = aws_security_group.sg_ping.id
source_security_group_id = aws_security_group.sg_8080.id
}
resource "aws_security_group_rule" "sg_8080" {
type = "ingress"
from_port = 8080
to_port = 8080
protocol = "tcp"
security_group_id = aws_security_group.sg_8080.id
source_security_group_id = aws_security_group.sg_ping.id
}
locals {
security_groups = {
sg_ping = aws_security_group.sg_ping.id,
sg_8080 = aws_security_group.sg_8080.id,
}
}
- Validate the configuration:
terraform validate
- You should receive a success message stating the configuration is valid.
6. Deploy Your Resources
- Create the Terraform plan
terraform plan
- Create the resources
terraform apply
- enter yes to confirm deployment
- EC2 instances - rev-mywebapp-sg_8080 and rev-mywebapp-sg_ping
Cleanup
terraform destroy
What we have done so far
We have successfully fixed the for_each error and deployed our resources.
Posted on October 12, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.