Saturday, 28 June 2025

Terraform + Terratest

 ✅ Folder Structure 

terraform-project/ 
├── modules/ 
│   ├── alb/ 
│   │   ├── main.tf 
│   │   ├── variables.tf 
│   │   └── outputs.tf 
│   ├── ec2/ 
│   │   ├── main.tf 
│   │   ├── variables.tf 
│   │   └── outputs.tf 
│   └── s3/ 
│       ├── main.tf 
│       ├── variables.tf 
│       └── outputs.tf 
├── environments/ 
│   └── dev/ 
│       ├── main.tf 
│       ├── variables.tf 
│       └── outputs.tf 
└── test/ 
   └── terratest_s3_ec2_alb.go  (Already done) 
 

 

📦 modules/s3/main.tf 

resource "aws_s3_bucket" "main" { 
 count  = var.create_bucket ? 1 : 0 
 bucket = var.bucket_name 
 
 dynamic "versioning" { 
   for_each = var.enable_versioning ? [1] : [] 
   content { 
     enabled = true 
   } 
 } 
 
 tags = merge({ 
   Environment = var.environment 
 }, var.additional_tags) 
} 
 

variables.tf 

variable "bucket_name" {} 
variable "environment" {} 
variable "create_bucket" { 
 type    = bool 
 default = true 
} 
variable "enable_versioning" { 
 type    = bool 
 default = false 
} 
variable "additional_tags" { 
 type    = map(string) 
 default = {} 
} 
 

outputs.tf 

output "bucket_name" { 
 value = aws_s3_bucket.main[0].bucket 
} 
 

 

⚙️ modules/ec2/main.tf 

data "aws_subnet" "selected" { 
 filter { 
   name   = "tag:Environment" 
   values = [var.environment] 
 } 
 
 filter { 
   name   = "vpc-id" 
   values = [var.vpc_id] 
 } 
} 
 
data "aws_ami" "amazon_linux" { 
 most_recent = true 
 owners      = ["amazon"] 
 filter { 
   name   = "name" 
   values = ["amzn2-ami-hvm-*-x86_64-gp2"] 
 } 
} 
 
resource "aws_instance" "this" { 
 count                    = var.instance_count 
 ami                      = data.aws_ami.amazon_linux.id 
 instance_type            = var.instance_type 
 subnet_id                = data.aws_subnet.selected.id 
 vpc_security_group_ids   = [var.security_group_id] 
 
 dynamic "ebs_block_device" { 
   for_each = var.ebs_block_devices 
   content { 
     device_name = ebs_block_device.value.device_name 
     volume_size = ebs_block_device.value.volume_size 
     volume_type = ebs_block_device.value.volume_type 
   } 
 } 
 
 tags = { 
   Name        = "${var.environment}-ec2-${count.index}" 
   Environment = var.environment 
 } 
} 
 

variables.tf 

variable "environment" {} 
variable "vpc_id" {} 
variable "instance_count" {} 
variable "instance_type" {} 
variable "security_group_id" {} 
 
variable "ebs_block_devices" { 
 type = map(object({ 
   device_name = string 
   volume_size = number 
   volume_type = string 
 })) 
 default = {} 
} 
 

outputs.tf 

output "instance_ids" { 
 value = aws_instance.this[*].id 
} 
 

 

🌐 modules/alb/main.tf 

hcl 

CopyEdit 

data "aws_vpc" "selected" { 
 filter { 
   name   = "tag:Environment" 
   values = [var.environment] 
 } 
} 
 
data "aws_subnets" "selected" { 
 filter { 
   name   = "tag:Environment" 
   values = [var.environment] 
 } 
 
 filter { 
   name   = "vpc-id" 
   values = [data.aws_vpc.selected.id] 
 } 
} 
 
resource "aws_lb" "this" { 
 name               = "${var.environment}-alb" 
 internal           = false 
 load_balancer_type = "application" 
 subnets            = data.aws_subnets.selected.ids 
 security_groups    = [var.security_group_id] 
 
 tags = { 
   Environment = var.environment 
 } 
} 
 
resource "aws_lb_listener" "http" { 
 load_balancer_arn = aws_lb.this.arn 
 port              = 80 
 protocol          = "HTTP" 
 
 default_action { 
   type = "fixed-response" 
   fixed_response { 
     content_type = "text/plain" 
     message_body = "Service running" 
     status_code  = "200" 
   } 
 } 
} 
 
resource "aws_lb_target_group" "app" { 
 for_each = { for idx, v in var.target_ports : idx => v } 
 
 name     = "${var.environment}-tg-${each.key}" 
 port     = each.value 
 protocol = "HTTP" 
 vpc_id   = data.aws_vpc.selected.id 
 
 health_check { 
   healthy_threshold   = 2 
   unhealthy_threshold = 2 
   timeout             = 3 
   interval            = 30 
   path                = "/" 
 } 
} 
 

variables.tf 

variable "security_group_id" {} 
variable "environment" {} 
variable "vpc_name" {} 
variable "target_ports" { 
 type = list(number) 
} 
 

outputs.tf 

output "alb_dns_name" { 
 value = aws_lb.this.dns_name 
} 
 
