Introduccion rapida a terratest


Introducción

En este artículo vamos a ver los conceptos básicos para realizar tests en tu código de terraform usando un patrón reutilizable. Usaremos el código del artículo anterior Autenticación serverless con Cognito, por lo que te recomiendo leer ese primero si querés saber cómo llegamos hasta acá. Además, como nota aparte, este es un ejemplo muy básico de cómo empezar con terratest.


Terratest es una librería en Go que facilita escribir tests automatizados para tu infraestructura, soporta Terraform, Docker, Packer, SSH, AWS, GCP, Kubernetes, Helm, y mucho más. Como está escrita en Go, tenés acceso a todas las APIs existentes.


El código

Hay comentarios por todo el código para explicar cada parte, pero lo que quiero destacar acá es el patrón que usamos con el módulo test-structure. Este módulo nos permite dividir el test en secciones y saltarnos partes que no necesitemos o que no queramos correr. Tenemos 3 etapas: cleanup, deploy y validate. Esto te permite usar SKIP_etapa, por ejemplo, SKIP_cleanup cuando corras tus tests con go test -timeout 90m . (agregué algunos bits adicionales que suelo usar, como el timeout, que por defecto creo que es de 10 minutos y suele ser demasiado corto), para correr solo validate y cleanup. Esto es útil cuando estás desarrollando un módulo y querés testear sin tener que esperar a que todo se recree.

package test

import (
    "crypto/tls"
    "fmt"
    "testing"
    "time"

    http_helper "github.com/gruntwork-io/terratest/modules/http-helper"
    "github.com/gruntwork-io/terratest/modules/retry"
    "github.com/gruntwork-io/terratest/modules/terraform"
    test_structure "github.com/gruntwork-io/terratest/modules/test-structure"
)

// Función principal, define etapas y ejecuta.
func TestTerraformAws(t *testing.T) {
    t.Parallel()

    // Elige una región aleatoria de AWS para testear. Esto ayuda a asegurar que el código funcione en todas las regiones.
    // awsRegion := aws.GetRandomStableRegion(t, nil, nil)
    awsRegion := "us-east-1"

    workingDir := "../terraform"

    // Al final del test, elimina la app web usando Terraform
    defer test_structure.RunTestStage(t, "cleanup", func() {
        destroyTerraform(t, workingDir)
    })

    // Despliega la app web usando Terraform
    test_structure.RunTestStage(t, "deploy", func() {
        deployTerraform(t, awsRegion, workingDir)
    })

    // Valida que el ASG esté desplegado y responda a solicitudes HTTP
    test_structure.RunTestStage(t, "validate", func() {
        validateAPIGateway(t, workingDir)
    })
}

// Valida que el API Gateway haya sido desplegado y esté funcionando
func validateAPIGateway(t *testing.T, workingDir string) {
    // Carga las opciones de Terraform guardadas por la etapa deploy_terraform anterior
    terraformOptions := test_structure.LoadTerraformOptions(t, workingDir)

    // Ejecuta `terraform output` para obtener el valor de una variable de salida
    url := terraform.Output(t, terraformOptions, "URL")

    // Puede tardar unos minutos en que el API GW y CloudFront terminen de levantarse, por lo que se reintenta varias veces
    //  maxRetries := 30
    timeBetweenRetries := 15 * time.Second

    // Configuración TLS en blanco para usar con el helper
    tlsConfig := tls.Config{}

    // Verifica que el API Gateway devuelva una respuesta correcta
    apigw := retry.DoInBackgroundUntilStopped(t, fmt.Sprintf("Check URL %s", url), timeBetweenRetries, func() {
        http_helper.HttpGetWithCustomValidation(t, fmt.Sprintf("%s/app/health", url), &tlsConfig, func(statusCode int, body string) bool {
            return statusCode == 200
        })
    })

    // Deja de verificar el API Gateway
    apigw.Done()
}

