Дайджесты новостей
Модульное разделение фреймворка Terratest 2.0 на компактное ядро и независимые облачные пакеты.

Terratest 2.0: модульная архитектура и тестирование Terraform без лишних SDK

Каждый Go-инженер, писавший автоматические тесты для облачной инфраструктуры с помощью фреймворка Terratest, помнит это странное ощущение: вы хотите протестировать всего один простейший манифест Terraform, поднимающий виртуальную машину, но команда go get начинает скачивать едва ли не треть облачного интернета. В проект затягивались гигантские SDK от AWS, Azure, Google Cloud, OCI, а в придачу — увесистые клиентские библиотеки Kubernetes.

В результате компактный инфраструктурный репозиторий обрастал файлом go.sum на сотни строк, а раннеры в CI тратили львиную долю времени на загрузку и компиляцию транзитивных зависимостей, к которым написанный тест даже не прикасался. Долгожданный мажорный релиз Terratest 2.0 от компании Gruntwork наконец разрешил эту давнюю архитектурную боль.

Проблема «тяжелого чемодана»: почему раздувался go.sum

Первая версия Terratest проектировалась как швейцарский нож: единая библиотека, покрывающая все аспекты автоматизации — от сборки образов в Packer и развертывания Helm-чартов до вызова команд Terraform и SSH-проверок. Но за универсальность пришлось заплатить монолитной структурой Go-модуля.

Если в вашей собственной кодовой базе использовалась более свежая версия AWS SDK v2, старый монолитный Terratest мог вступить с ней в конфликт, провоцируя сложный и болезненный dependency hell. Простой тест требовал фиксации 675 строк зависимостей в go.sum. Разработчики образно называли это «поездкой налегке с неподъемным чемоданом чужих вещей».

Модульное расщепление на 16 частей и перенос ядра в core/v2

В версии 2.0 монолит расщепили на 16 узкоспециализированных модулей, каждый из которых версионируется независимо и содержит суффикс /v2 в пути импорта.

Ключевые изменения архитектуры:

  • Выделение ядра в modules/core/v2: универсальные вспомогательные утилиты — генераторы случайных строк (random), механизмы повторных попыток (retry), системные логгеры (logger), функции работы с файлами и shell — вынесены в легкое независимое ядро без внешних вендорных привязок.
  • Драматическое сокращение графа зависимостей: для проекта, которому требуется только валидация HCL-кода через modules/terraform/v2, размер go.sum сократился с 675 до 77 строк — почти на порядок.
  • Унификация имен пакетов: из путей исключили дефисы в соответствии с лучшими практиками Go. Пакеты http-helper, dns-helper и test-structure превратились в httphelper, dnshelper и teststructure.
  • Очистка от устаревшего легаси: из дистрибутива окончательно удалили невостребованные и заброшенные модули collections, environment, git, version-checker, slack и oci.

Практика миграции: обновляем go.mod и пишем тест для Terraform

Переход на новую версию требует явного изменения объявлений в go.mod. Теперь вы декларируете только те облачные провайдеры, которые реально задействованы в ваших сценариях:

module my-infra-tests

go 1.23.0

require (
    github.com/gruntwork-io/terratest/modules/core/v2 v2.0.0
    github.com/gruntwork-io/terratest/modules/terraform/v2 v2.0.0
    github.com/stretchr/testify v1.9.0
)

В самом исходном коде теста меняются пути импорта: вместо обращения к глубоко вложенным пакетам старого монолита вы явно подключаете утилиты ядра и целевой раннер Terraform:

package test

import (
    "testing"

    "github.com/gruntwork-io/terratest/modules/core/v2/logger"
    "github.com/gruntwork-io/terratest/modules/core/v2/random"
    "github.com/gruntwork-io/terratest/modules/terraform/v2"
    "github.com/stretchr/testify/assert"
)

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

    // Генерация уникального префикса для изоляции ресурсов в облаке
    uniqueID := random.UniqueId()
    instanceName := "web-node-" + uniqueID

    terraformOptions := terraform.WithDefaultRetryableErrors(t, &terraform.Options{
        TerraformDir: "../examples/web-cluster",
        Vars: map[string]interface{}{
            "instance_name": instanceName,
        },
        Logger: logger.Default,
    })

    // Гарантированное удаление созданных ресурсов даже в случае падения теста
    defer terraform.Destroy(t, terraformOptions)

    terraform.InitAndApply(t, terraformOptions)

    endpoint := terraform.Output(t, terraformOptions, "service_endpoint")
    assert.NotEmpty(t, endpoint)
}

Благодаря отсутствию лишних SDK компиляция теста стартует моментально, а кеш модулей в CI не забивается мегабайтами неиспользуемых бинарников.

Ломающие изменения и подводные камни с ExternalIP

Поскольку Terratest 2.0 представляет собой мажорный релиз с ломающими изменениями (breaking changes), автоматический переход без правки кода невозможен. Командам предстоит пакетный рефакторинг путей импорта во всех тестовых файлах. Первая ветка v1 останется на жизнеобеспечении в течение 12 месяцев, но будет получать исключительно патчи критических уязвимостей безопасности.

Кроме того, инженерам инфраструктуры важно обратить внимание на скрытое изменение в хелперах для Kubernetes: при получении сетевых адресов нод кластера логика теперь отдает приоритет публичному адресу ExternalIP вместо внутреннего приватного InternalIP. Если ваши тесты выполняются внутри изолированного корпоративного VPC без прямого выхода наружу, этот нюанс может потребовать явного переопределения параметров подключения.

Для всех новых проектов и активных монорепозиториев переход на Terratest 2.0 — однозначно рекомендованный шаг, возвращающий сборкам легкость и предсказуемость.