Merge pull request #9268 from hashicorp/jbardin/hcl-maps

Get rid of the list when parsing HCL maps for vars
This commit is contained in:
James Bardin 2016-10-13 14:18:29 -04:00 committed by GitHub
commit 4f91507d05
4 changed files with 118 additions and 9 deletions

View File

@ -112,6 +112,10 @@ func loadKVFile(rawPath string) (map[string]interface{}, error) {
"Decoding errors are usually caused by an invalid format.", "Decoding errors are usually caused by an invalid format.",
err) err)
} }
err = flattenMultiMaps(result)
if err != nil {
return nil, err
}
return result, nil return result, nil
} }
@ -202,10 +206,34 @@ func parseVarFlagAsHCL(input string) (string, interface{}, error) {
return "", nil, fmt.Errorf("Cannot parse value for variable %s (%q) as valid HCL. Only one value may be specified.", probablyName, input) return "", nil, fmt.Errorf("Cannot parse value for variable %s (%q) as valid HCL. Only one value may be specified.", probablyName, input)
} }
for k, v := range decoded { err = flattenMultiMaps(decoded)
return k, v, nil if err != nil {
return probablyName, "", err
} }
// Should be unreachable var k string
return "", nil, fmt.Errorf("No value for variable: %s", input) var v interface{}
for k, v = range decoded {
break
}
return k, v, nil
}
// Variables don't support any type that can be configured via multiple
// declarations of the same HCL map, so any instances of
// []map[string]interface{} are either a single map that can be flattened, or
// are invalid config.
func flattenMultiMaps(m map[string]interface{}) error {
for k, v := range m {
switch v := v.(type) {
case []map[string]interface{}:
switch {
case len(v) > 1:
return fmt.Errorf("multiple map declarations not supported for variables")
case len(v) == 1:
m[k] = v[0]
}
}
}
return nil
} }

View File

@ -119,11 +119,9 @@ func TestFlagTypedKV(t *testing.T) {
{ {
`key={"hello" = "world", "foo" = "bar"}`, `key={"hello" = "world", "foo" = "bar"}`,
map[string]interface{}{ map[string]interface{}{
"key": []map[string]interface{}{ "key": map[string]interface{}{
map[string]interface{}{ "hello": "world",
"hello": "world", "foo": "bar",
"foo": "bar",
},
}, },
}, },
false, false,
@ -193,6 +191,10 @@ func TestFlagKVFile(t *testing.T) {
inputLibucl := ` inputLibucl := `
foo = "bar" foo = "bar"
` `
inputMap := `
foo = {
k = "v"
}`
inputJson := `{ inputJson := `{
"foo": "bar"}` "foo": "bar"}`
@ -219,6 +221,16 @@ foo = "bar"
map[string]interface{}{"map.key": "foo"}, map[string]interface{}{"map.key": "foo"},
false, false,
}, },
{
inputMap,
map[string]interface{}{
"foo": map[string]interface{}{
"k": "v",
},
},
false,
},
} }
path := testTempFile(t) path := testTempFile(t)

View File

@ -98,6 +98,19 @@ func (n *EvalWriteOutput) Eval(ctx EvalContext) (interface{}, error) {
Sensitive: n.Sensitive, Sensitive: n.Sensitive,
Value: valueTyped, Value: valueTyped,
} }
case []map[string]interface{}:
// an HCL map is multi-valued, so if this was read out of a config the
// map may still be in a slice.
if len(valueTyped) == 1 {
mod.Outputs[n.Name] = &OutputState{
Type: "map",
Sensitive: n.Sensitive,
Value: valueTyped[0],
}
break
}
return nil, fmt.Errorf("output %s type (%T) with %d values not valid for type map",
n.Name, valueTyped, len(valueTyped))
default: default:
return nil, fmt.Errorf("output %s is not a valid type (%T)\n", n.Name, valueTyped) return nil, fmt.Errorf("output %s is not a valid type (%T)\n", n.Name, valueTyped)
} }

View File

@ -0,0 +1,56 @@
package terraform
import (
"sync"
"testing"
)
func TestEvalWriteMapOutput(t *testing.T) {
ctx := new(MockEvalContext)
ctx.StateState = NewState()
ctx.StateLock = new(sync.RWMutex)
cases := []struct {
name string
cfg *ResourceConfig
err bool
}{
{
// Eval should recognize a single map in a slice, and collapse it
// into the map value
"single-map",
&ResourceConfig{
Config: map[string]interface{}{
"value": []map[string]interface{}{
map[string]interface{}{"a": "b"},
},
},
},
false,
},
{
// we can't apply a multi-valued map to a variable, so this should error
"multi-map",
&ResourceConfig{
Config: map[string]interface{}{
"value": []map[string]interface{}{
map[string]interface{}{"a": "b"},
map[string]interface{}{"c": "d"},
},
},
},
true,
},
}
for _, tc := range cases {
evalNode := &EvalWriteOutput{Name: tc.name}
ctx.InterpolateConfigResult = tc.cfg
t.Run(tc.name, func(t *testing.T) {
_, err := evalNode.Eval(ctx)
if err != nil && !tc.err {
t.Fatal(err)
}
})
}
}