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)