/
test_structure.go
220 lines (198 loc) · 10.2 KB
/
test_structure.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
package test_structure
import (
"fmt"
"os"
"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/opa"
"github.com/gruntwork-io/terratest/modules/terraform"
"github.com/gruntwork-io/terratest/modules/testing"
"github.com/stretchr/testify/require"
)
// SKIP_STAGE_ENV_VAR_PREFIX is the prefix used for skipping stage environment variables.
const SKIP_STAGE_ENV_VAR_PREFIX = "SKIP_"
// RunTestStage executes the given test stage (e.g., setup, teardown, validation) if an environment variable of the name
// `SKIP_<stageName>` (e.g., SKIP_teardown) is not set.
func RunTestStage(t testing.TestingT, stageName string, stage func()) {
envVarName := fmt.Sprintf("%s%s", SKIP_STAGE_ENV_VAR_PREFIX, stageName)
if os.Getenv(envVarName) == "" {
logger.Logf(t, "The '%s' environment variable is not set, so executing stage '%s'.", envVarName, stageName)
stage()
} else {
logger.Logf(t, "The '%s' environment variable is set, so skipping stage '%s'.", envVarName, stageName)
}
}
// SkipStageEnvVarSet returns true if an environment variable is set instructing Terratest to skip a test stage. This can be an easy way
// to tell if the tests are running in a local dev environment vs a CI server.
func SkipStageEnvVarSet() bool {
for _, environmentVariable := range os.Environ() {
if strings.HasPrefix(environmentVariable, SKIP_STAGE_ENV_VAR_PREFIX) {
return true
}
}
return false
}
// CopyTerraformFolderToTemp copies the given root folder to a randomly-named temp folder and return the path to the
// given terraform modules folder within the new temp root folder. This is useful when running multiple tests in
// parallel against the same set of Terraform files to ensure the tests don't overwrite each other's .terraform working
// directory and terraform.tfstate files. To ensure relative paths work, we copy over the entire root folder to a temp
// folder, and then return the path within that temp folder to the given terraform module dir, which is where the actual
// test will be running.
// For example, suppose you had the target terraform folder you want to test in "/examples/terraform-aws-example"
// relative to the repo root. If your tests reside in the "/test" relative to the root, then you will use this as
// follows:
//
// // Root folder where terraform files should be (relative to the test folder)
// rootFolder := ".."
//
// // Relative path to terraform module being tested from the root folder
// terraformFolderRelativeToRoot := "examples/terraform-aws-example"
//
// // Copy the terraform folder to a temp folder
// tempTestFolder := test_structure.CopyTerraformFolderToTemp(t, rootFolder, terraformFolderRelativeToRoot)
//
// // Make sure to use the temp test folder in the terraform options
// terraformOptions := &terraform.Options{
// TerraformDir: tempTestFolder,
// }
//
// Note that if any of the SKIP_<stage> environment variables is set, we assume this is a test in the local dev where
// there are no other concurrent tests running and we want to be able to cache test data between test stages, so in that
// case, we do NOT copy anything to a temp folder, and return the path to the original terraform module folder instead.
func CopyTerraformFolderToTemp(t testing.TestingT, rootFolder string, terraformModuleFolder string) string {
return CopyTerraformFolderToDest(t, rootFolder, terraformModuleFolder, os.TempDir())
}
// CopyTerraformFolderToDest copies the given root folder to a randomly-named temp folder and return the path to the
// given terraform modules folder within the new temp root folder. This is useful when running multiple tests in
// parallel against the same set of Terraform files to ensure the tests don't overwrite each other's .terraform working
// directory and terraform.tfstate files. To ensure relative paths work, we copy over the entire root folder to a temp
// folder, and then return the path within that temp folder to the given terraform module dir, which is where the actual
// test will be running.
// For example, suppose you had the target terraform folder you want to test in "/examples/terraform-aws-example"
// relative to the repo root. If your tests reside in the "/test" relative to the root, then you will use this as
// follows:
//
// // Destination for the copy of the files. In this example we are using the Azure Dev Ops variable
// // for the folder that is cleaned after each pipeline job.
// destRootFolder := os.Getenv("AGENT_TEMPDIRECTORY")
//
// // Root folder where terraform files should be (relative to the test folder)
// rootFolder := ".."
//
// // Relative path to terraform module being tested from the root folder
// terraformFolderRelativeToRoot := "examples/terraform-aws-example"
//
// // Copy the terraform folder to a temp folder
// tempTestFolder := test_structure.CopyTerraformFolderToTemp(t, rootFolder, terraformFolderRelativeToRoot, destRootFolder)
//
// // Make sure to use the temp test folder in the terraform options
// terraformOptions := &terraform.Options{
// TerraformDir: tempTestFolder,
// }
//
// Note that if any of the SKIP_<stage> environment variables is set, we assume this is a test in the local dev where
// there are no other concurrent tests running and we want to be able to cache test data between test stages, so in that
// case, we do NOT copy anything to a temp folder, and return the path to the original terraform module folder instead.
func CopyTerraformFolderToDest(t testing.TestingT, rootFolder string, terraformModuleFolder string, destRootFolder string) string {
if SkipStageEnvVarSet() {
logger.Logf(t, "A SKIP_XXX environment variable is set. Using original examples folder rather than a temp folder so we can cache data between stages for faster local testing.")
return filepath.Join(rootFolder, terraformModuleFolder)
}
fullTerraformModuleFolder := filepath.Join(rootFolder, terraformModuleFolder)
exists, err := files.FileExistsE(fullTerraformModuleFolder)
require.NoError(t, err)
if !exists {
t.Fatal(files.DirNotFoundError{Directory: fullTerraformModuleFolder})
}
tmpRootFolder, err := files.CopyTerraformFolderToDest(rootFolder, destRootFolder, cleanName(t.Name()))
if err != nil {
t.Fatal(err)
}
tmpTestFolder := filepath.Join(tmpRootFolder, terraformModuleFolder)
// Log temp folder so we can see it
logger.Logf(t, "Copied terraform folder %s to %s", fullTerraformModuleFolder, tmpTestFolder)
return tmpTestFolder
}
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
// Note that we have opted to place the ValidateAllTerraformModules function here instead of in the terraform package
// to avoid import cycling
func ValidateAllTerraformModules(t *go_test.T, opts *ValidationOptions) {
runValidateOnAllTerraformModules(
t,
opts,
func(t *go_test.T, fileType ValidateFileType, tfOpts *terraform.Options) {
if fileType == TG {
tfOpts.TerraformBinary = "terragrunt"
// First call init and terraform validate
terraform.InitAndValidate(t, tfOpts)
// Next, call terragrunt validate-inputs which will catch mis-aligned inputs provided via Terragrunt
terraform.ValidateInputs(t, tfOpts)
} else if fileType == TF {
terraform.InitAndValidate(t, tfOpts)
}
},
)
}
// OPAEvalAllTerraformModules automatically finds all folders specified in RootDir that contain .tf files and runs
// OPAEval in all of them. The behavior of this function is similar to ValidateAllTerraformModules. Refer to the docs of
// that function for more details.
func OPAEvalAllTerraformModules(
t *go_test.T,
opts *ValidationOptions,
opaEvalOpts *opa.EvalOptions,
resultQuery string,
) {
if opts.FileType != TF {
t.Fatalf("OPAEvalAllTerraformModules currently only works with Terraform modules")
}
runValidateOnAllTerraformModules(
t,
opts,
func(t *go_test.T, _ ValidateFileType, tfOpts *terraform.Options) {
terraform.OPAEval(t, tfOpts, opaEvalOpts, resultQuery)
},
)
}
// runValidateOnAllTerraformModules main driver for ValidateAllTerraformModules and OPAEvalAllTerraformModules. Refer to
// the function docs of ValidateAllTerraformModules for more details.
func runValidateOnAllTerraformModules(
t *go_test.T,
opts *ValidationOptions,
validationFunc func(t *go_test.T, fileType ValidateFileType, tfOps *terraform.Options),
) {
dirsToValidate, readErr := FindTerraformModulePathsInRootE(opts)
require.NoError(t, readErr)
for _, dir := range dirsToValidate {
dir := dir
t.Run(strings.TrimLeft(dir, "/"), func(t *go_test.T) {
// 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}
validationFunc(t, opts.FileType, tfOpts)
})
}
}