From a59540c5c9384edca58ecf4701462b70f4181272 Mon Sep 17 00:00:00 2001 From: Kyle Robertson Date: Wed, 9 Dec 2020 16:25:24 -0500 Subject: [PATCH 1/9] adding function to extract terraform vars from var file --- go.mod | 4 ++ go.sum | 1 + modules/terraform/options.go | 35 ++++++++++++ modules/terraform/options_test.go | 95 +++++++++++++++++++++++++++++++ 4 files changed, 135 insertions(+) create mode 100644 modules/terraform/options_test.go diff --git a/go.mod b/go.mod index 5d978987a..34e404639 100644 --- a/go.mod +++ b/go.mod @@ -21,7 +21,11 @@ require ( github.com/google/go-containerregistry v0.0.0-20200110202235-f4fb41bf00a3 github.com/google/uuid v1.1.1 github.com/gruntwork-io/gruntwork-cli v0.7.0 +<<<<<<< HEAD github.com/hashicorp/go-multierror v1.1.0 +======= + github.com/hashicorp/hcl v1.0.0 +>>>>>>> adding function to extract terraform vars from var file github.com/imdario/mergo v0.3.7 // indirect github.com/jinzhu/copier v0.0.0-20190924061706-b57f9002281a github.com/jstemmer/go-junit-report v0.9.1 diff --git a/go.sum b/go.sum index c528a9d9f..662ac7b26 100644 --- a/go.sum +++ b/go.sum @@ -250,6 +250,7 @@ github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+ github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.3/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= +github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= diff --git a/modules/terraform/options.go b/modules/terraform/options.go index 048e9b4a1..4460d5b45 100644 --- a/modules/terraform/options.go +++ b/modules/terraform/options.go @@ -1,11 +1,15 @@ package terraform import ( + "errors" + "fmt" + "io/ioutil" "testing" "time" "github.com/gruntwork-io/terratest/modules/logger" "github.com/gruntwork-io/terratest/modules/ssh" + "github.com/hashicorp/hcl" "github.com/jinzhu/copier" "github.com/stretchr/testify/require" ) @@ -103,3 +107,34 @@ func WithDefaultRetryableErrors(t *testing.T, originalOptions *Options) *Options return newOptions } + +// GetVariablesFromVarFiles Parses all data from each input file provided in VarFile and returns them as a key-value map, +// in the order that they appear in the VarFiles array +// Note that the values in the map are interface{} type so you will need to convert them to the expected data type +func GetVariablesFromVarFiles(option *Options) ([]map[string]interface{}, error) { + variableMaps := []map[string]interface{}{} + + if len(option.VarFiles) == 0 { + return variableMaps, nil + } + + for _, file := range(option.VarFiles) { + fileContents, err := ioutil.ReadFile(file) + + if err != nil { + return nil, err + } + + var parsedFile map[string]interface{} + err = hcl.Decode(&parsedFile, string(fileContents)) + + if err != nil{ + decodeErr := errors.New(fmt.Sprintf("%s - %s", file, err.Error())) + return nil, decodeErr + } + + variableMaps = append(variableMaps, parsedFile) + } + + return variableMaps, nil +} diff --git a/modules/terraform/options_test.go b/modules/terraform/options_test.go new file mode 100644 index 000000000..d45f1694c --- /dev/null +++ b/modules/terraform/options_test.go @@ -0,0 +1,95 @@ +package terraform + +import ( + "fmt" + "io/ioutil" + "os" + "testing" + + "github.com/gruntwork-io/terratest/modules/random" + "github.com/stretchr/testify/require" +) + +func TestGetVariablesFromVarFiles(t *testing.T) { + randomFileName := fmt.Sprintf("./%s.tfvars", random.UniqueId()) + randomFileName2 := fmt.Sprintf("./%s.tfvars", random.UniqueId()) + + testHcl := []byte(` + aws_region = "us-east-2" + aws_account_id = "111111111111" + tags = { + foo = "bar" + } + list = ["item1"]`) + + testHcl2 := []byte(` + aws_region = "us-west-2"`) + + WriteFile(t, randomFileName, testHcl) + defer os.Remove(randomFileName) + + WriteFile(t, randomFileName2, testHcl2) + defer os.Remove(randomFileName2) + + varMap, err := GetVariablesFromVarFiles(&Options{ + VarFiles: []string{randomFileName, randomFileName2}, + }) + + if err != nil { + fmt.Println(err.Error()) + t.FailNow() + } + + require.Equal(t, 2, len(varMap)) + require.Equal(t, "us-east-2", varMap[0]["aws_region"]) + require.Equal(t, "111111111111", varMap[0]["aws_account_id"]) + require.Equal(t, map[string]interface{}{ "foo": "bar", }, varMap[0]["tags"].([]map[string]interface{})[0]) + require.Equal(t, []interface{}{ "item1" }, varMap[0]["list"].([]interface{})) + require.Equal(t, "us-west-2", varMap[1]["aws_region"]) +} + +func TestGetVariablesFromVarFilesNoFileError(t *testing.T) { + _, err := GetVariablesFromVarFiles(&Options{ + VarFiles: []string{"thisdoesntexist"}, + }) + + require.Equal(t, "open thisdoesntexist: no such file or directory", err.Error()) +} + +func TestGetVariablesFromVarFilesBadFile(t *testing.T) { + randomFileName := fmt.Sprintf("./%s.tfvars", random.UniqueId()) + testHcl := []byte(` + thiswillnotwork`) + + err := ioutil.WriteFile(randomFileName, testHcl, 0644) + + if err != nil { + fmt.Println(err.Error()) + t.FailNow() + } + + defer os.Remove(randomFileName) + + _, err = GetVariablesFromVarFiles(&Options{ + VarFiles: []string{randomFileName}, + }) + + if err == nil { + t.FailNow() + } + + // HCL library could change their error string, so we are only testing the error string contains what we add to it + require.Regexp(t, fmt.Sprintf("^%s - ", randomFileName), err.Error()) + +} + +// Helper function to write a file to the filesystem +// Will immediately fail the test if it could not write the file +func WriteFile(t *testing.T, fileName string, bytes []byte) { + err := ioutil.WriteFile(fileName, bytes, 0644) + + if err != nil { + fmt.Println(err.Error()) + t.FailNow() + } +} \ No newline at end of file From 0207e62e8524076e43b63b2cbdb5246e4675443e Mon Sep 17 00:00:00 2001 From: Kyle Robertson Date: Wed, 23 Dec 2020 15:50:32 -0500 Subject: [PATCH 2/9] refactored extracting vars from var file to work more like the output functionality --- modules/terraform/errors.go | 18 +++++ modules/terraform/options.go | 131 ++++++++++++++++++++++++++++++----- 2 files changed, 130 insertions(+), 19 deletions(-) diff --git a/modules/terraform/errors.go b/modules/terraform/errors.go index 2e0e668b9..ad64d33af 100644 --- a/modules/terraform/errors.go +++ b/modules/terraform/errors.go @@ -56,3 +56,21 @@ type UnexpectedOutputType struct { func (err UnexpectedOutputType) Error() string { return fmt.Sprintf("Expected output '%s' to be of type '%s' but got '%s'", err.Key, err.ExpectedType, err.ActualType) } + +// VarFileNotFound is an error that occurs when a var file cannot be found in an option's VarFile list +type VarFileNotFound struct { + Path string +} + +func (err VarFileNotFound) Error() string { + return fmt.Sprintf("Could not resolve var file %s", err.Path) +} + +type HclDecodeError struct { + FilePath string + ErrorText string +} + +func (err HclDecodeError) Error() string { + return fmt.Sprintf("%s - %s", err.FilePath, err.ErrorText) +} diff --git a/modules/terraform/options.go b/modules/terraform/options.go index 4460d5b45..1d31c8634 100644 --- a/modules/terraform/options.go +++ b/modules/terraform/options.go @@ -1,9 +1,9 @@ package terraform import ( - "errors" "fmt" "io/ioutil" + "reflect" "testing" "time" @@ -108,33 +108,126 @@ func WithDefaultRetryableErrors(t *testing.T, originalOptions *Options) *Options return newOptions } -// GetVariablesFromVarFiles Parses all data from each input file provided in VarFile and returns them as a key-value map, -// in the order that they appear in the VarFiles array +// GetVariableAsStringFromVarFile Gets the string represention of a variable from a provided input file found in VarFile +// For list or map +func GetVariableAsStringFromVarFile(t *testing.T, option *Options, fileName string, key string) string { + result, err := GetVariableAsStringFromVarFileE(option, fileName, key) + require.NoError(t, err) + + return result +} + +func GetVariableAsStringFromVarFileE(option *Options, fileName string, key string) (string, error) { + variables, err := GetAllVariablesFromVarFileE(option, fileName) + + if err != nil { + return "", err + } + + variable, exists := variables[key] + + if !exists { + return "", OutputKeyNotFound(key) + } + + return fmt.Sprintf("%v", variable), nil +} + +func GetVariableAsMapFromVarFile(t *testing.T, option *Options, fileName string, key string) map[string]string { + result, err := GetVariableAsMapFromVarFileE(option, fileName, key) + require.NoError(t, err) + + return result +} + +func GetVariableAsMapFromVarFileE(option *Options, fileName string, key string) (map[string]string, error) { + resultMap := make(map[string]string) + variables, err := GetAllVariablesFromVarFileE(option, fileName) + + if err != nil { + return nil, err + } + + if reflect.TypeOf(variables[key]).String() != "[]map[string]interface {}" { + return nil, UnexpectedOutputType{Key: key, ExpectedType: "map", ActualType: reflect.TypeOf(variables[key]).String()} + } + + mapKeys := variables[key].([]map[string]interface{})[0] + + for mapKey, mapVal := range mapKeys { + resultMap[mapKey] = fmt.Sprintf("%v", mapVal) + } + + return resultMap, nil +} + +func GetVariableAsListFromVarFile(t *testing.T, option *Options, fileName string, key string) []string { + result, err := GetVariableAsListFromVarFileE(option, fileName, key) + require.NoError(t, err) + + return result +} + +func GetVariableAsListFromVarFileE(option *Options, fileName string, key string) ([]string, error) { + resultArray := []string{} + variables, err := GetAllVariablesFromVarFileE(option, fileName) + + if err != nil { + return nil, err + } + + if reflect.TypeOf(variables[key]).String() != "[]interface {}" { + return nil, UnexpectedOutputType{Key: key, ExpectedType: "list", ActualType: reflect.TypeOf(variables[key]).String()} + } + + for _, item := range variables[key].([]interface{}) { + resultArray = append(resultArray, fmt.Sprintf("%v", item)) + } + + return resultArray, nil +} + +// GetVariablesFromVarFiles Parses all data from a provided input file found in VarFile and returns them as a key-value map // Note that the values in the map are interface{} type so you will need to convert them to the expected data type -func GetVariablesFromVarFiles(option *Options) ([]map[string]interface{}, error) { - variableMaps := []map[string]interface{}{} +func GetAllVariablesFromVarFile(t *testing.T, option *Options, fileName string) map[string]interface{} { + variableMaps, err := GetAllVariablesFromVarFileE(option, fileName) + require.NoError(t, err) + + return variableMaps +} +// GetVariablesFromVarFilesE Parses all data from a provided input file found ind in VarFile and returns them as a key-value map +// Note that the values in the map are interface{} type so you will need to convert them to the expected data type +func GetAllVariablesFromVarFileE(option *Options, fileName string) (map[string]interface{}, error) { + variableMap := map[string]interface{}{} + fileIndex := -1 if len(option.VarFiles) == 0 { - return variableMaps, nil + return variableMap, nil } - for _, file := range(option.VarFiles) { - fileContents, err := ioutil.ReadFile(file) - - if err != nil { - return nil, err + for index, file := range option.VarFiles { + if file == fileName { + fileIndex = index + break } + } - var parsedFile map[string]interface{} - err = hcl.Decode(&parsedFile, string(fileContents)) + if fileIndex == -1 { + return nil, VarFileNotFound{Path: fileName} + } - if err != nil{ - decodeErr := errors.New(fmt.Sprintf("%s - %s", file, err.Error())) - return nil, decodeErr - } + fileContents, err := ioutil.ReadFile(option.VarFiles[fileIndex]) + + if err != nil { + return nil, err + } + + err = hcl.Decode(&variableMap, string(fileContents)) - variableMaps = append(variableMaps, parsedFile) + if err != nil { + return nil, HclDecodeError{FilePath: option.VarFiles[fileIndex], ErrorText: err.Error()} } + - return variableMaps, nil + return variableMap, nil } From aa128a1e92b8ffbac1166b11284573f11137b880 Mon Sep 17 00:00:00 2001 From: "jsakyle@gmail.com" Date: Sun, 10 Jan 2021 22:41:41 -0500 Subject: [PATCH 3/9] adding testing and comments --- modules/terraform/errors.go | 10 +- modules/terraform/options.go | 53 ++++--- modules/terraform/options_test.go | 245 ++++++++++++++++++++++++++---- 3 files changed, 259 insertions(+), 49 deletions(-) diff --git a/modules/terraform/errors.go b/modules/terraform/errors.go index ad64d33af..c446cec03 100644 --- a/modules/terraform/errors.go +++ b/modules/terraform/errors.go @@ -66,8 +66,16 @@ func (err VarFileNotFound) Error() string { return fmt.Sprintf("Could not resolve var file %s", err.Path) } +// InputFileKeyNotFound occurs when tfvar file does not contain a value for the key +// specified in the function call +type InputFileKeyNotFound string + +func (err InputFileKeyNotFound) Error() string { + return fmt.Sprintf("tfvar file doesn't contain a value for the key %q", string(err)) +} + type HclDecodeError struct { - FilePath string + FilePath string ErrorText string } diff --git a/modules/terraform/options.go b/modules/terraform/options.go index 1d31c8634..602fb1e69 100644 --- a/modules/terraform/options.go +++ b/modules/terraform/options.go @@ -109,16 +109,19 @@ func WithDefaultRetryableErrors(t *testing.T, originalOptions *Options) *Options } // GetVariableAsStringFromVarFile Gets the string represention of a variable from a provided input file found in VarFile -// For list or map +// For list or map, use GetVariableAsListFromVarFile or GetVariableAsMapFromVarFile, respectively. func GetVariableAsStringFromVarFile(t *testing.T, option *Options, fileName string, key string) string { - result, err := GetVariableAsStringFromVarFileE(option, fileName, key) + result, err := GetVariableAsStringFromVarFileE(t, option, fileName, key) require.NoError(t, err) return result } -func GetVariableAsStringFromVarFileE(option *Options, fileName string, key string) (string, error) { - variables, err := GetAllVariablesFromVarFileE(option, fileName) +// GetVariableAsStringFromVarFileE Gets the string represention of a variable from a provided input file found in VarFile +// Will return an error if GetAllVariablesFromVarFileE returns an error or the key provided does not exist in the file. +// For list or map, use GetVariableAsListFromVarFile or GetVariableAsMapFromVarFile, respectively. +func GetVariableAsStringFromVarFileE(t *testing.T, option *Options, fileName string, key string) (string, error) { + variables, err := GetAllVariablesFromVarFileE(t, option, fileName) if err != nil { return "", err @@ -127,27 +130,36 @@ func GetVariableAsStringFromVarFileE(option *Options, fileName string, key strin variable, exists := variables[key] if !exists { - return "", OutputKeyNotFound(key) + return "", InputFileKeyNotFound(key) } return fmt.Sprintf("%v", variable), nil } +// GetVariableAsMapFromVarFile Gets the map represention of a variable from a provided input file found in VarFile func GetVariableAsMapFromVarFile(t *testing.T, option *Options, fileName string, key string) map[string]string { - result, err := GetVariableAsMapFromVarFileE(option, fileName, key) + result, err := GetVariableAsMapFromVarFileE(t, option, fileName, key) require.NoError(t, err) return result } -func GetVariableAsMapFromVarFileE(option *Options, fileName string, key string) (map[string]string, error) { +// GetVariableAsMapFromVarFileE Gets the map represention of a variable from a provided input file found in VarFile +// Returns an error if GetAllVariablesFromVarFileE returns an error, the key provided does not exist, or the value associated with the key is not a map +func GetVariableAsMapFromVarFileE(t *testing.T, option *Options, fileName string, key string) (map[string]string, error) { resultMap := make(map[string]string) - variables, err := GetAllVariablesFromVarFileE(option, fileName) + variables, err := GetAllVariablesFromVarFileE(t, option, fileName) if err != nil { return nil, err } + _, exists := variables[key] + + if !exists { + return nil, InputFileKeyNotFound(key) + } + if reflect.TypeOf(variables[key]).String() != "[]map[string]interface {}" { return nil, UnexpectedOutputType{Key: key, ExpectedType: "map", ActualType: reflect.TypeOf(variables[key]).String()} } @@ -161,21 +173,28 @@ func GetVariableAsMapFromVarFileE(option *Options, fileName string, key string) return resultMap, nil } +// GetVariableAsListFromVarFile Gets the string list represention of a variable from a provided input file found in VarFile func GetVariableAsListFromVarFile(t *testing.T, option *Options, fileName string, key string) []string { - result, err := GetVariableAsListFromVarFileE(option, fileName, key) + result, err := GetVariableAsListFromVarFileE(t, option, fileName, key) require.NoError(t, err) return result } -func GetVariableAsListFromVarFileE(option *Options, fileName string, key string) ([]string, error) { +// GetVariableAsListFromVarFileE Gets the string list represention of a variable from a provided input file found in VarFile +// Will return error if GetAllVariablesFromVarFileE returns an error, the key provided does not exist, or the value associated with the key is not a list +func GetVariableAsListFromVarFileE(t *testing.T, option *Options, fileName string, key string) ([]string, error) { resultArray := []string{} - variables, err := GetAllVariablesFromVarFileE(option, fileName) + variables, err := GetAllVariablesFromVarFileE(t, option, fileName) if err != nil { return nil, err } + if _, exists := variables[key]; !exists { + return nil, InputFileKeyNotFound(key) + } + if reflect.TypeOf(variables[key]).String() != "[]interface {}" { return nil, UnexpectedOutputType{Key: key, ExpectedType: "list", ActualType: reflect.TypeOf(variables[key]).String()} } @@ -187,18 +206,17 @@ func GetVariableAsListFromVarFileE(option *Options, fileName string, key string) return resultArray, nil } -// GetVariablesFromVarFiles Parses all data from a provided input file found in VarFile and returns them as a key-value map -// Note that the values in the map are interface{} type so you will need to convert them to the expected data type +// GetAllVariablesFromVarFile Parses all data from a provided input file found in VarFile and returns them as a key-value map func GetAllVariablesFromVarFile(t *testing.T, option *Options, fileName string) map[string]interface{} { - variableMaps, err := GetAllVariablesFromVarFileE(option, fileName) + variableMaps, err := GetAllVariablesFromVarFileE(t, option, fileName) require.NoError(t, err) return variableMaps } -// GetVariablesFromVarFilesE Parses all data from a provided input file found ind in VarFile and returns them as a key-value map -// Note that the values in the map are interface{} type so you will need to convert them to the expected data type -func GetAllVariablesFromVarFileE(option *Options, fileName string) (map[string]interface{}, error) { +// GetAllVariablesFromVarFileE Parses all data from a provided input file found ind in VarFile and returns them as a key-value map +// Retursn an error if the specified file does not exist, the specified file is not readable, or the specified file cannot be decoded from HCL +func GetAllVariablesFromVarFileE(t *testing.T, option *Options, fileName string) (map[string]interface{}, error) { variableMap := map[string]interface{}{} fileIndex := -1 if len(option.VarFiles) == 0 { @@ -227,7 +245,6 @@ func GetAllVariablesFromVarFileE(option *Options, fileName string) (map[string]i if err != nil { return nil, HclDecodeError{FilePath: option.VarFiles[fileIndex], ErrorText: err.Error()} } - return variableMap, nil } diff --git a/modules/terraform/options_test.go b/modules/terraform/options_test.go index d45f1694c..8483192ee 100644 --- a/modules/terraform/options_test.go +++ b/modules/terraform/options_test.go @@ -10,57 +10,212 @@ import ( "github.com/stretchr/testify/require" ) -func TestGetVariablesFromVarFiles(t *testing.T) { +func TestGetVariablesFromVarFilesAsString(t *testing.T) { randomFileName := fmt.Sprintf("./%s.tfvars", random.UniqueId()) - randomFileName2 := fmt.Sprintf("./%s.tfvars", random.UniqueId()) testHcl := []byte(` aws_region = "us-east-2" aws_account_id = "111111111111" + number_type = 2 + boolean_type = true tags = { foo = "bar" } list = ["item1"]`) - testHcl2 := []byte(` - aws_region = "us-west-2"`) - WriteFile(t, randomFileName, testHcl) defer os.Remove(randomFileName) - WriteFile(t, randomFileName2, testHcl2) - defer os.Remove(randomFileName2) + stringVal := GetVariableAsStringFromVarFile(t, &Options{ + VarFiles: []string{randomFileName}, + }, randomFileName, "aws_region") - varMap, err := GetVariablesFromVarFiles(&Options{ - VarFiles: []string{randomFileName, randomFileName2}, - }) + boolString := GetVariableAsStringFromVarFile(t, &Options{ + VarFiles: []string{randomFileName}, + }, randomFileName, "boolean_type") - if err != nil { - fmt.Println(err.Error()) - t.FailNow() - } + numString := GetVariableAsStringFromVarFile(t, &Options{ + VarFiles: []string{randomFileName}, + }, randomFileName, "number_type") + + require.Equal(t, "us-east-2", stringVal) + require.Equal(t, "true", boolString) + require.Equal(t, "2", numString) + +} + +func TestGetVariablesFromVarFilesAsStringKeyDoesNotExist(t *testing.T) { + randomFileName := fmt.Sprintf("./%s.tfvars", random.UniqueId()) + + testHcl := []byte(` + aws_region = "us-east-2" + aws_account_id = "111111111111" + tags = { + foo = "bar" + } + list = ["item1"]`) + + WriteFile(t, randomFileName, testHcl) + defer os.Remove(randomFileName) + + _, err := GetVariableAsStringFromVarFileE(t, &Options{ + VarFiles: []string{randomFileName}, + }, randomFileName, "badkey") + + require.Error(t, err) +} + +func TestGetVariableAsMapFromVarFile(t *testing.T) { + randomFileName := fmt.Sprintf("./%s.tfvars", random.UniqueId()) + expected := make(map[string]string) + expected["foo"] = "bar" + + testHcl := []byte(` + aws_region = "us-east-2" + aws_account_id = "111111111111" + tags = { + foo = "bar" + } + list = ["item1"]`) + + WriteFile(t, randomFileName, testHcl) + defer os.Remove(randomFileName) + + val := GetVariableAsMapFromVarFile(t, &Options{ + VarFiles: []string{randomFileName}, + }, randomFileName, "tags") + + require.Equal(t, expected, val) +} + +func TestGetVariableAsMapFromVarFileNotMap(t *testing.T) { + randomFileName := fmt.Sprintf("./%s.tfvars", random.UniqueId()) + + testHcl := []byte(` + aws_region = "us-east-2" + aws_account_id = "111111111111" + tags = { + foo = "bar" + } + list = ["item1"]`) + + WriteFile(t, randomFileName, testHcl) + defer os.Remove(randomFileName) + + _, err := GetVariableAsMapFromVarFileE(t, &Options{ + VarFiles: []string{randomFileName}, + }, randomFileName, "aws_region") + + require.Error(t, err) +} + +func TestGetVariableAsMapFromVarFileKeyDoesNotExist(t *testing.T) { + randomFileName := fmt.Sprintf("./%s.tfvars", random.UniqueId()) + + testHcl := []byte(` + aws_region = "us-east-2" + aws_account_id = "111111111111" + tags = { + foo = "bar" + } + list = ["item1"]`) + + WriteFile(t, randomFileName, testHcl) + defer os.Remove(randomFileName) + + _, err := GetVariableAsMapFromVarFileE(t, &Options{ + VarFiles: []string{randomFileName}, + }, randomFileName, "badkey") + + require.Error(t, err) +} + +func TestGetVariableAsListFromVarFile(t *testing.T) { + randomFileName := fmt.Sprintf("./%s.tfvars", random.UniqueId()) + expected := []string{"item1"} + + testHcl := []byte(` + aws_region = "us-east-2" + aws_account_id = "111111111111" + tags = { + foo = "bar" + } + list = ["item1"]`) + + WriteFile(t, randomFileName, testHcl) + defer os.Remove(randomFileName) + + val := GetVariableAsListFromVarFile(t, &Options{ + VarFiles: []string{randomFileName}, + }, randomFileName, "list") - require.Equal(t, 2, len(varMap)) - require.Equal(t, "us-east-2", varMap[0]["aws_region"]) - require.Equal(t, "111111111111", varMap[0]["aws_account_id"]) - require.Equal(t, map[string]interface{}{ "foo": "bar", }, varMap[0]["tags"].([]map[string]interface{})[0]) - require.Equal(t, []interface{}{ "item1" }, varMap[0]["list"].([]interface{})) - require.Equal(t, "us-west-2", varMap[1]["aws_region"]) + require.Equal(t, expected, val) } -func TestGetVariablesFromVarFilesNoFileError(t *testing.T) { - _, err := GetVariablesFromVarFiles(&Options{ - VarFiles: []string{"thisdoesntexist"}, - }) +func TestGetVariableAsListNotList(t *testing.T) { + randomFileName := fmt.Sprintf("./%s.tfvars", random.UniqueId()) - require.Equal(t, "open thisdoesntexist: no such file or directory", err.Error()) + testHcl := []byte(` + aws_region = "us-east-2" + aws_account_id = "111111111111" + tags = { + foo = "bar" + } + list = ["item1"]`) + + WriteFile(t, randomFileName, testHcl) + defer os.Remove(randomFileName) + + _, err := GetVariableAsListFromVarFileE(t, &Options{ + VarFiles: []string{randomFileName}, + }, randomFileName, "tags") + + require.Error(t, err) } -func TestGetVariablesFromVarFilesBadFile(t *testing.T) { +func TestGetVariableAsListKeyDoesNotExist(t *testing.T) { + randomFileName := fmt.Sprintf("./%s.tfvars", random.UniqueId()) + + testHcl := []byte(` + aws_region = "us-east-2" + aws_account_id = "111111111111" + tags = { + foo = "bar" + } + list = ["item1"]`) + + WriteFile(t, randomFileName, testHcl) + defer os.Remove(randomFileName) + + _, err := GetVariableAsListFromVarFileE(t, &Options{ + VarFiles: []string{randomFileName}, + }, randomFileName, "badkey") + + require.Error(t, err) +} + +func TestGetAllVariablesFromVarFileENotInVarFiles(t *testing.T) { + _, err := GetAllVariablesFromVarFileE(t, &Options{ + VarFiles: []string{"filea"}, + }, "fileb") + + //require.Equal(t, "open thisdoesntexist: no such file or directory", err.Error()) + require.Error(t, err) +} + +func TestGetAllVariablesFromVarFileEFileDoesNotExist(t *testing.T) { + _, err := GetAllVariablesFromVarFileE(t, &Options{ + VarFiles: []string{"filea"}, + }, "filea") + + require.Equal(t, "open filea: no such file or directory", err.Error()) +} + +func TestGetAllVariablesFromVarFileBadFile(t *testing.T) { randomFileName := fmt.Sprintf("./%s.tfvars", random.UniqueId()) testHcl := []byte(` thiswillnotwork`) - + err := ioutil.WriteFile(randomFileName, testHcl, 0644) if err != nil { @@ -70,9 +225,9 @@ func TestGetVariablesFromVarFilesBadFile(t *testing.T) { defer os.Remove(randomFileName) - _, err = GetVariablesFromVarFiles(&Options{ + _, err = GetAllVariablesFromVarFileE(t, &Options{ VarFiles: []string{randomFileName}, - }) + }, randomFileName) if err == nil { t.FailNow() @@ -83,6 +238,36 @@ func TestGetVariablesFromVarFilesBadFile(t *testing.T) { } +func TestGetAllVariablesFromVarFile(t *testing.T) { + randomFileName := fmt.Sprintf("./%s.tfvars", random.UniqueId()) + testHcl := []byte(` + aws_region = "us-east-2" + `) + + err := ioutil.WriteFile(randomFileName, testHcl, 0644) + + if err != nil { + fmt.Println(err.Error()) + t.FailNow() + } + + defer os.Remove(randomFileName) + + val, err := GetAllVariablesFromVarFileE(t, &Options{ + VarFiles: []string{randomFileName}, + }, randomFileName) + + if err != nil { + t.FailNow() + } + + expected := make(map[string]interface{}) + expected["aws_region"] = "us-east-2" + + require.Equal(t, expected, val) + +} + // Helper function to write a file to the filesystem // Will immediately fail the test if it could not write the file func WriteFile(t *testing.T, fileName string, bytes []byte) { @@ -92,4 +277,4 @@ func WriteFile(t *testing.T, fileName string, bytes []byte) { fmt.Println(err.Error()) t.FailNow() } -} \ No newline at end of file +} From b15617224a32f9b228149b8fb93949c3c60b9082 Mon Sep 17 00:00:00 2001 From: Kyle Robertson Date: Fri, 29 Jan 2021 01:32:12 -0500 Subject: [PATCH 4/9] Update modules/terraform/errors.go Co-authored-by: Yevgeniy Brikman --- modules/terraform/errors.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/terraform/errors.go b/modules/terraform/errors.go index c446cec03..7b5ddd4c1 100644 --- a/modules/terraform/errors.go +++ b/modules/terraform/errors.go @@ -63,7 +63,7 @@ type VarFileNotFound struct { } func (err VarFileNotFound) Error() string { - return fmt.Sprintf("Could not resolve var file %s", err.Path) + return fmt.Sprintf("Var file '%s' not found", err.Path) } // InputFileKeyNotFound occurs when tfvar file does not contain a value for the key From cbe3ee71378925ecc502c42776f09f890c4c1bb8 Mon Sep 17 00:00:00 2001 From: "jsakyle@gmail.com" Date: Fri, 29 Jan 2021 02:58:52 -0500 Subject: [PATCH 5/9] addressed feedback --- modules/terraform/errors.go | 7 +- modules/terraform/options.go | 107 +++++++++++++++--------------- modules/terraform/options_test.go | 70 ++++++------------- 3 files changed, 77 insertions(+), 107 deletions(-) diff --git a/modules/terraform/errors.go b/modules/terraform/errors.go index 7b5ddd4c1..f5ae2534b 100644 --- a/modules/terraform/errors.go +++ b/modules/terraform/errors.go @@ -68,10 +68,13 @@ func (err VarFileNotFound) Error() string { // InputFileKeyNotFound occurs when tfvar file does not contain a value for the key // specified in the function call -type InputFileKeyNotFound string +type InputFileKeyNotFound struct { + FilePath string + Key string +} func (err InputFileKeyNotFound) Error() string { - return fmt.Sprintf("tfvar file doesn't contain a value for the key %q", string(err)) + return fmt.Sprintf("tfvar file %q doesn't contain a value for the key %q", err.FilePath, err.Key) } type HclDecodeError struct { diff --git a/modules/terraform/options.go b/modules/terraform/options.go index 602fb1e69..d18616895 100644 --- a/modules/terraform/options.go +++ b/modules/terraform/options.go @@ -1,6 +1,7 @@ package terraform import ( + "errors" "fmt" "io/ioutil" "reflect" @@ -110,8 +111,8 @@ func WithDefaultRetryableErrors(t *testing.T, originalOptions *Options) *Options // GetVariableAsStringFromVarFile Gets the string represention of a variable from a provided input file found in VarFile // For list or map, use GetVariableAsListFromVarFile or GetVariableAsMapFromVarFile, respectively. -func GetVariableAsStringFromVarFile(t *testing.T, option *Options, fileName string, key string) string { - result, err := GetVariableAsStringFromVarFileE(t, option, fileName, key) +func GetVariableAsStringFromVarFile(t *testing.T, fileName string, key string) string { + result, err := GetVariableAsStringFromVarFileE(t, fileName, key) require.NoError(t, err) return result @@ -120,8 +121,10 @@ func GetVariableAsStringFromVarFile(t *testing.T, option *Options, fileName stri // GetVariableAsStringFromVarFileE Gets the string represention of a variable from a provided input file found in VarFile // Will return an error if GetAllVariablesFromVarFileE returns an error or the key provided does not exist in the file. // For list or map, use GetVariableAsListFromVarFile or GetVariableAsMapFromVarFile, respectively. -func GetVariableAsStringFromVarFileE(t *testing.T, option *Options, fileName string, key string) (string, error) { - variables, err := GetAllVariablesFromVarFileE(t, option, fileName) +func GetVariableAsStringFromVarFileE(t *testing.T, fileName string, key string) (string, error) { + var variables map[string]interface{} + + err := GetAllVariablesFromVarFileE(t, fileName, &variables) if err != nil { return "", err @@ -130,43 +133,51 @@ func GetVariableAsStringFromVarFileE(t *testing.T, option *Options, fileName str variable, exists := variables[key] if !exists { - return "", InputFileKeyNotFound(key) + return "", InputFileKeyNotFound{FilePath: fileName, Key: key} } return fmt.Sprintf("%v", variable), nil } // GetVariableAsMapFromVarFile Gets the map represention of a variable from a provided input file found in VarFile -func GetVariableAsMapFromVarFile(t *testing.T, option *Options, fileName string, key string) map[string]string { - result, err := GetVariableAsMapFromVarFileE(t, option, fileName, key) +// Note that this returns a map of strings. For maps containing complex types, use GetAllVariablesFromVarFile. +func GetVariableAsMapFromVarFile(t *testing.T, fileName string, key string) map[string]string { + result, err := GetVariableAsMapFromVarFileE(t, fileName, key) require.NoError(t, err) return result } -// GetVariableAsMapFromVarFileE Gets the map represention of a variable from a provided input file found in VarFile +// GetVariableAsMapFromVarFileE Gets the map represention of a variable from a provided input file found in VarFile. +// Note that this returns a map of strings. For maps containing complex types, use GetAllVariablesFromVarFile // Returns an error if GetAllVariablesFromVarFileE returns an error, the key provided does not exist, or the value associated with the key is not a map -func GetVariableAsMapFromVarFileE(t *testing.T, option *Options, fileName string, key string) (map[string]string, error) { +func GetVariableAsMapFromVarFileE(t *testing.T, fileName string, key string) (map[string]string, error) { + var variables map[string]interface{} + resultMap := make(map[string]string) - variables, err := GetAllVariablesFromVarFileE(t, option, fileName) + err := GetAllVariablesFromVarFileE(t, fileName, &variables) if err != nil { return nil, err } - _, exists := variables[key] + variable, exists := variables[key] if !exists { - return nil, InputFileKeyNotFound(key) + return nil, InputFileKeyNotFound{FilePath: fileName, Key: key} } - if reflect.TypeOf(variables[key]).String() != "[]map[string]interface {}" { - return nil, UnexpectedOutputType{Key: key, ExpectedType: "map", ActualType: reflect.TypeOf(variables[key]).String()} + if reflect.TypeOf(variable).String() != "[]map[string]interface {}" { + return nil, UnexpectedOutputType{Key: key, ExpectedType: "[]map[string]interface {}", ActualType: reflect.TypeOf(variable).String()} } - mapKeys := variables[key].([]map[string]interface{})[0] + mapKeys := variable.([]map[string]interface{}) + + if len(mapKeys) == 0 { + return nil, errors.New("no map keys could be found for given map") + } - for mapKey, mapVal := range mapKeys { + for mapKey, mapVal := range mapKeys[0] { resultMap[mapKey] = fmt.Sprintf("%v", mapVal) } @@ -174,77 +185,63 @@ func GetVariableAsMapFromVarFileE(t *testing.T, option *Options, fileName string } // GetVariableAsListFromVarFile Gets the string list represention of a variable from a provided input file found in VarFile -func GetVariableAsListFromVarFile(t *testing.T, option *Options, fileName string, key string) []string { - result, err := GetVariableAsListFromVarFileE(t, option, fileName, key) +// Note that this returns a list of strings. For lists containing complex types, use GetAllVariablesFromVarFile. +func GetVariableAsListFromVarFile(t *testing.T, fileName string, key string) []string { + result, err := GetVariableAsListFromVarFileE(t, fileName, key) require.NoError(t, err) return result } // GetVariableAsListFromVarFileE Gets the string list represention of a variable from a provided input file found in VarFile +// Note that this returns a list of strings. For lists containing complex types, use GetAllVariablesFromVarFile. // Will return error if GetAllVariablesFromVarFileE returns an error, the key provided does not exist, or the value associated with the key is not a list -func GetVariableAsListFromVarFileE(t *testing.T, option *Options, fileName string, key string) ([]string, error) { +func GetVariableAsListFromVarFileE(t *testing.T, fileName string, key string) ([]string, error) { + var variables map[string]interface{} resultArray := []string{} - variables, err := GetAllVariablesFromVarFileE(t, option, fileName) + err := GetAllVariablesFromVarFileE(t, fileName, &variables) if err != nil { return nil, err } - if _, exists := variables[key]; !exists { - return nil, InputFileKeyNotFound(key) + variable, exists := variables[key] + + if !exists { + return nil, InputFileKeyNotFound{FilePath: fileName, Key: key} } - if reflect.TypeOf(variables[key]).String() != "[]interface {}" { - return nil, UnexpectedOutputType{Key: key, ExpectedType: "list", ActualType: reflect.TypeOf(variables[key]).String()} + if reflect.TypeOf(variable).String() != "[]interface {}" { + return nil, UnexpectedOutputType{Key: key, ExpectedType: "[]interface {}", ActualType: reflect.TypeOf(variable).String()} } - for _, item := range variables[key].([]interface{}) { + for _, item := range variable.([]interface{}) { resultArray = append(resultArray, fmt.Sprintf("%v", item)) } return resultArray, nil } -// GetAllVariablesFromVarFile Parses all data from a provided input file found in VarFile and returns them as a key-value map -func GetAllVariablesFromVarFile(t *testing.T, option *Options, fileName string) map[string]interface{} { - variableMaps, err := GetAllVariablesFromVarFileE(t, option, fileName) +// GetAllVariablesFromVarFile Parses all data from a provided input file found in VarFile and stores the result in the value pointed to by out +func GetAllVariablesFromVarFile(t *testing.T, fileName string, out interface{}) { + err := GetAllVariablesFromVarFileE(t, fileName, out) require.NoError(t, err) - - return variableMaps } -// GetAllVariablesFromVarFileE Parses all data from a provided input file found ind in VarFile and returns them as a key-value map -// Retursn an error if the specified file does not exist, the specified file is not readable, or the specified file cannot be decoded from HCL -func GetAllVariablesFromVarFileE(t *testing.T, option *Options, fileName string) (map[string]interface{}, error) { - variableMap := map[string]interface{}{} - fileIndex := -1 - if len(option.VarFiles) == 0 { - return variableMap, nil - } - - for index, file := range option.VarFiles { - if file == fileName { - fileIndex = index - break - } - } - - if fileIndex == -1 { - return nil, VarFileNotFound{Path: fileName} - } - - fileContents, err := ioutil.ReadFile(option.VarFiles[fileIndex]) +// GetAllVariablesFromVarFileE Parses all data from a provided input file found ind in VarFile and stores the result in the value pointed to by out +// Returns an error if the specified file does not exist, the specified file is not readable, or the specified file cannot be decoded from HCL +func GetAllVariablesFromVarFileE(t *testing.T, fileName string, out interface{}) error { + fileContents, err := ioutil.ReadFile(fileName) if err != nil { - return nil, err + return err } - err = hcl.Decode(&variableMap, string(fileContents)) + err = hcl.Decode(out, string(fileContents)) if err != nil { - return nil, HclDecodeError{FilePath: option.VarFiles[fileIndex], ErrorText: err.Error()} + return HclDecodeError{FilePath: fileName, ErrorText: err.Error()} } - return variableMap, nil + return nil } diff --git a/modules/terraform/options_test.go b/modules/terraform/options_test.go index 8483192ee..0eb4ab2d6 100644 --- a/modules/terraform/options_test.go +++ b/modules/terraform/options_test.go @@ -26,17 +26,11 @@ func TestGetVariablesFromVarFilesAsString(t *testing.T) { WriteFile(t, randomFileName, testHcl) defer os.Remove(randomFileName) - stringVal := GetVariableAsStringFromVarFile(t, &Options{ - VarFiles: []string{randomFileName}, - }, randomFileName, "aws_region") + stringVal := GetVariableAsStringFromVarFile(t, randomFileName, "aws_region") - boolString := GetVariableAsStringFromVarFile(t, &Options{ - VarFiles: []string{randomFileName}, - }, randomFileName, "boolean_type") + boolString := GetVariableAsStringFromVarFile(t, randomFileName, "boolean_type") - numString := GetVariableAsStringFromVarFile(t, &Options{ - VarFiles: []string{randomFileName}, - }, randomFileName, "number_type") + numString := GetVariableAsStringFromVarFile(t, randomFileName, "number_type") require.Equal(t, "us-east-2", stringVal) require.Equal(t, "true", boolString) @@ -58,9 +52,7 @@ func TestGetVariablesFromVarFilesAsStringKeyDoesNotExist(t *testing.T) { WriteFile(t, randomFileName, testHcl) defer os.Remove(randomFileName) - _, err := GetVariableAsStringFromVarFileE(t, &Options{ - VarFiles: []string{randomFileName}, - }, randomFileName, "badkey") + _, err := GetVariableAsStringFromVarFileE(t, randomFileName, "badkey") require.Error(t, err) } @@ -81,9 +73,7 @@ func TestGetVariableAsMapFromVarFile(t *testing.T) { WriteFile(t, randomFileName, testHcl) defer os.Remove(randomFileName) - val := GetVariableAsMapFromVarFile(t, &Options{ - VarFiles: []string{randomFileName}, - }, randomFileName, "tags") + val := GetVariableAsMapFromVarFile(t, randomFileName, "tags") require.Equal(t, expected, val) } @@ -102,9 +92,7 @@ func TestGetVariableAsMapFromVarFileNotMap(t *testing.T) { WriteFile(t, randomFileName, testHcl) defer os.Remove(randomFileName) - _, err := GetVariableAsMapFromVarFileE(t, &Options{ - VarFiles: []string{randomFileName}, - }, randomFileName, "aws_region") + _, err := GetVariableAsMapFromVarFileE(t, randomFileName, "aws_region") require.Error(t, err) } @@ -123,9 +111,7 @@ func TestGetVariableAsMapFromVarFileKeyDoesNotExist(t *testing.T) { WriteFile(t, randomFileName, testHcl) defer os.Remove(randomFileName) - _, err := GetVariableAsMapFromVarFileE(t, &Options{ - VarFiles: []string{randomFileName}, - }, randomFileName, "badkey") + _, err := GetVariableAsMapFromVarFileE(t, randomFileName, "badkey") require.Error(t, err) } @@ -145,9 +131,7 @@ func TestGetVariableAsListFromVarFile(t *testing.T) { WriteFile(t, randomFileName, testHcl) defer os.Remove(randomFileName) - val := GetVariableAsListFromVarFile(t, &Options{ - VarFiles: []string{randomFileName}, - }, randomFileName, "list") + val := GetVariableAsListFromVarFile(t, randomFileName, "list") require.Equal(t, expected, val) } @@ -166,9 +150,7 @@ func TestGetVariableAsListNotList(t *testing.T) { WriteFile(t, randomFileName, testHcl) defer os.Remove(randomFileName) - _, err := GetVariableAsListFromVarFileE(t, &Options{ - VarFiles: []string{randomFileName}, - }, randomFileName, "tags") + _, err := GetVariableAsListFromVarFileE(t, randomFileName, "tags") require.Error(t, err) } @@ -187,31 +169,21 @@ func TestGetVariableAsListKeyDoesNotExist(t *testing.T) { WriteFile(t, randomFileName, testHcl) defer os.Remove(randomFileName) - _, err := GetVariableAsListFromVarFileE(t, &Options{ - VarFiles: []string{randomFileName}, - }, randomFileName, "badkey") + _, err := GetVariableAsListFromVarFileE(t, randomFileName, "badkey") require.Error(t, err) } - -func TestGetAllVariablesFromVarFileENotInVarFiles(t *testing.T) { - _, err := GetAllVariablesFromVarFileE(t, &Options{ - VarFiles: []string{"filea"}, - }, "fileb") - - //require.Equal(t, "open thisdoesntexist: no such file or directory", err.Error()) - require.Error(t, err) -} - func TestGetAllVariablesFromVarFileEFileDoesNotExist(t *testing.T) { - _, err := GetAllVariablesFromVarFileE(t, &Options{ - VarFiles: []string{"filea"}, - }, "filea") + var variables map[string]interface{} + + err := GetAllVariablesFromVarFileE(t, "filea", variables) require.Equal(t, "open filea: no such file or directory", err.Error()) } func TestGetAllVariablesFromVarFileBadFile(t *testing.T) { + var variables map[string]interface{} + randomFileName := fmt.Sprintf("./%s.tfvars", random.UniqueId()) testHcl := []byte(` thiswillnotwork`) @@ -225,9 +197,7 @@ func TestGetAllVariablesFromVarFileBadFile(t *testing.T) { defer os.Remove(randomFileName) - _, err = GetAllVariablesFromVarFileE(t, &Options{ - VarFiles: []string{randomFileName}, - }, randomFileName) + err = GetAllVariablesFromVarFileE(t, randomFileName, variables) if err == nil { t.FailNow() @@ -239,6 +209,8 @@ func TestGetAllVariablesFromVarFileBadFile(t *testing.T) { } func TestGetAllVariablesFromVarFile(t *testing.T) { + var variables map[string]interface{} + randomFileName := fmt.Sprintf("./%s.tfvars", random.UniqueId()) testHcl := []byte(` aws_region = "us-east-2" @@ -253,9 +225,7 @@ func TestGetAllVariablesFromVarFile(t *testing.T) { defer os.Remove(randomFileName) - val, err := GetAllVariablesFromVarFileE(t, &Options{ - VarFiles: []string{randomFileName}, - }, randomFileName) + err = GetAllVariablesFromVarFileE(t, randomFileName, &variables) if err != nil { t.FailNow() @@ -264,7 +234,7 @@ func TestGetAllVariablesFromVarFile(t *testing.T) { expected := make(map[string]interface{}) expected["aws_region"] = "us-east-2" - require.Equal(t, expected, val) + require.Equal(t, expected, variables) } From d1985c004001e3dfb7b8ea1fe22cddca519c4965 Mon Sep 17 00:00:00 2001 From: "jsakyle@gmail.com" Date: Tue, 2 Feb 2021 21:53:53 -0500 Subject: [PATCH 6/9] moving clibrary to new file --- modules/terraform/options.go | 142 ----------------- modules/terraform/var-file.go | 149 ++++++++++++++++++ .../{options_test.go => var-file_test.go} | 0 3 files changed, 149 insertions(+), 142 deletions(-) create mode 100644 modules/terraform/var-file.go rename modules/terraform/{options_test.go => var-file_test.go} (100%) diff --git a/modules/terraform/options.go b/modules/terraform/options.go index d18616895..048e9b4a1 100644 --- a/modules/terraform/options.go +++ b/modules/terraform/options.go @@ -1,16 +1,11 @@ package terraform import ( - "errors" - "fmt" - "io/ioutil" - "reflect" "testing" "time" "github.com/gruntwork-io/terratest/modules/logger" "github.com/gruntwork-io/terratest/modules/ssh" - "github.com/hashicorp/hcl" "github.com/jinzhu/copier" "github.com/stretchr/testify/require" ) @@ -108,140 +103,3 @@ func WithDefaultRetryableErrors(t *testing.T, originalOptions *Options) *Options return newOptions } - -// GetVariableAsStringFromVarFile Gets the string represention of a variable from a provided input file found in VarFile -// For list or map, use GetVariableAsListFromVarFile or GetVariableAsMapFromVarFile, respectively. -func GetVariableAsStringFromVarFile(t *testing.T, fileName string, key string) string { - result, err := GetVariableAsStringFromVarFileE(t, fileName, key) - require.NoError(t, err) - - return result -} - -// GetVariableAsStringFromVarFileE Gets the string represention of a variable from a provided input file found in VarFile -// Will return an error if GetAllVariablesFromVarFileE returns an error or the key provided does not exist in the file. -// For list or map, use GetVariableAsListFromVarFile or GetVariableAsMapFromVarFile, respectively. -func GetVariableAsStringFromVarFileE(t *testing.T, fileName string, key string) (string, error) { - var variables map[string]interface{} - - err := GetAllVariablesFromVarFileE(t, fileName, &variables) - - if err != nil { - return "", err - } - - variable, exists := variables[key] - - if !exists { - return "", InputFileKeyNotFound{FilePath: fileName, Key: key} - } - - return fmt.Sprintf("%v", variable), nil -} - -// GetVariableAsMapFromVarFile Gets the map represention of a variable from a provided input file found in VarFile -// Note that this returns a map of strings. For maps containing complex types, use GetAllVariablesFromVarFile. -func GetVariableAsMapFromVarFile(t *testing.T, fileName string, key string) map[string]string { - result, err := GetVariableAsMapFromVarFileE(t, fileName, key) - require.NoError(t, err) - - return result -} - -// GetVariableAsMapFromVarFileE Gets the map represention of a variable from a provided input file found in VarFile. -// Note that this returns a map of strings. For maps containing complex types, use GetAllVariablesFromVarFile -// Returns an error if GetAllVariablesFromVarFileE returns an error, the key provided does not exist, or the value associated with the key is not a map -func GetVariableAsMapFromVarFileE(t *testing.T, fileName string, key string) (map[string]string, error) { - var variables map[string]interface{} - - resultMap := make(map[string]string) - err := GetAllVariablesFromVarFileE(t, fileName, &variables) - - if err != nil { - return nil, err - } - - variable, exists := variables[key] - - if !exists { - return nil, InputFileKeyNotFound{FilePath: fileName, Key: key} - } - - if reflect.TypeOf(variable).String() != "[]map[string]interface {}" { - return nil, UnexpectedOutputType{Key: key, ExpectedType: "[]map[string]interface {}", ActualType: reflect.TypeOf(variable).String()} - } - - mapKeys := variable.([]map[string]interface{}) - - if len(mapKeys) == 0 { - return nil, errors.New("no map keys could be found for given map") - } - - for mapKey, mapVal := range mapKeys[0] { - resultMap[mapKey] = fmt.Sprintf("%v", mapVal) - } - - return resultMap, nil -} - -// GetVariableAsListFromVarFile Gets the string list represention of a variable from a provided input file found in VarFile -// Note that this returns a list of strings. For lists containing complex types, use GetAllVariablesFromVarFile. -func GetVariableAsListFromVarFile(t *testing.T, fileName string, key string) []string { - result, err := GetVariableAsListFromVarFileE(t, fileName, key) - require.NoError(t, err) - - return result -} - -// GetVariableAsListFromVarFileE Gets the string list represention of a variable from a provided input file found in VarFile -// Note that this returns a list of strings. For lists containing complex types, use GetAllVariablesFromVarFile. -// Will return error if GetAllVariablesFromVarFileE returns an error, the key provided does not exist, or the value associated with the key is not a list -func GetVariableAsListFromVarFileE(t *testing.T, fileName string, key string) ([]string, error) { - var variables map[string]interface{} - resultArray := []string{} - err := GetAllVariablesFromVarFileE(t, fileName, &variables) - - if err != nil { - return nil, err - } - - variable, exists := variables[key] - - if !exists { - return nil, InputFileKeyNotFound{FilePath: fileName, Key: key} - } - - if reflect.TypeOf(variable).String() != "[]interface {}" { - return nil, UnexpectedOutputType{Key: key, ExpectedType: "[]interface {}", ActualType: reflect.TypeOf(variable).String()} - } - - for _, item := range variable.([]interface{}) { - resultArray = append(resultArray, fmt.Sprintf("%v", item)) - } - - return resultArray, nil -} - -// GetAllVariablesFromVarFile Parses all data from a provided input file found in VarFile and stores the result in the value pointed to by out -func GetAllVariablesFromVarFile(t *testing.T, fileName string, out interface{}) { - err := GetAllVariablesFromVarFileE(t, fileName, out) - require.NoError(t, err) -} - -// GetAllVariablesFromVarFileE Parses all data from a provided input file found ind in VarFile and stores the result in the value pointed to by out -// Returns an error if the specified file does not exist, the specified file is not readable, or the specified file cannot be decoded from HCL -func GetAllVariablesFromVarFileE(t *testing.T, fileName string, out interface{}) error { - fileContents, err := ioutil.ReadFile(fileName) - - if err != nil { - return err - } - - err = hcl.Decode(out, string(fileContents)) - - if err != nil { - return HclDecodeError{FilePath: fileName, ErrorText: err.Error()} - } - - return nil -} diff --git a/modules/terraform/var-file.go b/modules/terraform/var-file.go new file mode 100644 index 000000000..4572ae82b --- /dev/null +++ b/modules/terraform/var-file.go @@ -0,0 +1,149 @@ +package terraform + +import ( + "errors" + "fmt" + "io/ioutil" + "reflect" + "testing" + + "github.com/hashicorp/hcl" + "github.com/stretchr/testify/require" +) + +// GetVariableAsStringFromVarFile Gets the string represention of a variable from a provided input file found in VarFile +// For list or map, use GetVariableAsListFromVarFile or GetVariableAsMapFromVarFile, respectively. +func GetVariableAsStringFromVarFile(t *testing.T, fileName string, key string) string { + result, err := GetVariableAsStringFromVarFileE(t, fileName, key) + require.NoError(t, err) + + return result +} + +// GetVariableAsStringFromVarFileE Gets the string represention of a variable from a provided input file found in VarFile +// Will return an error if GetAllVariablesFromVarFileE returns an error or the key provided does not exist in the file. +// For list or map, use GetVariableAsListFromVarFile or GetVariableAsMapFromVarFile, respectively. +func GetVariableAsStringFromVarFileE(t *testing.T, fileName string, key string) (string, error) { + var variables map[string]interface{} + + err := GetAllVariablesFromVarFileE(t, fileName, &variables) + + if err != nil { + return "", err + } + + variable, exists := variables[key] + + if !exists { + return "", InputFileKeyNotFound{FilePath: fileName, Key: key} + } + + return fmt.Sprintf("%v", variable), nil +} + +// GetVariableAsMapFromVarFile Gets the map represention of a variable from a provided input file found in VarFile +// Note that this returns a map of strings. For maps containing complex types, use GetAllVariablesFromVarFile. +func GetVariableAsMapFromVarFile(t *testing.T, fileName string, key string) map[string]string { + result, err := GetVariableAsMapFromVarFileE(t, fileName, key) + require.NoError(t, err) + + return result +} + +// GetVariableAsMapFromVarFileE Gets the map represention of a variable from a provided input file found in VarFile. +// Note that this returns a map of strings. For maps containing complex types, use GetAllVariablesFromVarFile +// Returns an error if GetAllVariablesFromVarFileE returns an error, the key provided does not exist, or the value associated with the key is not a map +func GetVariableAsMapFromVarFileE(t *testing.T, fileName string, key string) (map[string]string, error) { + var variables map[string]interface{} + + resultMap := make(map[string]string) + err := GetAllVariablesFromVarFileE(t, fileName, &variables) + + if err != nil { + return nil, err + } + + variable, exists := variables[key] + + if !exists { + return nil, InputFileKeyNotFound{FilePath: fileName, Key: key} + } + + if reflect.TypeOf(variable).String() != "[]map[string]interface {}" { + return nil, UnexpectedOutputType{Key: key, ExpectedType: "[]map[string]interface {}", ActualType: reflect.TypeOf(variable).String()} + } + + mapKeys := variable.([]map[string]interface{}) + + if len(mapKeys) == 0 { + return nil, errors.New("no map keys could be found for given map") + } + + for mapKey, mapVal := range mapKeys[0] { + resultMap[mapKey] = fmt.Sprintf("%v", mapVal) + } + + return resultMap, nil +} + +// GetVariableAsListFromVarFile Gets the string list represention of a variable from a provided input file found in VarFile +// Note that this returns a list of strings. For lists containing complex types, use GetAllVariablesFromVarFile. +func GetVariableAsListFromVarFile(t *testing.T, fileName string, key string) []string { + result, err := GetVariableAsListFromVarFileE(t, fileName, key) + require.NoError(t, err) + + return result +} + +// GetVariableAsListFromVarFileE Gets the string list represention of a variable from a provided input file found in VarFile +// Note that this returns a list of strings. For lists containing complex types, use GetAllVariablesFromVarFile. +// Will return error if GetAllVariablesFromVarFileE returns an error, the key provided does not exist, or the value associated with the key is not a list +func GetVariableAsListFromVarFileE(t *testing.T, fileName string, key string) ([]string, error) { + var variables map[string]interface{} + resultArray := []string{} + err := GetAllVariablesFromVarFileE(t, fileName, &variables) + + if err != nil { + return nil, err + } + + variable, exists := variables[key] + + if !exists { + return nil, InputFileKeyNotFound{FilePath: fileName, Key: key} + } + + if reflect.TypeOf(variable).String() != "[]interface {}" { + return nil, UnexpectedOutputType{Key: key, ExpectedType: "[]interface {}", ActualType: reflect.TypeOf(variable).String()} + } + + for _, item := range variable.([]interface{}) { + resultArray = append(resultArray, fmt.Sprintf("%v", item)) + } + + return resultArray, nil +} + +// GetAllVariablesFromVarFile Parses all data from a provided input file found in VarFile and stores the result in the value pointed to by out +func GetAllVariablesFromVarFile(t *testing.T, fileName string, out interface{}) { + err := GetAllVariablesFromVarFileE(t, fileName, out) + require.NoError(t, err) +} + +// GetAllVariablesFromVarFileE Parses all data from a provided input file found ind in VarFile and stores the result in the value pointed to by out +// Returns an error if the specified file does not exist, the specified file is not readable, or the specified file cannot be decoded from HCL +func GetAllVariablesFromVarFileE(t *testing.T, fileName string, out interface{}) error { + fileContents, err := ioutil.ReadFile(fileName) + + if err != nil { + return err + } + + err = hcl.Decode(out, string(fileContents)) + + if err != nil { + return HclDecodeError{FilePath: fileName, ErrorText: err.Error()} + } + + return nil +} diff --git a/modules/terraform/options_test.go b/modules/terraform/var-file_test.go similarity index 100% rename from modules/terraform/options_test.go rename to modules/terraform/var-file_test.go From 72180fed4bd323d73df5d1ebf7eae6dbc260bc2f Mon Sep 17 00:00:00 2001 From: "jsakyle@gmail.com" Date: Tue, 9 Feb 2021 18:37:38 -0500 Subject: [PATCH 7/9] update go.mod --- go.mod | 3 --- 1 file changed, 3 deletions(-) diff --git a/go.mod b/go.mod index 34e404639..2eacd66d0 100644 --- a/go.mod +++ b/go.mod @@ -21,11 +21,8 @@ require ( github.com/google/go-containerregistry v0.0.0-20200110202235-f4fb41bf00a3 github.com/google/uuid v1.1.1 github.com/gruntwork-io/gruntwork-cli v0.7.0 -<<<<<<< HEAD github.com/hashicorp/go-multierror v1.1.0 -======= github.com/hashicorp/hcl v1.0.0 ->>>>>>> adding function to extract terraform vars from var file github.com/imdario/mergo v0.3.7 // indirect github.com/jinzhu/copier v0.0.0-20190924061706-b57f9002281a github.com/jstemmer/go-junit-report v0.9.1 From b5d2235aa82201340d8c8f5240c6b5b24ddcbddf Mon Sep 17 00:00:00 2001 From: Kyle Robertson Date: Thu, 11 Feb 2021 20:49:49 -0500 Subject: [PATCH 8/9] Update modules/terraform/var-file_test.go Co-authored-by: Yevgeniy Brikman --- modules/terraform/var-file_test.go | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/modules/terraform/var-file_test.go b/modules/terraform/var-file_test.go index 0eb4ab2d6..993ecf4ea 100644 --- a/modules/terraform/var-file_test.go +++ b/modules/terraform/var-file_test.go @@ -243,8 +243,5 @@ func TestGetAllVariablesFromVarFile(t *testing.T) { func WriteFile(t *testing.T, fileName string, bytes []byte) { err := ioutil.WriteFile(fileName, bytes, 0644) - if err != nil { - fmt.Println(err.Error()) - t.FailNow() - } + require.NoError(t, err) } From 8192f26adeae1aa4328e5845947e778eb47cca48 Mon Sep 17 00:00:00 2001 From: "jsakyle@gmail.com" Date: Thu, 11 Feb 2021 20:53:51 -0500 Subject: [PATCH 9/9] addressing test feedback --- modules/terraform/var-file_test.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/modules/terraform/var-file_test.go b/modules/terraform/var-file_test.go index 993ecf4ea..39e3b3e6b 100644 --- a/modules/terraform/var-file_test.go +++ b/modules/terraform/var-file_test.go @@ -199,9 +199,7 @@ func TestGetAllVariablesFromVarFileBadFile(t *testing.T) { err = GetAllVariablesFromVarFileE(t, randomFileName, variables) - if err == nil { - t.FailNow() - } + require.Error(t, err) // HCL library could change their error string, so we are only testing the error string contains what we add to it require.Regexp(t, fmt.Sprintf("^%s - ", randomFileName), err.Error())