DEV Community

Revathi Joshi for AWS Community Builders

Posted on

2

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.

Image of Docusign

Bring your solution into Docusign. Reach over 1.6M customers.

Docusign is now extensible. Overcome challenges with disconnected products and inaccessible data by bringing your solutions into Docusign and publishing to 1.6M customers in the App Center.

Learn more

Top comments (0)

Best Practices for Running  Container WordPress on AWS (ECS, EFS, RDS, ELB) using CDK cover image

Best Practices for Running Container WordPress on AWS (ECS, EFS, RDS, ELB) using CDK

This post discusses the process of migrating a growing WordPress eShop business to AWS using AWS CDK for an easily scalable, high availability architecture. The detailed structure encompasses several pillars: Compute, Storage, Database, Cache, CDN, DNS, Security, and Backup.

Read full post