TL;DR

  • Usa Terratest para testing de integración de módulos Terraform en GCP—despliega recursos reales, verifica con APIs de GCP, luego destruye
  • Combina gcloud terraform vet con constraints de Policy Library para validación de políticas pre-deployment
  • Testing nativo de Terraform (v1.6+) maneja tests unitarios; Terratest maneja tests de integración que necesitan recursos GCP reales

Ideal para: Equipos gestionando infraestructura GCP con Terraform que necesitan validación de compliance y confianza en deployments No recomendado si: Usas exclusivamente GCP Console o tienes menos de 5 módulos Terraform Tiempo de lectura: 12 minutos

El testing de infraestructura GCP difiere del testing de AWS o Azure debido al modelo único de recursos de Google, estructura IAM y el ecosistema de Config Validator. Esta guía cubre enfoques prácticos de testing que aprovechan herramientas nativas de GCP junto con patrones universales de testing de infraestructura.

Entender la pirámide de testing de Terraform proporciona contexto sobre dónde encaja el testing específico de GCP dentro de tu estrategia general de validación de IaC.

Enfoques Asistidos por IA

Las herramientas de IA aceleran el desarrollo de tests de infraestructura GCP, especialmente para generar constraint templates y assertions de Terratest.

Generando assertions de Terratest para recursos GCP:

Tengo un módulo Terraform que crea:

- GCP Compute Instance con metadata personalizada
- Red VPC con reglas de firewall
- Bucket de Cloud Storage con políticas de lifecycle

Genera assertions de Terratest en Go que verifiquen:

1. La instancia está corriendo y tiene el machine type correcto
2. Las reglas de firewall solo permiten puertos 443 y 22
3. El bucket tiene versioning habilitado y regla de lifecycle para eliminación a 30 días
4. Todos los recursos tienen labels requeridas: environment, team, cost-center

Incluye manejo de errores apropiado y usa paquetes google.golang.org/api.

Creando constraints de Config Validator:

Escribe un constraint template de Rego para Config Validator que aplique:

1. Todos los buckets GCS deben tener uniform bucket-level access habilitado
2. Todas las instancias Compute no deben tener direcciones IP externas
3. Todas las VPCs deben tener flow logs habilitados

Usa el formato de GCP Policy Library con tipos de asset CAI correctos
e incluye archivos YAML de constraint de ejemplo.

Depurando validaciones fallidas de terraform vet:

Mi comando terraform vet falla con esta salida:
[pegar salida de vet]

El constraint es:
[pegar YAML del constraint]

Mi plan de Terraform incluye:
[pegar salida relevante del terraform plan]

Explica por qué falla la validación y cómo arreglar
el código Terraform o el constraint.

Cuándo Usar Cada Enfoque de Testing

Framework de Decisión de Estrategia de Testing

Tipo de TestHerramientaCuándo UsarRecursos GCP Requeridos
Validación de sintaxisterraform validateCada commitNinguno
Análisis estáticotflint-ruleset-googleCada commitNinguno
Pre-check de políticasgcloud terraform vetAntes de plan/applySolo acceso a API
Testing unitarioTerraform test (nativo)Lógica de móduloNinguno (mockeado)
Testing de integraciónTerratestValidación de recursos realesAcceso completo al proyecto
End-to-endTerratest + tests de aplicaciónEntornos tipo producciónProyecto de test dedicado

Usa Terratest Cuando

  • Necesitas verificar propiedades reales de recursos GCP: Metadata, labels, configs de red
  • Testing de interacciones entre recursos: IAM bindings, reglas de firewall afectando instancias
  • Validando comportamientos específicos de GCP: Scaling de managed instance groups, failover de Cloud SQL
  • CI/CD requiere verificación de deployment: Los recursos deben existir antes de proceder

Usa Config Validator/terraform vet Cuando

  • Aplicando políticas organizacionales: Sin IPs públicas, encriptación requerida, regiones específicas
  • Checks de compliance pre-deployment: Bloquear planes no conformes antes del apply
  • Estandarizando entre equipos: Tagging consistente, convenciones de nombres, configuraciones de recursos

Terratest para Módulos GCP

Estructura Básica de Test de Módulo

package test

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

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

    projectID := gcp.GetGoogleProjectIDFromEnvVar(t)
    region := "us-central1"
    zone := "us-central1-a"

    terraformOptions := terraform.WithDefaultRetryableErrors(t, &terraform.Options{
        TerraformDir: "../modules/compute",
        Vars: map[string]interface{}{
            "project_id":    projectID,
            "region":        region,
            "zone":          zone,
            "instance_name": "test-instance-" + random.UniqueId(),
            "machine_type":  "e2-micro",
        },
    })

    defer terraform.Destroy(t, terraformOptions)
    terraform.InitAndApply(t, terraformOptions)

    // Verificar propiedades de la instancia
    instanceName := terraform.Output(t, terraformOptions, "instance_name")
    instance := gcp.FetchInstance(t, projectID, zone, instanceName)

    assert.Equal(t, "RUNNING", instance.Status)
    assert.Contains(t, instance.MachineType, "e2-micro")
}

