How to fix a Terraform for_each Error
Tags: terraform, ec2
Published: 2023-10-12 21:28:35

# 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](https://github.com/awsmine/Terraform_projects)** 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](https://registry.terraform.io/providers/hashicorp/aws/latest/docs).**
**[Terraform documentation for AMI](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/ami).**
**[Troubleshoot Terraform - Correct a for_each error](https://developer.hashicorp.com/terraform/tutorials/configuration-language/troubleshooting-workflow)**
**[learn-terraform-troubleshooting](https://github.com/hashicorp/learn-terraform-troubleshooting/tree/main)**

# 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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/263mpsoydcfpwqfpol6h.png)





## 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
```


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jzp1pqeshtp1xnufub88.png)




- Initiate the working directory

```
terraform init
```

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/f9phi1nieh1bhiwg1s9j.png)





- Validate the configuration

```
terraform validate
```

- get a number of errors in the configuration file


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/k6wtvabfsbdxka7xl5k8.png)



## 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.


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zcig1audzinjkvd6g9vm.png)

## 6. Deploy Your Resources


- Create the Terraform plan

```
terraform plan
```


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/x68ayv1fz24neugdtz2l.png)


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/obaucc2tav4jmrwcmsyt.png)





- Create the resources

```
terraform apply
```
- enter yes to confirm deployment


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/d8lla6znd92dqozs1r28.png)


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yzhjbv5rusns7ufusssw.png)



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


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/352jylboxi4hp2j2fxza.png)



# Cleanup


```
terraform destroy
```


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xkeulnm8d33azt2zrrgu.png)




# What we have done so far

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