Arun Shah

Creating Reusable and

Testable Infrastructure with Terraform Modules

Introduction

Terraform has become the industry standard for provisioning and managing infrastructure as code (IaC). While it’s easy to get started with writing Terraform configurations, building infrastructure that is scalable, maintainable, and robust requires a more disciplined approach. This is where Terraform modules come in.

This post will take a deep dive into creating reusable and testable Terraform modules. We will explore the principles of module design, best practices for creating a clean module structure, and how to implement automated testing to ensure your modules are reliable and production-ready.

Why Use Terraform Modules?

Terraform modules are self-contained packages of Terraform configurations that are managed as a group. They are the building blocks of Terraform and provide a number of benefits:

Designing a Reusable Module

A well-designed module is easy to understand, use, and extend. Here are some best practices for designing reusable modules:

Module Structure

A typical Terraform module has the following structure:

.
├── main.tf
├── variables.tf
├── outputs.tf
└── README.md

Example: A Reusable AWS S3 Bucket Module

Let’s create a reusable module for creating an AWS S3 bucket.

main.tf

resource "aws_s3_bucket" "this" {
  bucket = var.bucket_name
  acl    = var.acl

  versioning {
    enabled = var.versioning_enabled
  }

  tags = var.tags
}

variables.tf

variable "bucket_name" {
  description = "The name of the S3 bucket."
  type        = string
}

variable "acl" {
  description = "The canned ACL to apply to the bucket."
  type        = string
  default     = "private"
}

variable "versioning_enabled" {
  description = "A boolean that indicates if versioning should be enabled for the bucket."
  type        = bool
  default     = true
}

variable "tags" {
  description = "A map of tags to assign to the bucket."
  type        = map(string)
  default     = {}
}

outputs.tf

output "bucket_id" {
  description = "The name of the bucket."
  value       = aws_s3_bucket.this.id
}

output "bucket_arn" {
  description = "The ARN of the bucket."
  value       = aws_s3_bucket.this.arn
}

Testing Your Terraform Modules

Testing is a critical part of building reliable infrastructure. There are several tools available for testing Terraform modules, including:

Using Terratest for Integration Testing

Terratest is a popular choice for testing Terraform modules. It allows you to write integration tests in Go that provision real infrastructure and then verify that it is configured correctly.

Here is an example of a Terratest test for our S3 bucket module:

package test

import (
	"testing"

	"github.com/gruntwork-io/terratest/modules/aws"
	"github.com/gruntwork-io/terratest/modules/terraform"
	"github.com/stretchr/testify/assert"
)

func TestTerraformAwsS3Example(t *testing.T) {
	t.Parallel()

	// Specify the region where the S3 bucket will be created
	awsRegion := "us-west-2"

	// Construct the terraform options with default retryable errors to handle the most common
	// retryable errors in terraform testing.
	terraformOptions := terraform.WithDefaultRetryableErrors(t, &terraform.Options{
		// The path to where our Terraform code is located
		TerraformDir: "./",

		// Variables to pass to our Terraform code using -var options
		Vars: map[string]interface{}{
			"bucket_name": "terratest-s3-bucket-example",
		},

		// Environment variables to set when running Terraform
		EnvVars: map[string]string{
			"AWS_DEFAULT_REGION": awsRegion,
		},
	})

	// At the end of the test, run `terraform destroy` to clean up any resources that were created
	defer terraform.Destroy(t, terraformOptions)

	// This will run `terraform init` and `terraform apply` and fail the test if there are any errors
	terraform.InitAndApply(t, terraformOptions)

	// Run `terraform output` to get the value of an output variable
	bucketID := terraform.Output(t, terraformOptions, "bucket_id")

	// Verify that the S3 bucket exists
	aws.AssertS3BucketExists(t, awsRegion, bucketID)
}

Conclusion

Creating reusable and testable Terraform modules is a critical part of building scalable, maintainable, and robust infrastructure as code. By following the best practices outlined in this post, you can create modules that are easy to use, extend, and test.

Further Reading

Comments