Testing VPC y Reglas de Firewall

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

    projectID := gcp.GetGoogleProjectIDFromEnvVar(t)

    terraformOptions := terraform.WithDefaultRetryableErrors(t, &terraform.Options{
        TerraformDir: "../modules/network",
        Vars: map[string]interface{}{
            "project_id":   projectID,
            "network_name": "test-vpc-" + random.UniqueId(),
            "allowed_ports": []string{"443", "22"},
        },
    })

    defer terraform.Destroy(t, terraformOptions)
    terraform.InitAndApply(t, terraformOptions)

    networkName := terraform.Output(t, terraformOptions, "network_name")

    // Verificar reglas de firewall
    firewalls := gcp.GetFirewallRulesForNetwork(t, projectID, networkName)

    for _, fw := range firewalls {
        // Asegurar que ninguna regla permite 0.0.0.0/0 en puertos sensibles
        for _, allowed := range fw.Allowed {
            if contains(fw.SourceRanges, "0.0.0.0/0") {
                assert.NotContains(t, allowed.Ports, "3389",
                    "RDP no debe estar abierto a internet")
                assert.NotContains(t, allowed.Ports, "3306",
                    "MySQL no debe estar abierto a internet")
            }
        }
    }
}

Testing Cloud Storage con Reglas de Lifecycle

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

    projectID := gcp.GetGoogleProjectIDFromEnvVar(t)
    bucketName := "test-bucket-" + strings.ToLower(random.UniqueId())

    terraformOptions := terraform.WithDefaultRetryableErrors(t, &terraform.Options{
        TerraformDir: "../modules/storage",
        Vars: map[string]interface{}{
            "project_id":  projectID,
            "bucket_name": bucketName,
            "location":    "US",
        },
    })

    defer terraform.Destroy(t, terraformOptions)
    terraform.InitAndApply(t, terraformOptions)

    // Usar biblioteca cliente de GCP para inspección detallada del bucket
    ctx := context.Background()
    client, err := storage.NewClient(ctx)
    require.NoError(t, err)
    defer client.Close()

    bucket := client.Bucket(bucketName)
    attrs, err := bucket.Attrs(ctx)
    require.NoError(t, err)

    // Verificar requisitos de compliance
    assert.True(t, attrs.UniformBucketLevelAccess.Enabled,
        "Uniform bucket-level access debe estar habilitado")
    assert.True(t, attrs.VersioningEnabled,
        "Versioning debe estar habilitado")
    assert.NotEmpty(t, attrs.Lifecycle.Rules,
        "Reglas de lifecycle deben estar configuradas")
}

Config Validator y Policy Library

Configurando Policy Library

# Clonar la policy library de GCP
git clone https://github.com/GoogleCloudPlatform/policy-library.git
cd policy-library

# Estructura
# policies/
#   constraints/    # Tus definiciones de constraints
#   templates/      # Constraint templates (reglas Rego)

Creando Constraints Personalizados

# policies/constraints/require_bucket_versioning.yaml
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: GCPStorageBucketVersioningConstraintV1
metadata:
  name: require-bucket-versioning
spec:
  severity: high
  match:
    ancestries:

      - "organizations/123456789"
    excludedAncestries: []
  parameters:
    versioning_enabled: true

Ejemplo de Constraint Template

# policies/templates/gcp_storage_bucket_versioning.yaml
apiVersion: templates.gatekeeper.sh/v1beta1
kind: ConstraintTemplate
metadata:
  name: gcpstoragebucketversioningconstraintv1
spec:
  crd:
    spec:
      names:
        kind: GCPStorageBucketVersioningConstraintV1
      validation:
        openAPIV3Schema:
          properties:
            versioning_enabled:
              type: boolean
  targets:

    - target: validation.gcp.forsetisecurity.org
      rego: |
        package templates.gcp.GCPStorageBucketVersioningConstraintV1

        violation[{
            "msg": message,
            "details": metadata,
        }] {
            asset := input.asset
            asset.asset_type == "storage.googleapis.com/Bucket"

            versioning := asset.resource.data.versioning
            not versioning.enabled

            message := sprintf("Bucket %v no tiene versioning habilitado", [asset.name])
            metadata := {"resource": asset.name}
        }

Usando gcloud terraform vet

# Generar plan de Terraform en formato JSON
terraform plan -out=tfplan.binary
terraform show -json tfplan.binary > tfplan.json

# Validar contra policy library
gcloud beta terraform vet tfplan.json \
    --policy-library=./policy-library \
    --project=my-project

# En pipeline CI/CD
gcloud beta terraform vet tfplan.json \
    --policy-library=./policy-library \
    --format=json > violations.json

# Verificar violaciones
if [ $(jq '.violations | length' violations.json) -gt 0 ]; then
    echo "Se encontraron violaciones de política:"
    jq '.violations[] | .message' violations.json
    exit 1
fi

Integración CI/CD

Workflow de GitHub Actions

