DEV Community

Revathi Joshi for AWS Community Builders

Posted on

How to fix a Terraform Cycle Error

What are Cycle errors?

Cycle errors are instances of circular logic in the Terraform dependency tree. Terraform analyzes the dependencies between resources in your infrastructure configuration to determine the order to perform your operations.

In this article, I am going to show you how Cycle error occurs when aws_security_group resources reference one another in their security_groups attributes. AWS cannot create the security groups because their configurations each reference the other group, which would not exist yet.

How to fix the Cycle error?

  • The fix for this Cycle error is to remove the mutually dependent security group rules in your configuration, leaving the two group resources without ingress attributes.
  • Instead of including the rules in the aws_security_group configuration, use the aws_security_group_rule resource and reference the security group IDs instead.
  • This avoids a cycle error because the provider will have AWS create both of the aws_security_group resources first, without interdependent rules.
  • It will create the rules next, and attach the rules to the groups last.

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 Cycle 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 cycle 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               = 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 = "<DUMMY VALUE>"

  ingress {
    from_port       = -1
    to_port         = -1
    protocol        = "icmp"
    security_groups = [aws_security_group.sg_8080.id]
  }
}

 resource "aws_security_group" "sg_8080" {
   name = "Allow 8080"
   vpc_id = "<DUMMY VALUE>"

  ingress {
    from_port       = 8080
    to_port         = 8080
    protocol        = "tcp"
    security_groups = [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
  • 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 cycle error for the security groups sg_8080 and sg_ping.

Image description

  • This cycle error you get due to the security groups co-dependent on each other, expecting the other to be already created and must both exist at the same time for successful creation.

5. Fix the Cycle Error

  • in main.tf - delete the ingress sections from the sg_ping and sg_8080 resource blocks.

  • Beneath the resource block of sg_ping, create a separate security group rule of type ingress named allow_ping

resource "aws_security_group_rule" "allow_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
}
Enter fullscreen mode Exit fullscreen mode
  • Beneath the resource block of sg_8080, Create another security group rule of type ingress named allow_8080
resource "aws_security_group_rule" "allow_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
  • 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_rule" "allow_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" "sg_8080" {
  name   = "Allow 8080"
  vpc_id = "vpc-0da931f5deb73c9e2"
}

resource "aws_security_group_rule" "allow_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

  • 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 cycle error and deployed our resources.

Top comments (0)