CICD and IaC - Test terraform with terratest in Docker - Ep.3
How to test terraform using Golang and Terratest
Test terraform
In this third episode of the miniseries CICD and IaC we are going to see how to test the artifact we created in the previous video.
In the previous episodes
In previous episodes (link to the playlist) I showed you how to externalise the terraform configuration using the yamldecode function in terraform and how to create artifacts for terraform code using Docker.
Today
we are going to start from where we left, and I am going to create a simple test for our module. We want to to test our terraform code and our docker artifact. So, I will create a test and I will dockerise that too.
Ok let’s move to code (https://github.com/outofdevops/cicd-iac):
cicd-iac
├── configuration/
├── dockerisation/
└── testing/
│ ├── test/
│ │ ├── gcs_test.go
│ │ ├── go.mod
│ │ └── go.sum
│ ├── modules/
│ ├── Dockerfile
│ ├── main.tf
│ ├── providers.tf
│ └── terraform.rc
├── .gitignore
└── README.md
Now I have here a third folder testing
, this is going to contain the same code from the previous videos. In addition you may have noticed that it also contains a test
folder.
The Test file
This folder contains our test files, let’s look at them:
package test
import (
"context"
"os"
"testing"
"cloud.google.com/go/storage"
"github.com/gruntwork-io/terratest/modules/terraform"
"github.com/stretchr/testify/assert"
)
const input_yaml = `
---
project_id: "seed-423789"
prefix: "storage"
names: ["anto","general"]
folders:
anto: ["/documents","/private/anto"]
general: ["/docs","/public/general"]
bucket_policy_only:
anto: true
general: false
force_destroy: true
lifecycle_rules:
- action:
type: "SetStorageClass"
storage_class: "NEARLINE"
condition:
age: "10"
matches_storage_class: "MULTI_REGIONAL,STANDARD,DURABLE_REDUCED_AVAILABILITY"
`
func writeInput(content string) {
d1 := []byte(content)
e := os.WriteFile("/config/input.yaml", d1, 0644)
if e != nil {
panic(e)
}
}
func TestTerraformGCS(t *testing.T) {
// retryable errors in terraform testing.
terraformOptions := terraform.WithDefaultRetryableErrors(t, &terraform.Options{
TerraformDir: "/tf",
BackendConfig: map[string]interface{}{
"prefix": "test",
"bucket": "tf-state-outofdevops",
},
})
writeInput(input_yaml)
defer terraform.Destroy(t, terraformOptions)
assert.Equal(t, false, bucketExists("storage-eu-anto"))
assert.Equal(t, false, bucketExists("storage-eu-general"))
terraform.InitAndApply(t, terraformOptions)
assert.Equal(t, true, bucketExists("storage-eu-anto"))
assert.Equal(t, true, bucketExists("storage-eu-general"))
}
func bucketExists(bucketName string) bool {
ctx := context.Background()
client, err := storage.NewClient(ctx)
if err != nil {
return false
}
defer client.Close()
bucket := client.Bucket(bucketName)
_, err = bucket.Attrs(ctx)
return err == nil
}
This is our test written in Go, we are using terratest to manage the terraform lifecycle.
The test is very simple:
- we specify our input file in yaml (lines 13-32)
- we write that to disk (line 52)
- we verify that the buckets we want to create don’t exist already (lines 55-56)
- we
InitAndApply
or terraform code (line 57) - we verify again that the buckets exist (lines 59-60)
The Dockerfile
The other file we have in this folder is a Dockerfile, with want to run our test on top of the image we already have.
Let’s look inside it:
#STAGE 1 Build your test
FROM golang:1.16 as build
WORKDIR /work_dir
COPY go.mod go.sum ./
RUN go mod download && go mod verify
COPY gcs_test.go .
ENV CGO_ENABLED=0
RUN go test -c -o gcs_test
#STAGE 2 Run Test
FROM terraform-gcs as test
COPY --from=build /work_dir/gcs_test /working_dir/gcs_test
This is another multi-stage build where in the first stage we compile our Go test and in the second we copy the executable gcs_test
on top of the terraform_gcs
this is the name on the image we built in the previous episode.
Build and Test
So now we have to just build: docker build . -t terraform_test
and run:
docker run -it -e GOOGLE_APPLICATION_CREDENTIALS=/config/sa.json \
-v ~/sa.json:/config/sa.json:ro \
terraform_test /working_dir/gcs_test
Conclusions
Now we have our terraform code in a container and tested. We are treading our IaC like application code because:
- Externalised the configuration
- Packaged our artifact
- Tested our code
In the next episode we will see how we can build a CD pipeline for Application and Infrastructure code.
Share this post
Twitter
Google+
Facebook
Reddit
LinkedIn
StumbleUpon
Pinterest
Email