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