// Despliega los recursos usando Terraform
func deployTerraform(t *testing.T, awsRegion string, workingDir string) {
    terraformOptions := &terraform.Options{
        // Ruta al código de Terraform
        TerraformDir: workingDir,
    }

    // Guarda las opciones de Terraform para que las futuras etapas del test puedan usarlas
    test_structure.SaveTerraformOptions(t, workingDir, terraformOptions)

    // Esto ejecutará `terraform init` y `terraform apply` y fallará si hay errores
    terraform.InitAndApply(t, terraformOptions)
}

// Destruye los recursos usando Terraform
func destroyTerraform(t *testing.T, workingDir string) {
    // Carga las opciones de Terraform guardadas por la etapa deploy_terraform anterior
    terraformOptions := test_structure.LoadTerraformOptions(t, workingDir)

    terraform.Destroy(t, terraformOptions)
}

Algunas notas generales sobre cada etapa:

deploy: Esta etapa se encarga de ejecutar init y luego apply.

validate: Esta etapa se encarga de ejecutar una comprobación para verificar si nuestra API está arriba y si el código de retorno es 200.

cleanup: Esta etapa se encarga de ejecutar destroy y limpiar todo.


Dep

Actualmente terratest usa dep, por lo que vas a necesitar este archivo Gopkg.toml y tener dep instalado para poder instalar las dependencias con dep ensure -v.

[[constraint]]
  name = "github.com/gruntwork-io/terratest"
  version = "0.18.6"

Dockerfile

También podés usar este pequeño Dockerfile que hace todo por vos. En este ejemplo estamos usando el código del artículo previamente mencionado.

FROM golang:alpine
MAINTAINER "kainlite <[email protected]>"

ARG TERRAFORM_VERSION=0.12.8
ENV TERRAFORM_VERSION=$TERRAFORM_VERSION

RUN apk --no-cache add curl git unzip gcc g++ make ca-certificates && \
    curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh

RUN mkdir tmp && \
    curl "https://releases.hashicorp.com/terraform/${TERRAFORM_VERSION}/terraform_${TERRAFORM_VERSION}_linux_amd64.zip" -o tmp/terraform.zip && \
    unzip tmp/terraform.zip -d /usr/local/bin && \
    rm -rf tmp/

ARG GOPROJECTPATH=/go/src/github.com/kainlite/serverless-cognito
COPY ./ $GOPROJECTPATH

WORKDIR $GOPROJECTPATH/test

RUN dep ensure -v

CMD ["go", "test", " -timeout", "90m", "."]

Testeándolo manualmente

Primero verificamos que la URL realmente funcione y que todo esté en su lugar.

$ curl https://api.skynetng.pw/app/health
# OUTPUT:
# {"status":"healthy"}

Luego podemos testearlo usando nuestra etapa validate, utilizando terratest:

$ SKIP_deploy=true SKIP_cleanup=true go test -timeout 90m .
# OUTPUT:
# ok      github.com/kainlite/test        1.117s

Esto funciona porque en el código de terraform tenemos una salida llamada URL, que es https://api.skynetng.pw, luego le agregamos /app/health al final y verificamos si devuelve un código 200, de lo contrario, esperamos y reintentamos hasta que lo haga o se agote el tiempo.


Notas finales

Y eso es todo por ahora. En la próxima parte, voy a cubrir cómo automatizar este despliegue usando una herramienta de CI/CD, para que puedas tener una infraestructura verdaderamente repetible, lo cual puede ser muy importante cuando trabajás en varios módulos, versiones y despliegues.


Errata

Si encontrás algún error o tenés alguna sugerencia, por favor enviame un mensaje para que lo pueda corregir.



No tienes cuenta? Regístrate aqui

Ya registrado? Iniciar sesión a tu cuenta ahora.

Iniciar session con GitHub
Iniciar sesion con Google
  • Comentarios

    Online: 0

Por favor inicie sesión para poder escribir comentarios.

by Gabriel Garrido