name: GCP Infrastructure Tests

on:
  pull_request:
    paths:

      - 'terraform/**'
      - 'policies/**'

jobs:
  static-analysis:
    runs-on: ubuntu-latest
    steps:

      - uses: actions/checkout@v4

      - name: Setup Terraform
        uses: hashicorp/setup-terraform@v3
        with:
          terraform_version: 1.7.0

      - name: Terraform Validate
        run: terraform validate
        working-directory: terraform/

      - name: TFLint con ruleset GCP
        uses: terraform-linters/setup-tflint@v4
        with:
          tflint_version: latest

      - run: |
          tflint --init
          tflint --format=compact
        working-directory: terraform/

  policy-validation:
    runs-on: ubuntu-latest
    needs: static-analysis
    steps:

      - uses: actions/checkout@v4

      - name: Setup Terraform
        uses: hashicorp/setup-terraform@v3

      - name: Autenticar en GCP
        uses: google-github-actions/auth@v2
        with:
          credentials_json: ${{ secrets.GCP_SA_KEY }}

      - name: Setup gcloud
        uses: google-github-actions/setup-gcloud@v2

      - name: Generar plan
        run: |
          terraform init
          terraform plan -out=tfplan.binary
          terraform show -json tfplan.binary > tfplan.json
        working-directory: terraform/

      - name: Validación de políticas
        run: |
          gcloud beta terraform vet tfplan.json \
            --policy-library=../policies \
            --format=json > violations.json

          if [ $(jq '.violations | length' violations.json) -gt 0 ]; then
            echo "::error::Violaciones de política detectadas"
            jq -r '.violations[] | "- \(.constraint): \(.message)"' violations.json
            exit 1
          fi
        working-directory: terraform/

  integration-tests:
    runs-on: ubuntu-latest
    needs: policy-validation
    if: github.event.pull_request.draft == false
    steps:

      - uses: actions/checkout@v4

      - name: Setup Go
        uses: actions/setup-go@v5
        with:
          go-version: '1.22'

      - name: Autenticar en GCP
        uses: google-github-actions/auth@v2
        with:
          credentials_json: ${{ secrets.GCP_SA_KEY }}

      - name: Ejecutar Terratest
        run: |
          cd test
          go test -v -timeout 30m -parallel 4
        env:
          GOOGLE_PROJECT: ${{ secrets.GCP_TEST_PROJECT }}

Patrones Comunes de Testing GCP

Testing de IAM Bindings

func TestIAMBindings(t *testing.T) {
    projectID := gcp.GetGoogleProjectIDFromEnvVar(t)

    // Después de terraform apply...

    // Verificar que service account tiene roles esperados
    saEmail := terraform.Output(t, terraformOptions, "service_account_email")

    policy, err := getProjectIAMPolicy(projectID)
    require.NoError(t, err)

    expectedRole := "roles/storage.objectViewer"
    member := "serviceAccount:" + saEmail

    found := false
    for _, binding := range policy.Bindings {
        if binding.Role == expectedRole {
            for _, m := range binding.Members {
                if m == member {
                    found = true
                    break
                }
            }
        }
    }
    assert.True(t, found, "Service account debe tener rol %s", expectedRole)
}

Testing Private Google Access

func TestPrivateGoogleAccess(t *testing.T) {
    projectID := gcp.GetGoogleProjectIDFromEnvVar(t)

    // Después de crear subnet...

    subnetName := terraform.Output(t, terraformOptions, "subnet_name")
    region := "us-central1"

    subnet := gcp.FetchSubnet(t, projectID, region, subnetName)

    assert.True(t, subnet.PrivateIpGoogleAccess,
        "Private Google Access debe estar habilitado para subnets privadas")
}

Midiendo el Éxito

MétricaAntes del TestingDespués del TestingCómo Rastrear
Violaciones de política en prodDesconocido0Reportes de Config Validator
Deployments fallidos15%/mes<2%/mesMétricas de pipeline CI/CD
Hallazgos de auditoría de compliance5-10/trimestre0-1/trimestreReportes de auditoría
Tiempo medio para detectar issuesDíasMinutosTimestamps de alertas

Señales de advertencia de que tu testing no funciona:

  • Violaciones de política aún llegando a producción
  • Terratest pasando pero issues reales en recursos desplegados
  • Tests tomando >30 minutos (probable fuga de recursos)
  • Tests flaky debido a consistencia eventual

Conclusión

El testing de infraestructura GCP combina prácticas universales de testing IaC con herramientas específicas de Google:

  1. Usa Terratest para tests de integración que requieren recursos GCP reales
  2. Implementa constraints de Config Validator para políticas organizacionales
  3. Ejecuta gcloud terraform vet en CI/CD para detectar violaciones temprano
  4. Aprovecha la Policy Library para patrones comunes de compliance

La clave es estratificar estas herramientas—el análisis estático detecta issues de sintaxis, la validación de políticas detecta issues de compliance, y los tests de integración detectan issues funcionales.

Ver También

Recursos Oficiales