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.
-
Comentarios
Online: 0
Por favor inicie sesión para poder escribir comentarios.