github.com/terraform-modules-krish/terratest@v0.29.0/modules/test-structure/save_test_data.go (about) 1 package test_structure 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "io/ioutil" 7 "os" 8 "path/filepath" 9 10 "github.com/terraform-modules-krish/terratest/modules/aws" 11 "github.com/terraform-modules-krish/terratest/modules/files" 12 "github.com/terraform-modules-krish/terratest/modules/k8s" 13 "github.com/terraform-modules-krish/terratest/modules/logger" 14 "github.com/terraform-modules-krish/terratest/modules/packer" 15 "github.com/terraform-modules-krish/terratest/modules/terraform" 16 "github.com/terraform-modules-krish/terratest/modules/testing" 17 "github.com/stretchr/testify/require" 18 ) 19 20 // SaveTerraformOptions serializes and saves TerraformOptions into the given folder. This allows you to create TerraformOptions during setup 21 // and to reuse that TerraformOptions later during validation and teardown. 22 func SaveTerraformOptions(t testing.TestingT, testFolder string, terraformOptions *terraform.Options) { 23 SaveTestData(t, formatTerraformOptionsPath(testFolder), terraformOptions) 24 } 25 26 // LoadTerraformOptions loads and unserializes TerraformOptions from the given folder. This allows you to reuse a TerraformOptions that was 27 // created during an earlier setup step in later validation and teardown steps. 28 func LoadTerraformOptions(t testing.TestingT, testFolder string) *terraform.Options { 29 var terraformOptions terraform.Options 30 LoadTestData(t, formatTerraformOptionsPath(testFolder), &terraformOptions) 31 return &terraformOptions 32 } 33 34 // formatTerraformOptionsPath formats a path to save TerraformOptions in the given folder. 35 func formatTerraformOptionsPath(testFolder string) string { 36 return FormatTestDataPath(testFolder, "TerraformOptions.json") 37 } 38 39 // SavePackerOptions serializes and saves PackerOptions into the given folder. This allows you to create PackerOptions during setup 40 // and to reuse that PackerOptions later during validation and teardown. 41 func SavePackerOptions(t testing.TestingT, testFolder string, packerOptions *packer.Options) { 42 SaveTestData(t, formatPackerOptionsPath(testFolder), packerOptions) 43 } 44 45 // LoadPackerOptions loads and unserializes PackerOptions from the given folder. This allows you to reuse a PackerOptions that was 46 // created during an earlier setup step in later validation and teardown steps. 47 func LoadPackerOptions(t testing.TestingT, testFolder string) *packer.Options { 48 var packerOptions packer.Options 49 LoadTestData(t, formatPackerOptionsPath(testFolder), &packerOptions) 50 return &packerOptions 51 } 52 53 // formatPackerOptionsPath formats a path to save PackerOptions in the given folder. 54 func formatPackerOptionsPath(testFolder string) string { 55 return FormatTestDataPath(testFolder, "PackerOptions.json") 56 } 57 58 // SaveEc2KeyPair serializes and saves an Ec2KeyPair into the given folder. This allows you to create an Ec2KeyPair during setup 59 // and to reuse that Ec2KeyPair later during validation and teardown. 60 func SaveEc2KeyPair(t testing.TestingT, testFolder string, keyPair *aws.Ec2Keypair) { 61 SaveTestData(t, formatEc2KeyPairPath(testFolder), keyPair) 62 } 63 64 // LoadEc2KeyPair loads and unserializes an Ec2KeyPair from the given folder. This allows you to reuse an Ec2KeyPair that was 65 // created during an earlier setup step in later validation and teardown steps. 66 func LoadEc2KeyPair(t testing.TestingT, testFolder string) *aws.Ec2Keypair { 67 var keyPair aws.Ec2Keypair 68 LoadTestData(t, formatEc2KeyPairPath(testFolder), &keyPair) 69 return &keyPair 70 } 71 72 // formatEc2KeyPairPath formats a path to save an Ec2KeyPair in the given folder. 73 func formatEc2KeyPairPath(testFolder string) string { 74 return FormatTestDataPath(testFolder, "Ec2KeyPair.json") 75 } 76 77 // SaveKubectlOptions serializes and saves KubectlOptions into the given folder. This allows you to create a KubectlOptions during setup 78 // and reuse that KubectlOptions later during validation and teardown. 79 func SaveKubectlOptions(t testing.TestingT, testFolder string, kubectlOptions *k8s.KubectlOptions) { 80 SaveTestData(t, formatKubectlOptionsPath(testFolder), kubectlOptions) 81 } 82 83 // LoadKubectlOptions loads and unserializes a KubectlOptions from the given folder. This allows you to reuse a KubectlOptions that was 84 // created during an earlier setup step in later validation and teardown steps. 85 func LoadKubectlOptions(t testing.TestingT, testFolder string) *k8s.KubectlOptions { 86 var kubectlOptions k8s.KubectlOptions 87 LoadTestData(t, formatKubectlOptionsPath(testFolder), &kubectlOptions) 88 return &kubectlOptions 89 } 90 91 // formatKubectlOptionsPath formats a path to save a KubectlOptions in the given folder. 92 func formatKubectlOptionsPath(testFolder string) string { 93 return FormatTestDataPath(testFolder, "KubectlOptions.json") 94 } 95 96 // SaveString serializes and saves a uniquely named string value into the given folder. This allows you to create one or more string 97 // values during one stage -- each with a unique name -- and to reuse those values during later stages. 98 func SaveString(t testing.TestingT, testFolder string, name string, val string) { 99 path := formatNamedTestDataPath(testFolder, name) 100 SaveTestData(t, path, val) 101 } 102 103 // LoadString loads and unserializes a uniquely named string value from the given folder. This allows you to reuse one or more string 104 // values that were created during an earlier setup step in later steps. 105 func LoadString(t testing.TestingT, testFolder string, name string) string { 106 var val string 107 LoadTestData(t, formatNamedTestDataPath(testFolder, name), &val) 108 return val 109 } 110 111 // SaveInt saves a uniquely named int value into the given folder. This allows you to create one or more int 112 // values during one stage -- each with a unique name -- and to reuse those values during later stages. 113 func SaveInt(t testing.TestingT, testFolder string, name string, val int) { 114 path := formatNamedTestDataPath(testFolder, name) 115 SaveTestData(t, path, val) 116 } 117 118 // LoadInt loads a uniquely named int value from the given folder. This allows you to reuse one or more int 119 // values that were created during an earlier setup step in later steps. 120 func LoadInt(t testing.TestingT, testFolder string, name string) int { 121 var val int 122 LoadTestData(t, formatNamedTestDataPath(testFolder, name), &val) 123 return val 124 } 125 126 // SaveArtifactID serializes and saves an Artifact ID into the given folder. This allows you to build an Artifact during setup and to reuse that 127 // Artifact later during validation and teardown. 128 func SaveArtifactID(t testing.TestingT, testFolder string, artifactID string) { 129 SaveString(t, testFolder, "Artifact", artifactID) 130 } 131 132 // LoadArtifactID loads and unserializes an Artifact ID from the given folder. This allows you to reuse an Artifact that was created during an 133 // earlier setup step in later validation and teardown steps. 134 func LoadArtifactID(t testing.TestingT, testFolder string) string { 135 return LoadString(t, testFolder, "Artifact") 136 } 137 138 // SaveAmiId serializes and saves an AMI ID into the given folder. This allows you to build an AMI during setup and to reuse that 139 // AMI later during validation and teardown. 140 // 141 // Deprecated: Use SaveArtifactID instead. 142 func SaveAmiId(t testing.TestingT, testFolder string, amiId string) { 143 SaveString(t, testFolder, "AMI", amiId) 144 } 145 146 // LoadAmiId loads and unserializes an AMI ID from the given folder. This allows you to reuse an AMI that was created during an 147 // earlier setup step in later validation and teardown steps. 148 // 149 // Deprecated: Use LoadArtifactID instead. 150 func LoadAmiId(t testing.TestingT, testFolder string) string { 151 return LoadString(t, testFolder, "AMI") 152 } 153 154 // formatNamedTestDataPath formats a path to save an arbitrary named value in the given folder. 155 func formatNamedTestDataPath(testFolder string, name string) string { 156 filename := fmt.Sprintf("%s.json", name) 157 return FormatTestDataPath(testFolder, filename) 158 } 159 160 // FormatTestDataPath formats a path to save test data. 161 func FormatTestDataPath(testFolder string, filename string) string { 162 return filepath.Join(testFolder, ".test-data", filename) 163 } 164 165 // SaveTestData serializes and saves a value used at test time to the given path. This allows you to create some sort of test data 166 // (e.g., TerraformOptions) during setup and to reuse this data later during validation and teardown. 167 func SaveTestData(t testing.TestingT, path string, value interface{}) { 168 logger.Logf(t, "Storing test data in %s so it can be reused later", path) 169 170 if IsTestDataPresent(t, path) { 171 logger.Logf(t, "[WARNING] The named test data at path %s is non-empty. Save operation will overwrite existing value with \"%v\".\n.", path, value) 172 } 173 174 bytes, err := json.Marshal(value) 175 if err != nil { 176 t.Fatalf("Failed to convert value %s to JSON: %v", path, err) 177 } 178 179 logger.Logf(t, "Marshalled JSON: %s", string(bytes)) 180 181 parentDir := filepath.Dir(path) 182 if err := os.MkdirAll(parentDir, 0777); err != nil { 183 t.Fatalf("Failed to create folder %s: %v", parentDir, err) 184 } 185 186 if err := ioutil.WriteFile(path, bytes, 0644); err != nil { 187 t.Fatalf("Failed to save value %s: %v", path, err) 188 } 189 } 190 191 // LoadTestData loads and unserializes a value stored at the given path. The value should be a pointer to a struct into which the 192 // value will be deserialized. This allows you to reuse some sort of test data (e.g., TerraformOptions) from earlier 193 // setup steps in later validation and teardown steps. 194 func LoadTestData(t testing.TestingT, path string, value interface{}) { 195 logger.Logf(t, "Loading test data from %s", path) 196 197 bytes, err := ioutil.ReadFile(path) 198 if err != nil { 199 t.Fatalf("Failed to load value from %s: %v", path, err) 200 } 201 202 if err := json.Unmarshal(bytes, value); err != nil { 203 t.Fatalf("Failed to parse JSON for value %s: %v", path, err) 204 } 205 } 206 207 // IsTestDataPresent returns true if a file exists at $path and the test data there is non-empty. 208 func IsTestDataPresent(t testing.TestingT, path string) bool { 209 exists, err := files.FileExistsE(path) 210 if err != nil { 211 t.Fatalf("Failed to load test data from %s due to unexpected error: %v", path, err) 212 } 213 if !exists { 214 return false 215 } 216 217 bytes, err := ioutil.ReadFile(path) 218 219 if err != nil { 220 t.Fatalf("Failed to load test data from %s due to unexpected error: %v", path, err) 221 } 222 223 if isEmptyJSON(t, bytes) { 224 return false 225 } 226 227 return true 228 } 229 230 // isEmptyJSON returns true if the given bytes are empty, or in a valid JSON format that can reasonably be considered empty. 231 // The types used are based on the type possibilities listed at https://golang.org/src/encoding/json/decode.go?s=4062:4110#L51 232 func isEmptyJSON(t testing.TestingT, bytes []byte) bool { 233 var value interface{} 234 235 if len(bytes) == 0 { 236 return true 237 } 238 239 if err := json.Unmarshal(bytes, &value); err != nil { 240 t.Fatalf("Failed to parse JSON while testing whether it is empty: %v", err) 241 } 242 243 if value == nil { 244 return true 245 } 246 247 valueBool, ok := value.(bool) 248 if ok && !valueBool { 249 return true 250 } 251 252 valueFloat64, ok := value.(float64) 253 if ok && valueFloat64 == 0 { 254 return true 255 } 256 257 valueString, ok := value.(string) 258 if ok && valueString == "" { 259 return true 260 } 261 262 valueSlice, ok := value.([]interface{}) 263 if ok && len(valueSlice) == 0 { 264 return true 265 } 266 267 valueMap, ok := value.(map[string]interface{}) 268 if ok && len(valueMap) == 0 { 269 return true 270 } 271 272 return false 273 } 274 275 // CleanupTestData cleans up the test data at the given path. 276 func CleanupTestData(t testing.TestingT, path string) { 277 if files.FileExists(path) { 278 logger.Logf(t, "Cleaning up test data from %s", path) 279 if err := os.Remove(path); err != nil { 280 t.Fatalf("Failed to clean up file at %s: %v", path, err) 281 } 282 } else { 283 logger.Logf(t, "%s does not exist. Nothing to cleanup.", path) 284 } 285 } 286 287 // CleanupTestDataFolder cleans up the .test-data folder inside the given folder. 288 // If there are any errors, fail the test. 289 func CleanupTestDataFolder(t testing.TestingT, path string) { 290 err := CleanupTestDataFolderE(t, path) 291 require.NoError(t, err) 292 } 293 294 // CleanupTestDataFolderE cleans up the .test-data folder inside the given folder. 295 func CleanupTestDataFolderE(t testing.TestingT, path string) error { 296 path = filepath.Join(path, ".test-data") 297 exists, err := files.FileExistsE(path) 298 if err != nil { 299 logger.Logf(t, "Failed to clean up test data folder at %s: %v", path, err) 300 return err 301 } 302 303 if !exists { 304 logger.Logf(t, "%s does not exist. Nothing to cleanup.", path) 305 return nil 306 } 307 if err := os.RemoveAll(path); err != nil { 308 logger.Logf(t, "Failed to clean up test data folder at %s: %v", path, err) 309 return err 310 } 311 312 return nil 313 }