145 строки
4.5 KiB
Go
145 строки
4.5 KiB
Go
package terraform_module_test_helper
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"path/filepath"
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/gruntwork-io/terratest/modules/files"
|
|
"github.com/gruntwork-io/terratest/modules/logger"
|
|
"github.com/gruntwork-io/terratest/modules/terraform"
|
|
test_structure "github.com/gruntwork-io/terratest/modules/test-structure"
|
|
terratest "github.com/gruntwork-io/terratest/modules/testing"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
type TestOptions struct {
|
|
TerraformOptions terraform.Options
|
|
Assertion func(*testing.T, TerraformOutput)
|
|
SkipIdempotentCheck bool
|
|
SkipDestroy bool
|
|
}
|
|
|
|
var copyLock = &KeyedMutex{}
|
|
var initLock = &sync.Mutex{}
|
|
|
|
type TerraformOutput = map[string]interface{}
|
|
|
|
type testExecutor interface {
|
|
TearDown(t *testing.T, rootDir string, modulePath string)
|
|
Logger() logger.TestLogger
|
|
}
|
|
|
|
var _ testExecutor = e2eTestExecutor{}
|
|
|
|
type e2eTestExecutor struct{}
|
|
|
|
func (e2eTestExecutor) TearDown(t *testing.T, rootDir string, modulePath string) {
|
|
s := SuccessTestVersionSnapshot(rootDir, modulePath)
|
|
if t.Failed() {
|
|
s = FailedTestVersionSnapshot(rootDir, modulePath, "")
|
|
}
|
|
require.NoError(t, s.Save(t))
|
|
}
|
|
|
|
func (e2eTestExecutor) Logger() logger.TestLogger {
|
|
l := NewMemoryLogger()
|
|
return l
|
|
}
|
|
|
|
func RunE2ETest(t *testing.T, moduleRootPath, exampleRelativePath string, option terraform.Options, assertion func(*testing.T, TerraformOutput)) {
|
|
initAndApplyAndIdempotentTest(t, moduleRootPath, exampleRelativePath, option, false, false, assertion, e2eTestExecutor{})
|
|
}
|
|
|
|
func RunE2ETestWithOption(t *testing.T, moduleRootPath, exampleRelativePath string, testOption TestOptions) {
|
|
initAndApplyAndIdempotentTest(t, moduleRootPath, exampleRelativePath, testOption.TerraformOptions, false, testOption.SkipIdempotentCheck, testOption.Assertion, e2eTestExecutor{})
|
|
}
|
|
|
|
func initAndApplyAndIdempotentTest(t *testing.T, moduleRootPath string, exampleRelativePath string, option terraform.Options, skipDestroy bool, skipCheckIdempotent bool, assertion func(*testing.T, TerraformOutput), executor testExecutor) {
|
|
tryParallel(t)
|
|
defer executor.TearDown(t, moduleRootPath, exampleRelativePath)
|
|
testDir := filepath.Join(moduleRootPath, exampleRelativePath)
|
|
logger.Log(t, fmt.Sprintf("===> Starting test for %s, since we're running tests in parallel, the test log will be buffered and output to stdout after the test was finished.", testDir))
|
|
|
|
tmpDir := copyTerraformFolderToTemp(t, moduleRootPath, exampleRelativePath)
|
|
defer func() {
|
|
_ = os.RemoveAll(filepath.Clean(tmpDir))
|
|
}()
|
|
option.TerraformDir = tmpDir
|
|
|
|
l := executor.Logger()
|
|
c, ok := l.(io.Closer)
|
|
if ok {
|
|
defer func() {
|
|
_ = c.Close()
|
|
}()
|
|
}
|
|
option.Logger = logger.New(l)
|
|
option = setupRetryLogic(option)
|
|
|
|
if !skipDestroy {
|
|
defer destroy(t, option)
|
|
}
|
|
initAndApply(t, &option)
|
|
var err error
|
|
if !skipCheckIdempotent {
|
|
err = initAndPlanAndIdempotentAtEasyMode(t, option)
|
|
}
|
|
require.NoError(t, err)
|
|
if assertion != nil {
|
|
assertion(t, terraform.OutputAll(t, removeLogger(option)))
|
|
}
|
|
}
|
|
|
|
func copyTerraformFolderToTemp(t *testing.T, moduleRootPath string, exampleRelativePath string) string {
|
|
unlock := copyLock.Lock(exampleRelativePath)
|
|
defer unlock()
|
|
tmpDir := test_structure.CopyTerraformFolderToTemp(t, moduleRootPath, exampleRelativePath)
|
|
return tmpDir
|
|
}
|
|
|
|
func initAndApply(t terratest.TestingT, options *terraform.Options) string {
|
|
tfInit(t, options)
|
|
return terraform.Apply(t, options)
|
|
}
|
|
|
|
func tfInit(t terratest.TestingT, options *terraform.Options) {
|
|
initLock.Lock()
|
|
defer initLock.Unlock()
|
|
terraform.Init(t, options)
|
|
}
|
|
|
|
func destroy(t *testing.T, option terraform.Options) {
|
|
path := option.TerraformDir
|
|
if !files.IsExistingDir(path) || !files.FileExists(filepath.Join(path, "terraform.tfstate")) {
|
|
return
|
|
}
|
|
|
|
option.MaxRetries = 5
|
|
option.TimeBetweenRetries = time.Minute
|
|
option.RetryableTerraformErrors = map[string]string{
|
|
".*": "Retry destroy on any error",
|
|
}
|
|
_, err := terraform.RunTerraformCommandE(t, &option, terraform.FormatArgs(&option, "destroy", "-auto-approve", "-input=false", "-refresh=false")...)
|
|
if err != nil {
|
|
_, err = terraform.DestroyE(t, &option)
|
|
}
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
func removeLogger(option terraform.Options) *terraform.Options {
|
|
// default logger might leak sensitive data
|
|
option.Logger = logger.Discard
|
|
return &option
|
|
}
|
|
|
|
func retryableOptions(t *testing.T, options terraform.Options) terraform.Options {
|
|
result := terraform.WithDefaultRetryableErrors(t, &options)
|
|
result.RetryableTerraformErrors[".*Please try again.*"] = "Service side suggest retry."
|
|
return *result
|
|
}
|