Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Copy validated Terraform to /tmp to avoid conflict #935

Merged
merged 3 commits into from
Jun 19, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 0 additions & 25 deletions modules/terraform/validate.go
Original file line number Diff line number Diff line change
@@ -1,35 +1,10 @@
package terraform

import (
// We alias Golang's native testing package to go_test to avoid naming conflicts with terratest's own testing module

go_test "testing"

"github.com/gruntwork-io/terratest/modules/testing"
"github.com/stretchr/testify/require"
)

// ValidateAllTerraformModules automatically finds all folders specified in RootDir that contain .tf files and runs
// InitAndValidate in all of them.
// Filters down to only those paths passed in ValidationOptions.IncludeDirs, if passed.
// Excludes any folders specified in the ValidationOptions.ExcludeDirs. IncludeDirs will take precedence over ExcludeDirs
// Use the NewValidationOptions method to pass relative paths for either of these options to have the full paths built
// Note that go_test is an alias to Golang's native testing package created to avoid naming conflicts with Terratest's
// own testing package. We are using the native testing.T here because Terratest's testing.T struct does not implement Run
func ValidateAllTerraformModules(t *go_test.T, opts *ValidationOptions) {
dirsToValidate, readErr := FindTerraformModulePathsInRootE(opts)
require.NoError(t, readErr)

for _, dir := range dirsToValidate {
dir := dir
t.Run(dir, func(t *go_test.T) {
t.Parallel()
tfOpts := &Options{TerraformDir: dir}
InitAndValidate(t, tfOpts)
})
}
}

