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