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.
Top comments (0)