diff --git a/pkg/atmos/aws-component-helper/atmos.go b/pkg/atmos/aws-component-helper/atmos.go index 6716eb0..2a89603 100644 --- a/pkg/atmos/aws-component-helper/atmos.go +++ b/pkg/atmos/aws-component-helper/atmos.go @@ -10,6 +10,16 @@ import ( "github.com/stretchr/testify/require" ) +func GetAtmosTestOptions(t *testing.T, componentName string, stackName string, vars map[string]interface{}) *atmos.Options { + atmosOptions := atmos.WithDefaultRetryableErrors(t, &atmos.Options{ + Component: componentName, + Stack: stackName, + NoColor: false, + Vars: vars, + }) + return atmosOptions +} + func GetAtmosOptions(t *testing.T, suite *TestSuite, componentName string, stackName string, vars map[string]interface{}) *atmos.Options { mergedVars := map[string]interface{}{ "attributes": []string{suite.RandomIdentifier}, diff --git a/pkg/atmos/aws-component-helper/setup_subject.go b/pkg/atmos/aws-component-helper/setup_subject.go new file mode 100644 index 0000000..237d2fd --- /dev/null +++ b/pkg/atmos/aws-component-helper/setup_subject.go @@ -0,0 +1,106 @@ +package aws_component_helper + +import ( + "github.com/gruntwork-io/terratest/modules/logger" + "github.com/stretchr/testify/require" + "os" + "path/filepath" + "strings" + "testing" + + "github.com/cloudposse/test-helpers/pkg/atmos" + "github.com/gruntwork-io/terratest/modules/files" + "github.com/stretchr/testify/assert" +) + +func awsComponentTestCleanup(t *testing.T, opts *atmos.Options, destroy bool, tmpDir string, protectDir string) { + if destroy { + out, err := atmos.DestroyE(t, opts) + if err == nil { + // if the destroy was successful, remove the temp directory + if tmpDir == protectDir { + t.Logf("Not removing protected directory %s", protectDir) + } else { + t.Log("Cleaning out temp folder...") + err = os.RemoveAll(tmpDir) + if err == nil { + t.Logf("Removed temp directory %s", tmpDir) + } else { + assert.NoError(t, err, "Failed to remove temp directory %s", tmpDir) + } + } + } else { + // if the destroy failed, leave the temp directory in place + assert.NoError(t, err, "Failed to destroy subject. Leaving source and state in %v\n\nDestroy output:\n%v\n\n", tmpDir, out) + } + } +} + +type ComponentTestResults struct { + Output string + TestID string +} + +func IsLocalAwsComponentTest(t *testing.T) bool { + atmosPlainOpts := &atmos.Options{} + existingTestID, err := atmos.RunAtmosCommandE(t, atmosPlainOpts, "test", "get-test-id") + + return err == nil && len(existingTestID) > 0 +} + +func AwsComponentTestHelper(t *testing.T, opts *atmos.Options, callback func(t *testing.T, opts *atmos.Options, results ComponentTestResults)) { + testSrcRoot := os.Getenv("ATMOS_BASE_PATH") + testRoot := testSrcRoot + if testSrcRoot == "" { + assert.FailNow(t, "ATMOS_BASE_PATH must be set, but is empty") + } + + atmosPlainOpts := &atmos.Options{} + doApply := false + + var testID string + + existingTestID, err := atmos.RunAtmosCommandE(t, atmosPlainOpts, "test", "get-test-id") + if err != nil || len(existingTestID) == 0 { + doApply = true + // Copy test source to a temp directory and create a new test ID + t.Log("Copying files to temp folder...") + testRoot, err = files.CopyTerraformFolderToTemp(testSrcRoot, t.Name()) + require.NoError(t, err) + err = copyDirectoryRecursively(filepath.Join(testSrcRoot, "state"), filepath.Join(testRoot, "state")) + require.NoError(t, err) + atmosPlainOpts.AtmosBasePath = testRoot + testID, err = atmos.RunAtmosCommandE(t, atmosPlainOpts, "test", "make-test-id") + require.NoError(t, err) + testID = strings.TrimSpace(testID) + } else { + testID = strings.TrimSpace(existingTestID) + } + + t.Logf("Running test \"%s\" with test ID \"%s\" in directory %s", t.Name(), testID, testRoot) + + options := atmos.WithDefaultRetryableErrors(t, opts) + options.AtmosBasePath = testRoot + // Keep the output quiet + if !testing.Verbose() { + options.Logger = logger.Discard + } + + defer awsComponentTestCleanup(t, options, doApply, testRoot, testSrcRoot) + + // Apply the deployment + out := "" + if doApply { + out, err = atmos.ApplyE(t, options) + require.NoError(t, err, "Failed to deploy component, skipping other tests.") + } + // Call the callback function for assertions + callback(t, options, ComponentTestResults{ + Output: out, + TestID: testID, + }) + + if !doApply { + t.Logf("\n\n\nTests complete in %s\n\n", testRoot) + } +} diff --git a/pkg/atmos/cmd.go b/pkg/atmos/cmd.go index 7963290..2a97f66 100644 --- a/pkg/atmos/cmd.go +++ b/pkg/atmos/cmd.go @@ -14,6 +14,16 @@ import ( ) func generateCommand(options *Options, args ...string) shell.Command { + // TODO: replace with command line argument + if options.AtmosBasePath != "" { + var err error + options, err = options.Clone() + if err != nil { + fmt.Printf("Error cloning options: %v", err) + return shell.Command{} + } + options.EnvVars["ATMOS_BASE_PATH"] = options.AtmosBasePath + } cmd := shell.Command{ Command: options.AtmosBinary, Args: args, @@ -43,6 +53,13 @@ func GetCommonOptions(options *Options, args ...string) (*Options, []string) { options.AtmosBinary = DefaultExecutable } + // TODO: + // if options.AtmosBasePath != "", then add args "--base-path", options.AtmosBasePath to the args + // Unfortunately, as of Atmos 1.109.0, the flag "--base-path" does not work + // if options.AtmosBasePath != "" { + // args = append([]string{"--base-path", options.AtmosBasePath}, args...) + // } + if options.Parallelism > 0 && len(args) > 0 && args[0] == "terraform" && collections.ListContains(commandsWithParallelism, args[1]) { args = append(args, fmt.Sprintf("--parallelism=%d", options.Parallelism)) }