output "target_group_arns" { 
 value = [for tg in aws_lb_target_group.app : tg.arn] 
} 
 

 

🧪 environments/dev/main.tf 

module "s3" { 
 source             = "../../modules/s3" 
 bucket_name        = "myapp-dev-bucket" 
 environment        = "dev" 
 create_bucket      = true 
 enable_versioning  = true 
 additional_tags    = { 
   team  = "infra" 
   owner = "devops" 
 } 
} 
 
module "alb" { 
 source            = "../../modules/alb" 
 environment       = "dev" 
 security_group_id = var.security_group_id 
 vpc_name          = var.vpc_name 
 target_ports      = [80, 8080] 
} 
 
module "ec2" { 
 source            = "../../modules/ec2" 
 environment       = "dev" 
 vpc_id            = module.alb.vpc_id // Or provide directly 
 instance_type     = "t3.micro" 
 instance_count    = 1 
 security_group_id = var.security_group_id 
 ebs_block_devices = { 
   vol1 = { 
     device_name = "/dev/sdf" 
     volume_size = 20 
     volume_type = "gp2" 
   } 
 } 
} 
 

variables.tf 

variable "security_group_id" {} 
variable "vpc_name" {} 
 

outputs.tf 

output "s3_bucket_name" { 
 value = module.s3.bucket_name 
} 
 
output "ec2_instance_ids" { 
 value = module.ec2.instance_ids 
} 
 
output "alb_dns_name" { 
 value = module.alb.alb_dns_name 
} 
 
output "target_group_arns" { 
 value = module.alb.target_group_arns 
} 
 
output "aws_region" { 
 value = "us-east-1" 
} 

 

 

test/terratest_s3_ec2_alb.go 

go 

package test 
 
import ( 
 "fmt" 
 "strings" 
 "testing" 
 
 "github.com/gruntwork-io/terratest/modules/aws" 
 "github.com/gruntwork-io/terratest/modules/http-helper" 
 "github.com/gruntwork-io/terratest/modules/terraform" 
 "github.com/stretchr/testify/assert" 
) 
 
func TestTerraformS3EC2ALB(t *testing.T) { 

 t.Parallel() 
 
 terraformOptions := &terraform.Options{ 
   TerraformDir: "../environments/dev", 
   Vars: map[string]interface{}{ 
     "vpc_name":          "dev-vpc", 
     "security_group_id": "sg-xxxxxx", // Replace with valid security group ID 
   }, 
 } 
 
 // Ensure cleanup at the end 
 defer terraform.Destroy(t, terraformOptions) 
 
 // Initialize and apply Terraform code 
 terraform.InitAndApply(t, terraformOptions) 
 
 // ----- S3 Tests ----- 
 bucketName := terraform.Output(t, terraformOptions, "s3_bucket_name") 
 region := terraform.Output(t, terraformOptions, "aws_region") 
 
 // Check if versioning is enabled via dynamic block 
 versioningStatus := aws.GetS3BucketVersioning(t, bucketName, region) 
 assert.Equal(t, "Enabled", versioningStatus, "S3 versioning should be enabled") 
 
 // Check tags correctness 
 tags := aws.GetS3BucketTags(t, region, bucketName) 
 assert.Equal(t, "infra", tags["team"]) 
 assert.Equal(t, "devops", tags["owner"]) 
 assert.Equal(t, "dev", tags["Environment"]) 
 
 // ----- EC2 Tests ----- 
 instanceIDs := terraform.OutputList(t, terraformOptions, "ec2_instance_ids") 
 assert.Greater(t, len(instanceIDs), 0, "EC2 instances should be created") 
 
 for _, id := range instanceIDs { 
   instance := aws.GetInstanceDetails(t, region, id) 
   assert.Equal(t, "t3.micro", instance.InstanceType) 
   assert.True(t, strings.Contains(instance.Tags["Name"], "dev-ec2")) 
 
   // Validate dynamic EBS volumes attached 
   volumes := aws.GetEbsVolumesOfInstance(t, id, region) 
   assert.GreaterOrEqual(t, len(volumes), 1, "At least one EBS volume should be attached dynamically") 
   for _, vol := range volumes { 
     assert.Equal(t, int64(20), *vol.Size) 
     assert.Equal(t, "gp2", *vol.VolumeType) 
   } 
 } 
 
 // ----- ALB Tests ----- 
 albDNS := terraform.Output(t, terraformOptions, "alb_dns_name") 
 assert.Contains(t, albDNS, "elb.amazonaws.com") 
 
 targetGroupARNs := terraform.OutputList(t, terraformOptions, "target_group_arns") 
 assert.Greater(t, len(targetGroupARNs), 0, "At least one target group should be created") 
 
 albURL := fmt.Sprintf("http://%s", albDNS) 
 statusCode, body := http_helper.HttpGet(t, albURL, nil) 
 assert.Equal(t, 200, statusCode, "ALB should return HTTP 200") 
 assert.Contains(t, body, "Service running", "ALB fixed-response body should match") 
} 
 

 

How to run tests 

  1. Replace "sg-xxxxxx" in the test with your actual security group ID. 

  1. Run tests from the test folder with: 

go test -v 

 

No comments:

Post a Comment