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  }