Merge pull request #10203 from hashicorp/jbardin/GH-8104
Allow primitive types in schema maps
This commit is contained in:
commit
e9af2361be
|
@ -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{}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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{}{
|
||||
|
|
|
@ -92,6 +92,11 @@ func (r *DiffFieldReader) readMap(
|
|||
result[k] = v.New
|
||||
}
|
||||
|
||||
err = mapValuesToPrimitive(result, schema)
|
||||
if err != nil {
|
||||
return FieldReadResult{}, nil
|
||||
}
|
||||
|
||||
var resultVal interface{}
|
||||
if resultSet {
|
||||
resultVal = result
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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",
|
||||
}),
|
||||
}
|
||||
})
|
||||
|
|
|
@ -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{
|
||||
|
|
|
@ -2959,6 +2959,111 @@ func TestResourceDataState_schema(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestResourceData_nonStringValuesInMap(t *testing.T) {
|
||||
cases := []struct {
|
||||
Schema map[string]*Schema
|
||||
Diff *terraform.InstanceDiff
|
||||
MapFieldName string
|
||||
ItemName string
|
||||
ExpectedType string
|
||||
}{
|
||||
{
|
||||
Schema: map[string]*Schema{
|
||||
"boolMap": &Schema{
|
||||
Type: TypeMap,
|
||||
Elem: TypeBool,
|
||||
Optional: true,
|
||||
},
|
||||
},
|
||||
Diff: &terraform.InstanceDiff{
|
||||
Attributes: map[string]*terraform.ResourceAttrDiff{
|
||||
"boolMap.%": &terraform.ResourceAttrDiff{
|
||||
Old: "",
|
||||
New: "1",
|
||||
},
|
||||
"boolMap.boolField": &terraform.ResourceAttrDiff{
|
||||
Old: "",
|
||||
New: "true",
|
||||
},
|
||||
},
|
||||
},
|
||||
MapFieldName: "boolMap",
|
||||
ItemName: "boolField",
|
||||
ExpectedType: "bool",
|
||||
},
|
||||
{
|
||||
Schema: map[string]*Schema{
|
||||
"intMap": &Schema{
|
||||
Type: TypeMap,
|
||||
Elem: TypeInt,
|
||||
Optional: true,
|
||||
},
|
||||
},
|
||||
Diff: &terraform.InstanceDiff{
|
||||
Attributes: map[string]*terraform.ResourceAttrDiff{
|
||||
"intMap.%": &terraform.ResourceAttrDiff{
|
||||
Old: "",
|
||||
New: "1",
|
||||
},
|
||||
"intMap.intField": &terraform.ResourceAttrDiff{
|
||||
Old: "",
|
||||
New: "8",
|
||||
},
|
||||
},
|
||||
},
|
||||
MapFieldName: "intMap",
|
||||
ItemName: "intField",
|
||||
ExpectedType: "int",
|
||||
},
|
||||
{
|
||||
Schema: map[string]*Schema{
|
||||
"floatMap": &Schema{
|
||||
Type: TypeMap,
|
||||
Elem: TypeFloat,
|
||||
Optional: true,
|
||||
},
|
||||
},
|
||||
Diff: &terraform.InstanceDiff{
|
||||
Attributes: map[string]*terraform.ResourceAttrDiff{
|
||||
"floatMap.%": &terraform.ResourceAttrDiff{
|
||||
Old: "",
|
||||
New: "1",
|
||||
},
|
||||
"floatMap.floatField": &terraform.ResourceAttrDiff{
|
||||
Old: "",
|
||||
New: "8.22",
|
||||
},
|
||||
},
|
||||
},
|
||||
MapFieldName: "floatMap",
|
||||
ItemName: "floatField",
|
||||
ExpectedType: "float64",
|
||||
},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
d, err := schemaMap(c.Schema).Data(nil, c.Diff)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
m, ok := d.Get(c.MapFieldName).(map[string]interface{})
|
||||
if !ok {
|
||||
t.Fatalf("expected %q to be castable to a map", c.MapFieldName)
|
||||
}
|
||||
field, ok := m[c.ItemName]
|
||||
if !ok {
|
||||
t.Fatalf("expected %q in the map", c.ItemName)
|
||||
}
|
||||
|
||||
typeName := reflect.TypeOf(field).Name()
|
||||
if typeName != c.ExpectedType {
|
||||
t.Fatalf("expected %q to be %q, it is %q.",
|
||||
c.ItemName, c.ExpectedType, typeName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestResourceDataSetConnInfo(t *testing.T) {
|
||||
d := &ResourceData{}
|
||||
d.SetId("foo")
|
||||
|
|
Loading…
Reference in New Issue