// Validate calls terraform validate and returns stdout/stderr.
func Validate(t testing.TestingT, options *Options) string {
out, err := ValidateE(t, options)
Expand Down
95 changes: 0 additions & 95 deletions modules/terraform/validate_test.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,9 @@
package terraform

import (
"os"
"path/filepath"
"testing"

"github.com/gruntwork-io/terratest/modules/collections"
"github.com/gruntwork-io/terratest/modules/files"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

Expand Down Expand Up @@ -39,94 +35,3 @@ func TestInitAndValidateWithError(t *testing.T) {
require.Error(t, err)
require.Contains(t, out, "Reference to undeclared input variable")
}

func TestNewValidationOptionsRejectsEmptyRootDir(t *testing.T) {
_, err := NewValidationOptions("", []string{}, []string{})
require.Error(t, err)
}

func TestFindTerraformModulePathsInRootEExamples(t *testing.T) {
cwd, cwdErr := os.Getwd()
require.NoError(t, cwdErr)

opts, optsErr := NewValidationOptions(filepath.Join(cwd, "../../"), []string{}, []string{})
require.NoError(t, optsErr)

subDirs, err := FindTerraformModulePathsInRootE(opts)
require.NoError(t, err)
// There are many valid Terraform modules in the root/examples directory of the Terratest project, so we should get back many results
require.Greater(t, len(subDirs), 0)
}

// Verify ExcludeDirs is working properly, by explicitly passing a list of two test fixture modules to exclude
// and ensuring at the end that they do not appear in the returned slice of sub directories to validate
// Then, re-run the function with no exclusions and ensure the excluded paths ARE returned in the result set when no
// exclusions are passed
func TestFindTerraformModulePathsInRootEWithResultsExclusion(t *testing.T) {

cwd, cwdErr := os.Getwd()
require.NoError(t, cwdErr)

projectRootDir := filepath.Join(cwd, "../..")

// First, call the FindTerraformModulePathsInRootE method with several exclusions
exclusions := []string{
filepath.Join("test", "fixtures", "terraform-output"),
filepath.Join("test", "fixtures", "terraform-output-map"),
}

opts, optsErr := NewValidationOptions(projectRootDir, []string{}, exclusions)
require.NoError(t, optsErr)

subDirs, err := FindTerraformModulePathsInRootE(opts)
require.NoError(t, err)
require.Greater(t, len(subDirs), 0)
// Ensure none of the excluded paths were returned by FindTerraformModulePathsInRootE
for _, exclusion := range exclusions {
assert.False(t, collections.ListContains(subDirs, filepath.Join(projectRootDir, exclusion)))
}

// Next, call the same function but this time without exclusions and ensure that the excluded paths
// exist in the non-excluded result set
optsWithoutExclusions, optswoErr := NewValidationOptions(projectRootDir, []string{}, []string{})
require.NoError(t, optswoErr)

subDirsWithoutExclusions, woExErr := FindTerraformModulePathsInRootE(optsWithoutExclusions)
require.NoError(t, woExErr)
require.Greater(t, len(subDirsWithoutExclusions), 0)
for _, exclusion := range exclusions {
assert.True(t, collections.ListContains(subDirsWithoutExclusions, filepath.Join(projectRootDir, exclusion)))
}
}

// TestValidateAllTerraformModulesSucceedsOnValidTerraform points at a simple text fixture Terraform module that is
// known to be valid
func TestValidateAllTerraformModulesSucceedsOnValidTerraform(t *testing.T) {
cwd, err := os.Getwd()
require.NoError(t, err)

// Use the test fixtures directory as the RootDir for ValidationOptions
projectRootDir := filepath.Join(cwd, "../../test/fixtures")

opts, optsErr := NewValidationOptions(projectRootDir, []string{"terraform-validation-valid"}, []string{})
require.NoError(t, optsErr)

ValidateAllTerraformModules(t, opts)
}

// This test calls ValidateAllTerraformModules on the Terratest root directory
func TestValidateAllTerraformModulesOnTerratest(t *testing.T) {
cwd, err := os.Getwd()
require.NoError(t, err)

projectRootDir := filepath.Join(cwd, "../..")

opts, optsErr := NewValidationOptions(projectRootDir, []string{}, []string{
"test/fixtures/terraform-with-plan-error",
"test/fixtures/terragrunt/terragrunt-with-plan-error",
"examples/terraform-backend-example",
})
require.NoError(t, optsErr)

ValidateAllTerraformModules(t, opts)
}
39 changes: 39 additions & 0 deletions modules/test-structure/test_structure.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,11 @@ import (
"path/filepath"
"strings"

go_test "testing"

"github.com/gruntwork-io/terratest/modules/files"
"github.com/gruntwork-io/terratest/modules/logger"
"github.com/gruntwork-io/terratest/modules/terraform"
"github.com/gruntwork-io/terratest/modules/testing"
"github.com/stretchr/testify/require"
)
Expand Down Expand Up @@ -98,3 +101,39 @@ func cleanName(originalName string) string {
parts := strings.Split(originalName, "/")
return parts[len(parts)-1]
}

// ValidateAllTerraformModules automatically finds all folders specified in RootDir that contain .tf files and runs
// InitAndValidate in all of them.
// Filters down to only those paths passed in ValidationOptions.IncludeDirs, if passed.
// Excludes any folders specified in the ValidationOptions.ExcludeDirs. IncludeDirs will take precedence over ExcludeDirs
// Use the NewValidationOptions method to pass relative paths for either of these options to have the full paths built
// Note that go_test is an alias to Golang's native testing package created to avoid naming conflicts with Terratest's
// own testing package. We are using the native testing.T here because Terratest's testing.T struct does not implement Run
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NIT: Mention that the this is here instead of terraform module to avoid import cycling.

func ValidateAllTerraformModules(t *go_test.T, opts *ValidationOptions) {
dirsToValidate, readErr := FindTerraformModulePathsInRootE(opts)
require.NoError(t, readErr)

for _, dir := range dirsToValidate {
dir := dir
t.Run(strings.TrimLeft(dir, "/"), func(t *go_test.T) {
t.Parallel()

// Determine the absolute path to the git repository root
cwd, cwdErr := os.Getwd()
require.NoError(t, cwdErr)
gitRoot, gitRootErr := filepath.Abs(filepath.Join(cwd, "../../"))
require.NoError(t, gitRootErr)

// Determine the relative path to the example, module, etc that is currently being considered
relativePath, pathErr := filepath.Rel(gitRoot, dir)
require.NoError(t, pathErr)
// Copy git root to tmp and supply the path to the current module to run init and validate on
testFolder := CopyTerraformFolderToTemp(t, gitRoot, relativePath)
require.NotNil(t, testFolder)
// Run Terraform init and terraform validate on the test folder that was copied to /tmp
// to avoid any potential conflicts with tests that may not use the same copy to /tmp behavior
tfOpts := &terraform.Options{TerraformDir: testFolder}
terraform.InitAndValidate(t, tfOpts)
})
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package terraform
package test_structure
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NIT: Mention in the function docs that this is here instead of terraform module to avoid import cycling.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NIT: This file should probably still be called validate_struct.go, given the contents.


import (
"fmt"
Expand All @@ -7,6 +7,7 @@ import (

go_commons_collections "github.com/gruntwork-io/go-commons/collections"
"github.com/gruntwork-io/terratest/modules/collections"
"github.com/gruntwork-io/terratest/modules/files"
"github.com/mattn/go-zglob"
)

Expand Down Expand Up @@ -96,7 +97,10 @@ func FindTerraformModulePathsInRootE(opts *ValidationOptions) ([]string, error)
// The glob match returns all full paths to every .tf file, whereas we're only interested in their root
// directories for the purposes of running Terraform validate
rootDir := path.Dir(match)
terraformDirSet[rootDir] = "exists"
// Don't include hidden .terraform directories when finding paths to validate
if !files.PathContainsHiddenFileOrFolder(rootDir) {
terraformDirSet[rootDir] = "exists"
}
}

// Retrieve just the unique paths to each Terraform module directory from the map we're using as a set
Expand Down
101 changes: 100 additions & 1 deletion modules/test-structure/test_structure_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
package test_structure

import "testing"
import (
"os"
"path/filepath"
"testing"

"github.com/gruntwork-io/terratest/modules/collections"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestCopyToTempFolder(t *testing.T) {
tempFolder := CopyTerraformFolderToTemp(t, "../../", "examples")
Expand All @@ -13,3 +21,94 @@ func TestCopySubtestToTempFolder(t *testing.T) {
t.Log(tempFolder)
})
}

// TestValidateAllTerraformModulesSucceedsOnValidTerraform points at a simple text fixture Terraform module that is
// known to be valid
func TestValidateAllTerraformModulesSucceedsOnValidTerraform(t *testing.T) {
cwd, err := os.Getwd()
require.NoError(t, err)

// Use the test fixtures directory as the RootDir for ValidationOptions
projectRootDir := filepath.Join(cwd, "../../test/fixtures")

opts, optsErr := NewValidationOptions(projectRootDir, []string{"terraform-validation-valid"}, []string{})
require.NoError(t, optsErr)

ValidateAllTerraformModules(t, opts)
}

func TestNewValidationOptionsRejectsEmptyRootDir(t *testing.T) {
_, err := NewValidationOptions("", []string{}, []string{})
require.Error(t, err)
}

func TestFindTerraformModulePathsInRootEExamples(t *testing.T) {
cwd, cwdErr := os.Getwd()
require.NoError(t, cwdErr)

opts, optsErr := NewValidationOptions(filepath.Join(cwd, "../../"), []string{}, []string{})
require.NoError(t, optsErr)

subDirs, err := FindTerraformModulePathsInRootE(opts)
require.NoError(t, err)
// There are many valid Terraform modules in the root/examples directory of the Terratest project, so we should get back many results
require.Greater(t, len(subDirs), 0)
}

// This test calls ValidateAllTerraformModules on the Terratest root directory
func TestValidateAllTerraformModulesOnTerratest(t *testing.T) {
cwd, err := os.Getwd()
require.NoError(t, err)

projectRootDir := filepath.Join(cwd, "../..")

opts, optsErr := NewValidationOptions(projectRootDir, []string{}, []string{
"test/fixtures/terraform-with-plan-error",
"test/fixtures/terragrunt/terragrunt-with-plan-error",
"examples/terraform-backend-example",
})
require.NoError(t, optsErr)

ValidateAllTerraformModules(t, opts)
}

// Verify ExcludeDirs is working properly, by explicitly passing a list of two test fixture modules to exclude
// and ensuring at the end that they do not appear in the returned slice of sub directories to validate
// Then, re-run the function with no exclusions and ensure the excluded paths ARE returned in the result set when no
// exclusions are passed
func TestFindTerraformModulePathsInRootEWithResultsExclusion(t *testing.T) {

cwd, cwdErr := os.Getwd()
require.NoError(t, cwdErr)

projectRootDir := filepath.Join(cwd, "../..")

// First, call the FindTerraformModulePathsInRootE method with several exclusions
exclusions := []string{
filepath.Join("test", "fixtures", "terraform-output"),
filepath.Join("test", "fixtures", "terraform-output-map"),
}

opts, optsErr := NewValidationOptions(projectRootDir, []string{}, exclusions)
require.NoError(t, optsErr)

subDirs, err := FindTerraformModulePathsInRootE(opts)
require.NoError(t, err)
require.Greater(t, len(subDirs), 0)
// Ensure none of the excluded paths were returned by FindTerraformModulePathsInRootE
for _, exclusion := range exclusions {
assert.False(t, collections.ListContains(subDirs, filepath.Join(projectRootDir, exclusion)))
}

// Next, call the same function but this time without exclusions and ensure that the excluded paths
// exist in the non-excluded result set
optsWithoutExclusions, optswoErr := NewValidationOptions(projectRootDir, []string{}, []string{})
require.NoError(t, optswoErr)

subDirsWithoutExclusions, woExErr := FindTerraformModulePathsInRootE(optsWithoutExclusions)
require.NoError(t, woExErr)
require.Greater(t, len(subDirsWithoutExclusions), 0)
for _, exclusion := range exclusions {
assert.True(t, collections.ListContains(subDirsWithoutExclusions, filepath.Join(projectRootDir, exclusion)))
}
}