Saturday, 28 June 2025

Terratest

 


1. Package and Imports

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" )
  • package test: Defines this file as a test package.

  • Imports standard Go packages like fmt, strings, and testing.

  • Imports Terratest modules:

    • aws: AWS helper functions (to inspect AWS resources like S3 buckets, EC2, etc.).

    • http-helper: For making HTTP requests and validating HTTP responses.

    • terraform: For running Terraform commands (init, apply, destroy).

  • Imports assert from stretchr/testify for easy test assertions.


2. Test Function Declaration

go

func TestTerraformS3EC2ALB(t *testing.T) { t.Parallel()
  • TestTerraformS3EC2ALB is the test entry point (recognized by Go testing).

  • t *testing.T: Provides the testing framework's handle.

  • t.Parallel() allows tests to run concurrently with others for faster testing.


3. Terraform Options Setup

go

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 }, }
  • This configures how Terratest will run Terraform.

  • TerraformDir: The folder containing your Terraform code (your dev environment).

  • Vars: Passes variables to Terraform (e.g., VPC name and Security Group ID).

  • You must replace "sg-xxxxxx" with your actual security group.


4. Clean Up with defer terraform.Destroy

go

defer terraform.Destroy(t, terraformOptions)
  • Ensures that after the test finishes (pass/fail), Terraform destroys all resources created.

  • This avoids resource leaks and unwanted costs.


5. Run terraform init and apply

go

terraform.InitAndApply(t, terraformOptions)
  • Initializes the Terraform working directory.

  • Applies the Terraform configuration (creates resources).

  • If this fails, the test fails here.


6. S3 Bucket Tests

go

bucketName := terraform.Output(t, terraformOptions, "s3_bucket_name") region := terraform.Output(t, terraformOptions, "aws_region") versioningStatus := aws.GetS3BucketVersioning(t, bucketName, region) assert.Equal(t, "Enabled", versioningStatus, "S3 versioning should be enabled") tags := aws.GetS3BucketTags(t, region, bucketName) assert.Equal(t, "infra", tags["team"]) assert.Equal(t, "devops", tags["owner"]) assert.Equal(t, "dev", tags["Environment"])
  • Retrieves the S3 bucket name and AWS region from Terraform outputs.

  • Uses Terratest AWS helpers to:

    • Check if versioning is enabled (validates your dynamic block worked).

    • Retrieve the tags on the bucket, then asserts they match expected values (team, owner, Environment).

  • If any assertion fails, the test fails with a descriptive message.


7. EC2 Instance Tests

go

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")) 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) } }
  • Retrieves all EC2 instance IDs from Terraform outputs.

  • Checks that at least one EC2 instance was created.

  • For each instance:

    • Verifies the instance type is t3.micro.

    • Checks the Name tag contains "dev-ec2".

  • Checks EBS volumes attached dynamically:

    • Ensures at least one volume is attached (your dynamic EBS block test).

    • Validates volume size (20 GiB) and type (gp2).

  • These assertions confirm both static and dynamic block configurations in Terraform worked.


8. Application Load Balancer (ALB) Tests

go

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")
  • Gets ALB DNS name and confirms it includes the AWS ELB domain.

  • Checks at least one target group was created dynamically.

  • Constructs an HTTP URL from the ALB DNS name.

  • Sends an HTTP GET request to the ALB.

  • Asserts the HTTP response code is 200 OK.

  • Asserts the response body contains "Service running" — matching your fixed-response listener in Terraform.

  • Validates ALB works and serves traffic as expected.


Summary

This test automates end-to-end verification of your Terraform project:

  • Runs Terraform to create infrastructure.

  • Validates that all dynamic features (versioning, EBS volumes, target groups) are correctly provisioned.

  • Confirms tags and configurations.

  • Validates live infrastructure via AWS SDK calls and HTTP requests.

  • Cleans up resources after testing.



In short:

  • Terratest triggers Terraform to create resources.

  • It validates those Terraform-created resources using AWS SDK calls, HTTP requests, etc.

  • Finally, it destroys the resources with terraform destroy (if you use defer terraform.Destroy()), cleaning up after testing.

Example Flow

Terratest run ->

  terraform init & apply (creates resources) ->

  AWS SDK & HTTP API calls to validate resources ->

  terraform destroy (cleans up resources)



Q1: Will Terratest create and delete resources every time?

  • Terratest calls Terraform apply and destroy by default in tests.

  • If your Terratest uses defer terraform.Destroy(), it will destroy all resources defined in that Terraform code at the end of the test.

  • So if you run Terratest against your real infrastructure, it WILL delete those resources when the test finishes — this is risky in production or shared environments.

Q2: How to test updates (like tags or new properties) without deleting your existing resources?

Here are two approaches:


Approach 1: Use a separate test environment (recommended)

  • Create a dedicated test AWS account or separate environment.

  • Your Terratest runs Terraform on this clean, isolated environment.

  • After test finishes, Terratest destroys all created resources — no risk to production or existing infra.

  • This is how Terratest is meant to be used — test infrastructure code in disposable environments.


Approach 2: Run Terratest without destroy & manually test updates

  • You can skip terraform.Destroy() in Terratest so it does not delete resources after test.

  • Run terraform apply via Terratest — it updates existing resources according to your new code (e.g., adds tags, new S3 settings).

  • Then Terratest runs tests/assertions to verify updates are applied.

  • But this means resources persist, so you should be careful not to run these tests in production accidentally.


Summary of your exact case:

StepBehaviorWhat to do
Initial Terraform runCreates 2 EC2 + 1 S3Run Terraform normally
Update codeAdd tag to EC2, new S3 propertyRun terraform apply to update resources
Test with Terratestterraform apply runs, updates resources; terraform destroy deletes them if calledRemove destroy call or run tests in test environment

No comments:

Post a Comment