Month: November 2021

Git Tagging (Note)

This post is more of a note for me how to clone a specific tag from git repo branch and the commit/push the changes back. Also I’m adding a new tag for my changes.

Tags are nothing but checkpoints in a branch.

git clone <git repo url>
git checkout tags/v1.0.4 -b <Branch Name> //v1.0.4 is the latest tag on the branch
git describe --tags    //This command will help us to confirm the tag

Now you can make all your changes to the code and once you are done,

git add <Files Updated>
git commit -m "<Summary of changes made>"
git tag -a v1.0.5 -m "<Overall Change Summary>" //Here we have added a new tag for our changes
git push --tags origin <Branch Name>

Unit testing Terraform code using Terratest

Unit testing of Terraform or an IaC tool is breaking the infrastructure code into small modules and testing them individually. But most of the infrastructure code deals with outside entities (cloud resources) so the testing steps would be,

  • Deploy the infrastructure
  • Validate it works (Via HTTP/API/SSH calls or commands)
  • Undeploy/Destroy the infrastructure.

Terratest is a go library that provides patterns and helper functions for testing the infrastructure, with support for Terraform, Packer, AWS and more. Here we are unit testing EC2, ELB &  ASG modules using Terratest. 

Benefits of Terratest:

  • Terratest is written in go language which has strong community and developer support. Terraform and Kubernetes are also written in go, this helps the transition easier and offers better integration.
  • It supports a wide range of cloud platforms, including AWS , Azure, GCP, as well as Kubernetes, Packer machine images, and Docker images.

Terratest framework is written in golang and has the following directory structure. All the files should be inside $GOPATH\src folder.

Folder Structure

examples  – All the Terraform code is saved in this folder. You can create sub folders for each project.

test – this folder contains all the Terratest files which we will use for testing our modules

Before we test our modules using Terratest we will explain the format of a simple test file. The basic structure/code to run a Terratest file is given below. Lets create a go file terraform_test.go which will contain our test cases.

First, we need to create a package and lets name it test.

package test

Now import all the necessary golang libraries that we need for our testing.  This can be done by creating an import block and then adding all the libraries we need.

package test
 
import (
    "testing"
    "github.com/gruntwork-io/terratest/modules/terraform"
    "github.com/stretchr/testify/assert"
)

Add a new function to the code. This function can be named anything but for our continence we name it as TestEC2. The testing library is used by calling it inside the newly created function.

package test
 
import (
    "testing"
    "github.com/gruntwork-io/terratest/modules/terraform"
    "github.com/stretchr/testify/assert"
)
 
func TestEC2 (t *testing.T) {
}

Now we can add our tests to the file. You can also see two other statements in the file which initialize and apply the changes also destroy the created infrastructure. Using “defer” runs the command at the end of the test, whether the test succeeds or fails.

package test
 
import (
    "testing"
    "github.com/gruntwork-io/terratest/modules/terraform"
    "github.com/stretchr/testify/assert"
)
 
func  TestEC2 (t *testing.T) {
    terraformOptions := &terraform.Options {
 
    }
     // 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)
}

Now we will test our modules using Terratest. The test will create an EC2 instance, return the instance Id and and if the instance id is valid then mark the test as PASSED. Finally it will destroy the infrastructure it created.

Terraform code : This code will create an EC2 instance with Linux AMI using the EC2 module. We also have an output parameters which return instance id and tags.

module "alb_label" {
    source  = "<module url>"
    version = "1.1.0"
 
    name            = var.app_name
    environment     = var.app_environment
    business_unit   = var.app_business_unit
    department      = var.department
    mandatory_tags  = var.mandatory_tags
    attributes      = var.attributes
    additional_tags = var.additional_tags
}
 
module "ec2" {
    source      = "<module url>"
    version     = "2.2.0"
    # EC2 Details
    ami_id                  = var.ami_id
    instance_type           = var.instance_type
    key_name                = var.key_name
    vpc_security_group_ids  = var.security_groups
    iam_instance_profile    = var.iam_instance_profile
    subnet_id               = var.subnet_id
    instance_count          = var.instance_count
    tags                    = module.alb_label.tags
    use_num_suffix          = var.use_num_suffix
}
 
output "instance_id" {
    description     = "ID of the EC2 instance"
    value           = module.ec2.id
}
 
output "instance_tags" {
description = "Tags of the EC2 instance"
value = module.ec2.tags
}

Terratest code: In this code we mention the path of the Terraform files and then apply init and plan functions followed by apply. Once the instance is created, we check the instance id and the test is passed if it is valid.

Once the test is complete the resources are destroyed by Terratest.

package test
 
import (
    "testing"
    "github.com/gruntwork-io/terratest/modules/terraform"
    "github.com/stretchr/testify/assert"
)
 
func TestEC2 (t *testing.T) {
    t.Parallel()
 
    terraformOptions := terraform.WithDefaultRetryableErrors(t, &terraform.Options{
    // The path to where our Terraform code is located
    TerraformDir: "../examples/ec2",
    })
 
    defer terraform.Destroy(t, terraformOptions)
    terraform.InitAndApply(t, terraformOptions)
 
    instanceID := terraform.Output(t, terraformOptions, "instance_id")
    assert.NotNil(t, instanceID)
}

Below are the results of our Terratest module. We ran the test in verbose mode so maximum information is displayed. It clearly lists the step-by-step process followed by the testing tool.

You can see the instance ids and tags values listed as per the output parameters. I had to truncate most of the output to remove sensitive information.

PS > go test -v -timeout 30m
=== RUN TestEC2
=== PAUSE TestEC2
=== CONT TestEC2
TestEC2 2021-07-12T12:32:18-04:00 retry.go:91: terraform [init -upgrade=false]
TestEC2 2021-07-12T12:32:18-04:00 logger.go:66: Running command terraform with args [init -upgrade=false]
TestEC2 2021-07-12T12:32:19-04:00 logger.go:66: ?[0m?[1mInitializing modules...?[0m
......
......
......
......
TestEC2 2021-07-12T12:34:28-04:00 logger.go:66: Destroy complete! Resources: 1 destroyed.?[0m
--- PASS: TestEC2 (130.78s)
PASS
ok test 131.936s