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:
commit
4f91507d05
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -119,13 +119,11 @@ 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)
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue