DEV Community

Revathi Joshi for AWS Community Builders

Posted on

How to fix a Terraform for_each Error

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.

Image description

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 } 
Enter fullscreen mode Exit fullscreen mode
  • 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" } 
Enter fullscreen mode Exit fullscreen mode
  • 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] } 
Enter fullscreen mode Exit fullscreen mode
  • 4. terrafprm.tfvars
 name = "rev" region = "us-east-1" 
Enter fullscreen mode Exit fullscreen mode

4. Initialize Your Working Directory

 cd terraform_files 
Enter fullscreen mode Exit fullscreen mode
  • Terraform format
 terraform fmt 
Enter fullscreen mode Exit fullscreen mode

Image description

  • Initiate the working directory
 terraform init 
Enter fullscreen mode Exit fullscreen mode

Image description

  • Validate the configuration
 terraform validate 
Enter fullscreen mode Exit fullscreen mode
  • get a number of errors in the configuration file

Image description

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. 
Enter fullscreen mode Exit fullscreen mode
  • 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] │ 
Enter fullscreen mode Exit fullscreen mode

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, } } 
Enter fullscreen mode Exit fullscreen mode

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 
Enter fullscreen mode Exit fullscreen mode
  • with
 for_each = local.security_groups 
Enter fullscreen mode Exit fullscreen mode

3. Fixing the errors

  • On line 37, replace the [each.id] value with [each.value]

  • replace

 vpc_security_group_ids = [each.id] 
Enter fullscreen mode Exit fullscreen mode
  • with
 vpc_security_group_ids = [each.value] 
Enter fullscreen mode Exit fullscreen mode

4. Fixing the errors

  • On line 44, for the tag Name attribute

  • replace the value

 ${var.name}-mywebapp 
Enter fullscreen mode Exit fullscreen mode
  • with
 ${var.name}-mywebapp-${each.key} 
Enter fullscreen mode Exit fullscreen mode
  • 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, } } 
Enter fullscreen mode Exit fullscreen mode
  • Validate the configuration:
 terraform validate 
Enter fullscreen mode Exit fullscreen mode
  • You should receive a success message stating the configuration is valid.

Image description

6. Deploy Your Resources

  • Create the Terraform plan
 terraform plan 
Enter fullscreen mode Exit fullscreen mode

Image description

Image description

  • Create the resources
 terraform apply 
Enter fullscreen mode Exit fullscreen mode
  • enter yes to confirm deployment

Image description

Image description

  • EC2 instances - rev-mywebapp-sg_8080 and rev-mywebapp-sg_ping

Image description

Cleanup

 terraform destroy 
Enter fullscreen mode Exit fullscreen mode

Image description

What we have done so far

We have successfully fixed the for_each error and deployed our resources.

Top comments (0)