Skip to content

Commit

Permalink
Merge pull request #10325 from hashicorp/jbardin/GH-10187
Browse files Browse the repository at this point in the history
Fix some cases for nested maps and lists
  • Loading branch information
jbardin authored Nov 29, 2016
2 parents 5dcc6ec + 01be1a5 commit 7677bd9
Show file tree
Hide file tree
Showing 4 changed files with 326 additions and 25 deletions.
38 changes: 36 additions & 2 deletions helper/schema/field_reader.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,8 @@ func addrToSchema(addr []string, schemaMap map[string]*Schema) []*Schema {
return nil
}
case TypeList, TypeSet:
isIndex := len(addr) > 0 && addr[0] == "#"

switch v := current.Elem.(type) {
case *Resource:
current = &Schema{
Expand All @@ -83,20 +85,52 @@ func addrToSchema(addr []string, schemaMap map[string]*Schema) []*Schema {
}
case *Schema:
current = v
case ValueType:
current = &Schema{Type: v}
default:
// we may not know the Elem type and are just looking for the
// index
if isIndex {
break
}

if len(addr) == 0 {
// we've processed the address, so return what we've
// collected
return result
}

if len(addr) == 1 {
if _, err := strconv.Atoi(addr[0]); err == nil {
// we're indexing a value without a schema. This can
// happen if the list is nested in another schema type.
// Default to a TypeString like we do with a map
current = &Schema{Type: TypeString}
break
}
}

return nil
}

// If we only have one more thing and the next thing
// is a #, then we're accessing the index which is always
// an int.
if len(addr) > 0 && addr[0] == "#" {
if isIndex {
current = &Schema{Type: TypeInt}
break
}

case TypeMap:
if len(addr) > 0 {
current = &Schema{Type: TypeString}
switch v := current.Elem.(type) {
case ValueType:
current = &Schema{Type: v}
default:
// maps default to string values. This is all we can have
// if this is nested in another list or map.
current = &Schema{Type: TypeString}
}
}
case typeObject:
// If we're already in the object, then we want to handle Sets
Expand Down
13 changes: 12 additions & 1 deletion helper/schema/field_reader_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,18 @@ func (r *ConfigFieldReader) readMap(k string, schema *Schema) (FieldReadResult,
// the type of the raw value.
mraw, ok := r.Config.GetRaw(k)
if !ok {
return FieldReadResult{}, nil
// check if this is from an interpolated field by seeing if it exists
// in the config
_, ok := r.Config.Get(k)
if !ok {
// this really doesn't exist
return FieldReadResult{}, nil
}

// We couldn't fetch the value from a nested data structure, so treat the
// raw value as an interpolation string. The mraw value is only used
// for the type switch below.
mraw = "${INTERPOLATED}"
}

result := make(map[string]interface{})
Expand Down
178 changes: 156 additions & 22 deletions helper/schema/field_reader_config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -219,15 +219,27 @@ func TestConfigFieldReader_ComputedMap(t *testing.T) {
Type: TypeMap,
Computed: true,
},
"listmap": &Schema{
Type: TypeMap,
Computed: true,
Elem: TypeList,
},
"maplist": &Schema{
Type: TypeList,
Computed: true,
Elem: TypeMap,
},
}

cases := map[string]struct {
cases := []struct {
Name string
Addr []string
Result FieldReadResult
Config *terraform.ResourceConfig
Err bool
}{
"set, normal": {
{
"set, normal",
[]string{"map"},
FieldReadResult{
Value: map[string]interface{}{
Expand All @@ -244,7 +256,8 @@ func TestConfigFieldReader_ComputedMap(t *testing.T) {
false,
},

"computed element": {
{
"computed element",
[]string{"map"},
FieldReadResult{
Exists: true,
Expand All @@ -263,7 +276,8 @@ func TestConfigFieldReader_ComputedMap(t *testing.T) {
false,
},

"native map": {
{
"native map",
[]string{"map"},
FieldReadResult{
Value: map[string]interface{}{
Expand Down Expand Up @@ -292,27 +306,147 @@ func TestConfigFieldReader_ComputedMap(t *testing.T) {
}),
false,
},

{
"map-from-list-of-maps",
[]string{"maplist", "0"},
FieldReadResult{
Value: map[string]interface{}{
"key": "bar",
},
Exists: true,
Computed: false,
},
testConfigInterpolate(t, map[string]interface{}{
"maplist": "${var.foo}",
}, map[string]ast.Variable{
"var.foo": ast.Variable{
Type: ast.TypeList,
Value: []ast.Variable{
{
Type: ast.TypeMap,
Value: map[string]ast.Variable{
"key": ast.Variable{
Type: ast.TypeString,
Value: "bar",
},
},
},
},
},
}),
false,
},

{
"value-from-list-of-maps",
[]string{"maplist", "0", "key"},
FieldReadResult{
Value: "bar",
Exists: true,
Computed: false,
},
testConfigInterpolate(t, map[string]interface{}{
"maplist": "${var.foo}",
}, map[string]ast.Variable{
"var.foo": ast.Variable{
Type: ast.TypeList,
Value: []ast.Variable{
{
Type: ast.TypeMap,
Value: map[string]ast.Variable{
"key": ast.Variable{
Type: ast.TypeString,
Value: "bar",
},
},
},
},
},
}),
false,
},

{
"list-from-map-of-lists",
[]string{"listmap", "key"},
FieldReadResult{
Value: []interface{}{"bar"},
Exists: true,
Computed: false,
},
testConfigInterpolate(t, map[string]interface{}{
"listmap": "${var.foo}",
}, map[string]ast.Variable{
"var.foo": ast.Variable{
Type: ast.TypeMap,
Value: map[string]ast.Variable{
"key": ast.Variable{
Type: ast.TypeList,
Value: []ast.Variable{
ast.Variable{
Type: ast.TypeString,
Value: "bar",
},
},
},
},
},
}),
false,
},

{
"value-from-map-of-lists",
[]string{"listmap", "key", "0"},
FieldReadResult{
Value: "bar",
Exists: true,
Computed: false,
},
testConfigInterpolate(t, map[string]interface{}{
"listmap": "${var.foo}",
}, map[string]ast.Variable{
"var.foo": ast.Variable{
Type: ast.TypeMap,
Value: map[string]ast.Variable{
"key": ast.Variable{
Type: ast.TypeList,
Value: []ast.Variable{
ast.Variable{
Type: ast.TypeString,
Value: "bar",
},
},
},
},
},
}),
false,
},
}

for name, tc := range cases {
r := &ConfigFieldReader{
Schema: schema,
Config: tc.Config,
}
out, err := r.ReadField(tc.Addr)
if err != nil != tc.Err {
t.Fatalf("%s: err: %s", name, err)
}
if s, ok := out.Value.(*Set); ok {
// If it is a set, convert to the raw map
out.Value = s.m
if len(s.m) == 0 {
out.Value = nil
for i, tc := range cases {
t.Run(fmt.Sprintf("%d-%s", i, tc.Name), func(t *testing.T) {
r := &ConfigFieldReader{
Schema: schema,
Config: tc.Config,
}
}
if !reflect.DeepEqual(tc.Result, out) {
t.Fatalf("%s: bad: %#v", name, out)
}
out, err := r.ReadField(tc.Addr)
if err != nil != tc.Err {
t.Fatal(err)
}
if s, ok := out.Value.(*Set); ok {
// If it is a set, convert to the raw map
out.Value = s.m
if len(s.m) == 0 {
out.Value = nil
}
}
if !reflect.DeepEqual(tc.Result, out) {
t.Fatalf("\nexpected: %#v\ngot: %#v", tc.Result, out)
}
})
}
}

Expand Down
Loading

0 comments on commit 7677bd9

Please sign in to comment.