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  }