From c4eefd4b5e4ef91686516ec71f02893a23202fec Mon Sep 17 00:00:00 2001 From: James Bardin Date: Thu, 17 Nov 2016 15:12:29 -0500 Subject: [PATCH] Allow primitive type in maps via all FieldReaders Now that we can read primitive type out of a map, each field reader needs to be able to set the proper type as expected. --- helper/schema/field_reader.go | 27 ++++++++++++ helper/schema/field_reader_config.go | 9 +++- helper/schema/field_reader_config_test.go | 11 +++++ helper/schema/field_reader_diff.go | 24 +++-------- helper/schema/field_reader_diff_test.go | 35 ++++++++++++++++ helper/schema/field_reader_map.go | 9 +++- helper/schema/field_reader_map_test.go | 11 +++++ helper/schema/field_reader_test.go | 50 +++++++++++++++++++++++ 8 files changed, 153 insertions(+), 23 deletions(-) diff --git a/helper/schema/field_reader.go b/helper/schema/field_reader.go index 263308813..5606e6d8b 100644 --- a/helper/schema/field_reader.go +++ b/helper/schema/field_reader.go @@ -214,6 +214,33 @@ func readObjectField( }, nil } +// convert map values to the proper primitive type based on schema.Elem +func mapValuesToPrimitive(m map[string]interface{}, schema *Schema) error { + + elemType := TypeString + if et, ok := schema.Elem.(ValueType); ok { + elemType = et + } + + switch elemType { + case TypeInt, TypeFloat, TypeBool: + for k, v := range m { + vs, ok := v.(string) + if !ok { + continue + } + + v, err := stringToPrimitive(vs, false, &Schema{Type: elemType}) + if err != nil { + return err + } + + m[k] = v + } + } + return nil +} + func stringToPrimitive( value string, computed bool, schema *Schema) (interface{}, error) { var returnVal interface{} diff --git a/helper/schema/field_reader_config.go b/helper/schema/field_reader_config.go index 0d4c2a97c..042fe9d0e 100644 --- a/helper/schema/field_reader_config.go +++ b/helper/schema/field_reader_config.go @@ -85,7 +85,7 @@ func (r *ConfigFieldReader) readField( case TypeList: return readListField(&nestedConfigFieldReader{r}, address, schema) case TypeMap: - return r.readMap(k) + return r.readMap(k, schema) case TypeSet: return r.readSet(address, schema) case typeObject: @@ -97,7 +97,7 @@ func (r *ConfigFieldReader) readField( } } -func (r *ConfigFieldReader) readMap(k string) (FieldReadResult, error) { +func (r *ConfigFieldReader) readMap(k string, schema *Schema) (FieldReadResult, error) { // We want both the raw value and the interpolated. We use the interpolated // to store actual values and we use the raw one to check for // computed keys. Actual values are obtained in the switch, depending on @@ -170,6 +170,11 @@ func (r *ConfigFieldReader) readMap(k string) (FieldReadResult, error) { panic(fmt.Sprintf("unknown type: %#v", mraw)) } + err := mapValuesToPrimitive(result, schema) + if err != nil { + return FieldReadResult{}, nil + } + var value interface{} if !computed { value = result diff --git a/helper/schema/field_reader_config_test.go b/helper/schema/field_reader_config_test.go index 8ca0a2fbe..0b68cce22 100644 --- a/helper/schema/field_reader_config_test.go +++ b/helper/schema/field_reader_config_test.go @@ -35,6 +35,17 @@ func TestConfigFieldReader(t *testing.T) { "foo": "bar", "bar": "baz", }, + "mapInt": map[string]interface{}{ + "one": "1", + "two": "2", + }, + "mapFloat": map[string]interface{}{ + "oneDotTwo": "1.2", + }, + "mapBool": map[string]interface{}{ + "True": "true", + "False": "false", + }, "set": []interface{}{10, 50}, "setDeep": []interface{}{ diff --git a/helper/schema/field_reader_diff.go b/helper/schema/field_reader_diff.go index 807abfdc2..16bbae296 100644 --- a/helper/schema/field_reader_diff.go +++ b/helper/schema/field_reader_diff.go @@ -69,12 +69,6 @@ func (r *DiffFieldReader) readMap( resultSet = true } - // Determine what element type the map contains, defaulting to string - elemType := TypeString - if et, ok := schema.Elem.(ValueType); ok { - elemType = et - } - // Next, read all the elements we have in our diff, and apply // the diff to our result. prefix := strings.Join(address, ".") + "." @@ -95,20 +89,12 @@ func (r *DiffFieldReader) readMap( continue } - // Replace the new value with one of the correct Elem type if needed. - // We don't supported arbitrarily nested schemas, so we can only handle - // the primitive types here. - var vNew interface{} = v.New - switch elemType { - case TypeBool, TypeInt, TypeFloat: - v, err := stringToPrimitive(v.New, false, &Schema{Type: elemType}) - if err != nil { - return FieldReadResult{}, err - } - vNew = v - } + result[k] = v.New + } - result[k] = vNew + err = mapValuesToPrimitive(result, schema) + if err != nil { + return FieldReadResult{}, nil } var resultVal interface{} diff --git a/helper/schema/field_reader_diff_test.go b/helper/schema/field_reader_diff_test.go index 7339dea7a..537e62e55 100644 --- a/helper/schema/field_reader_diff_test.go +++ b/helper/schema/field_reader_diff_test.go @@ -420,6 +420,41 @@ func TestDiffFieldReader(t *testing.T) { New: "baz", }, + "mapInt.%": &terraform.ResourceAttrDiff{ + Old: "", + New: "2", + }, + "mapInt.one": &terraform.ResourceAttrDiff{ + Old: "", + New: "1", + }, + "mapInt.two": &terraform.ResourceAttrDiff{ + Old: "", + New: "2", + }, + + "mapFloat.%": &terraform.ResourceAttrDiff{ + Old: "", + New: "1", + }, + "mapFloat.oneDotTwo": &terraform.ResourceAttrDiff{ + Old: "", + New: "1.2", + }, + + "mapBool.%": &terraform.ResourceAttrDiff{ + Old: "", + New: "2", + }, + "mapBool.True": &terraform.ResourceAttrDiff{ + Old: "", + New: "true", + }, + "mapBool.False": &terraform.ResourceAttrDiff{ + Old: "", + New: "false", + }, + "set.#": &terraform.ResourceAttrDiff{ Old: "0", New: "2", diff --git a/helper/schema/field_reader_map.go b/helper/schema/field_reader_map.go index fc3b5a02f..95339810b 100644 --- a/helper/schema/field_reader_map.go +++ b/helper/schema/field_reader_map.go @@ -26,7 +26,7 @@ func (r *MapFieldReader) ReadField(address []string) (FieldReadResult, error) { case TypeList: return readListField(r, address, schema) case TypeMap: - return r.readMap(k) + return r.readMap(k, schema) case TypeSet: return r.readSet(address, schema) case typeObject: @@ -36,7 +36,7 @@ func (r *MapFieldReader) ReadField(address []string) (FieldReadResult, error) { } } -func (r *MapFieldReader) readMap(k string) (FieldReadResult, error) { +func (r *MapFieldReader) readMap(k string, schema *Schema) (FieldReadResult, error) { result := make(map[string]interface{}) resultSet := false @@ -61,6 +61,11 @@ func (r *MapFieldReader) readMap(k string) (FieldReadResult, error) { return true }) + err := mapValuesToPrimitive(result, schema) + if err != nil { + return FieldReadResult{}, nil + } + var resultVal interface{} if resultSet { resultVal = result diff --git a/helper/schema/field_reader_map_test.go b/helper/schema/field_reader_map_test.go index 279b9145b..4fa78e25c 100644 --- a/helper/schema/field_reader_map_test.go +++ b/helper/schema/field_reader_map_test.go @@ -41,6 +41,17 @@ func TestMapFieldReader(t *testing.T) { "setDeep.10.value": "foo", "setDeep.50.index": "50", "setDeep.50.value": "bar", + + "mapInt.%": "2", + "mapInt.one": "1", + "mapInt.two": "2", + + "mapFloat.%": "1", + "mapFloat.oneDotTwo": "1.2", + + "mapBool.%": "2", + "mapBool.True": "true", + "mapBool.False": "false", }), } }) diff --git a/helper/schema/field_reader_test.go b/helper/schema/field_reader_test.go index c61fb8eb7..fb0030722 100644 --- a/helper/schema/field_reader_test.go +++ b/helper/schema/field_reader_test.go @@ -211,6 +211,18 @@ func testFieldReader(t *testing.T, f func(map[string]*Schema) FieldReader) { // Maps "map": &Schema{Type: TypeMap}, + "mapInt": &Schema{ + Type: TypeMap, + Elem: TypeInt, + }, + "mapFloat": &Schema{ + Type: TypeMap, + Elem: TypeFloat, + }, + "mapBool": &Schema{ + Type: TypeMap, + Elem: TypeBool, + }, // Sets "set": &Schema{ @@ -335,6 +347,44 @@ func testFieldReader(t *testing.T, f func(map[string]*Schema) FieldReader) { false, }, + "mapInt": { + []string{"mapInt"}, + FieldReadResult{ + Value: map[string]interface{}{ + "one": 1, + "two": 2, + }, + Exists: true, + Computed: false, + }, + false, + }, + + "mapFloat": { + []string{"mapFloat"}, + FieldReadResult{ + Value: map[string]interface{}{ + "oneDotTwo": 1.2, + }, + Exists: true, + Computed: false, + }, + false, + }, + + "mapBool": { + []string{"mapBool"}, + FieldReadResult{ + Value: map[string]interface{}{ + "True": true, + "False": false, + }, + Exists: true, + Computed: false, + }, + false, + }, + "mapelem": { []string{"map", "foo"}, FieldReadResult{