Merge pull request #9634 from hashicorp/jbardin/GH-9549
Fix for missing computed map entries disappearing
This commit is contained in:
commit
b93331ba20
|
@ -149,6 +149,10 @@ func (v *ModuleVariable) FullKey() string {
|
|||
return v.key
|
||||
}
|
||||
|
||||
func (v *ModuleVariable) GoString() string {
|
||||
return fmt.Sprintf("*%#v", *v)
|
||||
}
|
||||
|
||||
func NewPathVariable(key string) (*PathVariable, error) {
|
||||
var fieldType PathValueType
|
||||
parts := strings.SplitN(key, ".", 2)
|
||||
|
|
|
@ -240,6 +240,22 @@ func TestDetectVariables(t *testing.T) {
|
|||
},
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
`foo ${module.foo.output["key"]}`,
|
||||
[]InterpolatedVariable{
|
||||
&ModuleVariable{
|
||||
Name: "foo",
|
||||
Field: "output",
|
||||
key: "module.foo.output",
|
||||
},
|
||||
&ModuleVariable{
|
||||
Name: "foo",
|
||||
Field: "output",
|
||||
key: "module.foo.output",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
|
|
|
@ -176,8 +176,7 @@ func (w *interpolationWalker) Primitive(v reflect.Value) error {
|
|||
}
|
||||
|
||||
if remove {
|
||||
w.removeCurrent()
|
||||
return nil
|
||||
w.unknownKeys = append(w.unknownKeys, strings.Join(w.key, "."))
|
||||
}
|
||||
|
||||
resultVal := reflect.ValueOf(replaceVal)
|
||||
|
@ -209,27 +208,6 @@ func (w *interpolationWalker) Primitive(v reflect.Value) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (w *interpolationWalker) removeCurrent() {
|
||||
// Append the key to the unknown keys
|
||||
w.unknownKeys = append(w.unknownKeys, strings.Join(w.key, "."))
|
||||
|
||||
for i := 1; i <= len(w.cs); i++ {
|
||||
c := w.cs[len(w.cs)-i]
|
||||
switch c.Kind() {
|
||||
case reflect.Map:
|
||||
// Zero value so that we delete the map key
|
||||
var val reflect.Value
|
||||
|
||||
// Get the key and delete it
|
||||
k := w.csData.(reflect.Value)
|
||||
c.SetMapIndex(k, val)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
panic("No container found for removeCurrent")
|
||||
}
|
||||
|
||||
func (w *interpolationWalker) replaceCurrent(v reflect.Value) {
|
||||
c := w.cs[len(w.cs)-2]
|
||||
switch c.Kind() {
|
||||
|
|
|
@ -169,8 +169,14 @@ func TestInterpolationWalker_replace(t *testing.T) {
|
|||
"bing",
|
||||
},
|
||||
},
|
||||
Output: map[string]interface{}{},
|
||||
Value: []interface{}{UnknownVariableValue, "baz"},
|
||||
Output: map[string]interface{}{
|
||||
"foo": []interface{}{
|
||||
UnknownVariableValue,
|
||||
"baz",
|
||||
"bing",
|
||||
},
|
||||
},
|
||||
Value: []interface{}{UnknownVariableValue, "baz"},
|
||||
},
|
||||
}
|
||||
|
||||
|
|
|
@ -127,27 +127,6 @@ func (r *RawConfig) Interpolate(vs map[string]ast.Variable) error {
|
|||
|
||||
config := langEvalConfig(vs)
|
||||
return r.interpolate(func(root ast.Node) (interface{}, error) {
|
||||
// We detect the variables again and check if the value of any
|
||||
// of the variables is the computed value. If it is, then we
|
||||
// treat this entire value as computed.
|
||||
//
|
||||
// We have to do this here before the `lang.Eval` because
|
||||
// if any of the variables it depends on are computed, then
|
||||
// the interpolation can fail at runtime for other reasons. Example:
|
||||
// `${count.index+1}`: in a world where `count.index` is computed,
|
||||
// this would fail a type check since the computed placeholder is
|
||||
// a string, but realistically the whole value is just computed.
|
||||
vars, err := DetectVariables(root)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
for _, v := range vars {
|
||||
varVal, ok := vs[v.FullKey()]
|
||||
if ok && varVal.Value == UnknownVariableValue {
|
||||
return UnknownVariableValue, nil
|
||||
}
|
||||
}
|
||||
|
||||
// None of the variables we need are computed, meaning we should
|
||||
// be able to properly evaluate.
|
||||
result, err := hil.Eval(root, config)
|
||||
|
|
|
@ -191,7 +191,7 @@ func TestRawConfig_merge(t *testing.T) {
|
|||
},
|
||||
"var.baz": ast.Variable{
|
||||
Value: UnknownVariableValue,
|
||||
Type: ast.TypeString,
|
||||
Type: ast.TypeUnknown,
|
||||
},
|
||||
}
|
||||
if err := rc2.Interpolate(vars); err != nil {
|
||||
|
@ -216,6 +216,7 @@ func TestRawConfig_merge(t *testing.T) {
|
|||
expected := map[string]interface{}{
|
||||
"foo": "foovalue",
|
||||
"bar": "barvalue",
|
||||
"baz": UnknownVariableValue,
|
||||
}
|
||||
if !reflect.DeepEqual(actual, expected) {
|
||||
t.Fatalf("bad: %#v", actual)
|
||||
|
@ -250,7 +251,7 @@ func TestRawConfig_unknown(t *testing.T) {
|
|||
vars := map[string]ast.Variable{
|
||||
"var.bar": ast.Variable{
|
||||
Value: UnknownVariableValue,
|
||||
Type: ast.TypeString,
|
||||
Type: ast.TypeUnknown,
|
||||
},
|
||||
}
|
||||
if err := rc.Interpolate(vars); err != nil {
|
||||
|
@ -258,7 +259,7 @@ func TestRawConfig_unknown(t *testing.T) {
|
|||
}
|
||||
|
||||
actual := rc.Config()
|
||||
expected := map[string]interface{}{}
|
||||
expected := map[string]interface{}{"foo": UnknownVariableValue}
|
||||
|
||||
if !reflect.DeepEqual(actual, expected) {
|
||||
t.Fatalf("bad: %#v", actual)
|
||||
|
@ -283,7 +284,7 @@ func TestRawConfig_unknownPartial(t *testing.T) {
|
|||
vars := map[string]ast.Variable{
|
||||
"var.bar": ast.Variable{
|
||||
Value: UnknownVariableValue,
|
||||
Type: ast.TypeString,
|
||||
Type: ast.TypeUnknown,
|
||||
},
|
||||
}
|
||||
if err := rc.Interpolate(vars); err != nil {
|
||||
|
@ -291,7 +292,42 @@ func TestRawConfig_unknownPartial(t *testing.T) {
|
|||
}
|
||||
|
||||
actual := rc.Config()
|
||||
expected := map[string]interface{}{}
|
||||
expected := map[string]interface{}{"foo": UnknownVariableValue}
|
||||
|
||||
if !reflect.DeepEqual(actual, expected) {
|
||||
t.Fatalf("bad: %#v", actual)
|
||||
}
|
||||
|
||||
expectedKeys := []string{"foo"}
|
||||
if !reflect.DeepEqual(rc.UnknownKeys(), expectedKeys) {
|
||||
t.Fatalf("bad: %#v", rc.UnknownKeys())
|
||||
}
|
||||
}
|
||||
|
||||
func TestRawConfig_unknownPartialList(t *testing.T) {
|
||||
raw := map[string]interface{}{
|
||||
"foo": []interface{}{
|
||||
"${var.bar}/32",
|
||||
},
|
||||
}
|
||||
|
||||
rc, err := NewRawConfig(raw)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
vars := map[string]ast.Variable{
|
||||
"var.bar": ast.Variable{
|
||||
Value: UnknownVariableValue,
|
||||
Type: ast.TypeUnknown,
|
||||
},
|
||||
}
|
||||
if err := rc.Interpolate(vars); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
actual := rc.Config()
|
||||
expected := map[string]interface{}{"foo": []interface{}{UnknownVariableValue}}
|
||||
|
||||
if !reflect.DeepEqual(actual, expected) {
|
||||
t.Fatalf("bad: %#v", actual)
|
||||
|
|
|
@ -3,6 +3,7 @@ package diff
|
|||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/terraform/config"
|
||||
"github.com/hashicorp/terraform/flatmap"
|
||||
"github.com/hashicorp/terraform/terraform"
|
||||
)
|
||||
|
@ -94,7 +95,7 @@ func (b *ResourceBuilder) Diff(
|
|||
|
||||
// If this key is in the cleaned config, then use that value
|
||||
// because it'll have its variables properly interpolated
|
||||
if cleanV, ok := flatConfig[k]; ok {
|
||||
if cleanV, ok := flatConfig[k]; ok && cleanV != config.UnknownVariableValue {
|
||||
v = cleanV
|
||||
originalV = v
|
||||
|
||||
|
|
|
@ -344,7 +344,7 @@ func TestConfigFieldReader_ComputedSet(t *testing.T) {
|
|||
}, map[string]ast.Variable{
|
||||
"var.foo": ast.Variable{
|
||||
Value: config.UnknownVariableValue,
|
||||
Type: ast.TypeString,
|
||||
Type: ast.TypeUnknown,
|
||||
},
|
||||
}),
|
||||
false,
|
||||
|
@ -362,7 +362,7 @@ func TestConfigFieldReader_ComputedSet(t *testing.T) {
|
|||
}, map[string]ast.Variable{
|
||||
"var.foo": ast.Variable{
|
||||
Value: config.UnknownVariableValue,
|
||||
Type: ast.TypeString,
|
||||
Type: ast.TypeUnknown,
|
||||
},
|
||||
}),
|
||||
false,
|
||||
|
|
|
@ -131,7 +131,8 @@ func interfaceToVariableSwallowError(input interface{}) ast.Variable {
|
|||
}
|
||||
|
||||
func TestSchemaMap_Diff(t *testing.T) {
|
||||
cases := map[string]struct {
|
||||
cases := []struct {
|
||||
Name string
|
||||
Schema map[string]*Schema
|
||||
State *terraform.InstanceState
|
||||
Config map[string]interface{}
|
||||
|
@ -139,7 +140,7 @@ func TestSchemaMap_Diff(t *testing.T) {
|
|||
Diff *terraform.InstanceDiff
|
||||
Err bool
|
||||
}{
|
||||
"#0": {
|
||||
{
|
||||
Schema: map[string]*Schema{
|
||||
"availability_zone": &Schema{
|
||||
Type: TypeString,
|
||||
|
@ -168,7 +169,7 @@ func TestSchemaMap_Diff(t *testing.T) {
|
|||
Err: false,
|
||||
},
|
||||
|
||||
"#1": {
|
||||
{
|
||||
Schema: map[string]*Schema{
|
||||
"availability_zone": &Schema{
|
||||
Type: TypeString,
|
||||
|
@ -195,7 +196,7 @@ func TestSchemaMap_Diff(t *testing.T) {
|
|||
Err: false,
|
||||
},
|
||||
|
||||
"#2": {
|
||||
{
|
||||
Schema: map[string]*Schema{
|
||||
"availability_zone": &Schema{
|
||||
Type: TypeString,
|
||||
|
@ -216,7 +217,8 @@ func TestSchemaMap_Diff(t *testing.T) {
|
|||
Err: false,
|
||||
},
|
||||
|
||||
"#3 Computed, but set in config": {
|
||||
{
|
||||
Name: "Computed, but set in config",
|
||||
Schema: map[string]*Schema{
|
||||
"availability_zone": &Schema{
|
||||
Type: TypeString,
|
||||
|
@ -247,7 +249,8 @@ func TestSchemaMap_Diff(t *testing.T) {
|
|||
Err: false,
|
||||
},
|
||||
|
||||
"#4 Default": {
|
||||
{
|
||||
Name: "Default",
|
||||
Schema: map[string]*Schema{
|
||||
"availability_zone": &Schema{
|
||||
Type: TypeString,
|
||||
|
@ -272,7 +275,8 @@ func TestSchemaMap_Diff(t *testing.T) {
|
|||
Err: false,
|
||||
},
|
||||
|
||||
"#5 DefaultFunc, value": {
|
||||
{
|
||||
Name: "DefaultFunc, value",
|
||||
Schema: map[string]*Schema{
|
||||
"availability_zone": &Schema{
|
||||
Type: TypeString,
|
||||
|
@ -299,7 +303,8 @@ func TestSchemaMap_Diff(t *testing.T) {
|
|||
Err: false,
|
||||
},
|
||||
|
||||
"#6 DefaultFunc, configuration set": {
|
||||
{
|
||||
Name: "DefaultFunc, configuration set",
|
||||
Schema: map[string]*Schema{
|
||||
"availability_zone": &Schema{
|
||||
Type: TypeString,
|
||||
|
@ -328,7 +333,8 @@ func TestSchemaMap_Diff(t *testing.T) {
|
|||
Err: false,
|
||||
},
|
||||
|
||||
"String with StateFunc": {
|
||||
{
|
||||
Name: "String with StateFunc",
|
||||
Schema: map[string]*Schema{
|
||||
"availability_zone": &Schema{
|
||||
Type: TypeString,
|
||||
|
@ -359,7 +365,8 @@ func TestSchemaMap_Diff(t *testing.T) {
|
|||
Err: false,
|
||||
},
|
||||
|
||||
"StateFunc not called with nil value": {
|
||||
{
|
||||
Name: "StateFunc not called with nil value",
|
||||
Schema: map[string]*Schema{
|
||||
"availability_zone": &Schema{
|
||||
Type: TypeString,
|
||||
|
@ -389,7 +396,8 @@ func TestSchemaMap_Diff(t *testing.T) {
|
|||
Err: false,
|
||||
},
|
||||
|
||||
"#8 Variable (just checking)": {
|
||||
{
|
||||
Name: "Variable (just checking)",
|
||||
Schema: map[string]*Schema{
|
||||
"availability_zone": &Schema{
|
||||
Type: TypeString,
|
||||
|
@ -419,7 +427,8 @@ func TestSchemaMap_Diff(t *testing.T) {
|
|||
Err: false,
|
||||
},
|
||||
|
||||
"#9 Variable computed": {
|
||||
{
|
||||
Name: "Variable computed",
|
||||
Schema: map[string]*Schema{
|
||||
"availability_zone": &Schema{
|
||||
Type: TypeString,
|
||||
|
@ -450,7 +459,8 @@ func TestSchemaMap_Diff(t *testing.T) {
|
|||
Err: false,
|
||||
},
|
||||
|
||||
"#10 Int decode": {
|
||||
{
|
||||
Name: "Int decode",
|
||||
Schema: map[string]*Schema{
|
||||
"port": &Schema{
|
||||
Type: TypeInt,
|
||||
|
@ -479,7 +489,8 @@ func TestSchemaMap_Diff(t *testing.T) {
|
|||
Err: false,
|
||||
},
|
||||
|
||||
"#11 bool decode": {
|
||||
{
|
||||
Name: "bool decode",
|
||||
Schema: map[string]*Schema{
|
||||
"port": &Schema{
|
||||
Type: TypeBool,
|
||||
|
@ -508,7 +519,8 @@ func TestSchemaMap_Diff(t *testing.T) {
|
|||
Err: false,
|
||||
},
|
||||
|
||||
"#12 Bool": {
|
||||
{
|
||||
Name: "Bool",
|
||||
Schema: map[string]*Schema{
|
||||
"delete": &Schema{
|
||||
Type: TypeBool,
|
||||
|
@ -530,7 +542,8 @@ func TestSchemaMap_Diff(t *testing.T) {
|
|||
Err: false,
|
||||
},
|
||||
|
||||
"#13 List decode": {
|
||||
{
|
||||
Name: "List decode",
|
||||
Schema: map[string]*Schema{
|
||||
"ports": &Schema{
|
||||
Type: TypeList,
|
||||
|
@ -569,7 +582,7 @@ func TestSchemaMap_Diff(t *testing.T) {
|
|||
Err: false,
|
||||
},
|
||||
|
||||
"#14": {
|
||||
{
|
||||
Schema: map[string]*Schema{
|
||||
"ports": &Schema{
|
||||
Type: TypeList,
|
||||
|
@ -612,7 +625,7 @@ func TestSchemaMap_Diff(t *testing.T) {
|
|||
Err: false,
|
||||
},
|
||||
|
||||
"#15": {
|
||||
{
|
||||
Schema: map[string]*Schema{
|
||||
"ports": &Schema{
|
||||
Type: TypeList,
|
||||
|
@ -645,7 +658,7 @@ func TestSchemaMap_Diff(t *testing.T) {
|
|||
Err: false,
|
||||
},
|
||||
|
||||
"#16": {
|
||||
{
|
||||
Schema: map[string]*Schema{
|
||||
"ports": &Schema{
|
||||
Type: TypeList,
|
||||
|
@ -672,7 +685,8 @@ func TestSchemaMap_Diff(t *testing.T) {
|
|||
Err: false,
|
||||
},
|
||||
|
||||
"#17": {
|
||||
{
|
||||
Name: "",
|
||||
Schema: map[string]*Schema{
|
||||
"ports": &Schema{
|
||||
Type: TypeList,
|
||||
|
@ -709,7 +723,8 @@ func TestSchemaMap_Diff(t *testing.T) {
|
|||
Err: false,
|
||||
},
|
||||
|
||||
"#18": {
|
||||
{
|
||||
Name: "",
|
||||
Schema: map[string]*Schema{
|
||||
"ports": &Schema{
|
||||
Type: TypeList,
|
||||
|
@ -753,7 +768,8 @@ func TestSchemaMap_Diff(t *testing.T) {
|
|||
Err: false,
|
||||
},
|
||||
|
||||
"#19": {
|
||||
{
|
||||
Name: "",
|
||||
Schema: map[string]*Schema{
|
||||
"ports": &Schema{
|
||||
Type: TypeList,
|
||||
|
@ -779,7 +795,8 @@ func TestSchemaMap_Diff(t *testing.T) {
|
|||
Err: false,
|
||||
},
|
||||
|
||||
"#20 Set": {
|
||||
{
|
||||
Name: "Set",
|
||||
Schema: map[string]*Schema{
|
||||
"ports": &Schema{
|
||||
Type: TypeSet,
|
||||
|
@ -821,7 +838,8 @@ func TestSchemaMap_Diff(t *testing.T) {
|
|||
Err: false,
|
||||
},
|
||||
|
||||
"#21 Set": {
|
||||
{
|
||||
Name: "Set",
|
||||
Schema: map[string]*Schema{
|
||||
"ports": &Schema{
|
||||
Type: TypeSet,
|
||||
|
@ -847,7 +865,8 @@ func TestSchemaMap_Diff(t *testing.T) {
|
|||
Err: false,
|
||||
},
|
||||
|
||||
"#22 Set": {
|
||||
{
|
||||
Name: "Set",
|
||||
Schema: map[string]*Schema{
|
||||
"ports": &Schema{
|
||||
Type: TypeSet,
|
||||
|
@ -876,7 +895,8 @@ func TestSchemaMap_Diff(t *testing.T) {
|
|||
Err: false,
|
||||
},
|
||||
|
||||
"#23 Set": {
|
||||
{
|
||||
Name: "Set",
|
||||
Schema: map[string]*Schema{
|
||||
"ports": &Schema{
|
||||
Type: TypeSet,
|
||||
|
@ -922,7 +942,8 @@ func TestSchemaMap_Diff(t *testing.T) {
|
|||
Err: false,
|
||||
},
|
||||
|
||||
"#24 Set": {
|
||||
{
|
||||
Name: "Set",
|
||||
Schema: map[string]*Schema{
|
||||
"ports": &Schema{
|
||||
Type: TypeSet,
|
||||
|
@ -958,7 +979,8 @@ func TestSchemaMap_Diff(t *testing.T) {
|
|||
Err: false,
|
||||
},
|
||||
|
||||
"#25 Set": {
|
||||
{
|
||||
Name: "Set",
|
||||
Schema: map[string]*Schema{
|
||||
"ports": &Schema{
|
||||
Type: TypeSet,
|
||||
|
@ -1006,7 +1028,8 @@ func TestSchemaMap_Diff(t *testing.T) {
|
|||
Err: false,
|
||||
},
|
||||
|
||||
"#26 Set": {
|
||||
{
|
||||
Name: "Set",
|
||||
Schema: map[string]*Schema{
|
||||
"ports": &Schema{
|
||||
Type: TypeSet,
|
||||
|
@ -1050,7 +1073,8 @@ func TestSchemaMap_Diff(t *testing.T) {
|
|||
Err: false,
|
||||
},
|
||||
|
||||
"#27 Set": {
|
||||
{
|
||||
Name: "Set",
|
||||
Schema: map[string]*Schema{
|
||||
"ports": &Schema{
|
||||
Type: TypeSet,
|
||||
|
@ -1078,7 +1102,8 @@ func TestSchemaMap_Diff(t *testing.T) {
|
|||
Err: false,
|
||||
},
|
||||
|
||||
"#28 Set": {
|
||||
{
|
||||
Name: "Set",
|
||||
Schema: map[string]*Schema{
|
||||
"ingress": &Schema{
|
||||
Type: TypeSet,
|
||||
|
@ -1130,7 +1155,8 @@ func TestSchemaMap_Diff(t *testing.T) {
|
|||
Err: false,
|
||||
},
|
||||
|
||||
"#29 List of structure decode": {
|
||||
{
|
||||
Name: "List of structure decode",
|
||||
Schema: map[string]*Schema{
|
||||
"ingress": &Schema{
|
||||
Type: TypeList,
|
||||
|
@ -1172,7 +1198,8 @@ func TestSchemaMap_Diff(t *testing.T) {
|
|||
Err: false,
|
||||
},
|
||||
|
||||
"#30 ComputedWhen": {
|
||||
{
|
||||
Name: "ComputedWhen",
|
||||
Schema: map[string]*Schema{
|
||||
"availability_zone": &Schema{
|
||||
Type: TypeString,
|
||||
|
@ -1202,7 +1229,8 @@ func TestSchemaMap_Diff(t *testing.T) {
|
|||
Err: false,
|
||||
},
|
||||
|
||||
"#31": {
|
||||
{
|
||||
Name: "",
|
||||
Schema: map[string]*Schema{
|
||||
"availability_zone": &Schema{
|
||||
Type: TypeString,
|
||||
|
@ -1280,7 +1308,8 @@ func TestSchemaMap_Diff(t *testing.T) {
|
|||
},
|
||||
*/
|
||||
|
||||
"#32 Maps": {
|
||||
{
|
||||
Name: "Maps",
|
||||
Schema: map[string]*Schema{
|
||||
"config_vars": &Schema{
|
||||
Type: TypeMap,
|
||||
|
@ -1314,7 +1343,8 @@ func TestSchemaMap_Diff(t *testing.T) {
|
|||
Err: false,
|
||||
},
|
||||
|
||||
"#33 Maps": {
|
||||
{
|
||||
Name: "Maps",
|
||||
Schema: map[string]*Schema{
|
||||
"config_vars": &Schema{
|
||||
Type: TypeMap,
|
||||
|
@ -1351,7 +1381,8 @@ func TestSchemaMap_Diff(t *testing.T) {
|
|||
Err: false,
|
||||
},
|
||||
|
||||
"#34 Maps": {
|
||||
{
|
||||
Name: "Maps",
|
||||
Schema: map[string]*Schema{
|
||||
"vars": &Schema{
|
||||
Type: TypeMap,
|
||||
|
@ -1391,7 +1422,8 @@ func TestSchemaMap_Diff(t *testing.T) {
|
|||
Err: false,
|
||||
},
|
||||
|
||||
"#35 Maps": {
|
||||
{
|
||||
Name: "Maps",
|
||||
Schema: map[string]*Schema{
|
||||
"vars": &Schema{
|
||||
Type: TypeMap,
|
||||
|
@ -1412,7 +1444,8 @@ func TestSchemaMap_Diff(t *testing.T) {
|
|||
Err: false,
|
||||
},
|
||||
|
||||
"#36 Maps": {
|
||||
{
|
||||
Name: "Maps",
|
||||
Schema: map[string]*Schema{
|
||||
"config_vars": &Schema{
|
||||
Type: TypeList,
|
||||
|
@ -1451,7 +1484,8 @@ func TestSchemaMap_Diff(t *testing.T) {
|
|||
Err: false,
|
||||
},
|
||||
|
||||
"#37 Maps": {
|
||||
{
|
||||
Name: "Maps",
|
||||
Schema: map[string]*Schema{
|
||||
"config_vars": &Schema{
|
||||
Type: TypeList,
|
||||
|
@ -1493,7 +1527,8 @@ func TestSchemaMap_Diff(t *testing.T) {
|
|||
Err: false,
|
||||
},
|
||||
|
||||
"#38 ForceNews": {
|
||||
{
|
||||
Name: "ForceNews",
|
||||
Schema: map[string]*Schema{
|
||||
"availability_zone": &Schema{
|
||||
Type: TypeString,
|
||||
|
@ -1538,7 +1573,8 @@ func TestSchemaMap_Diff(t *testing.T) {
|
|||
Err: false,
|
||||
},
|
||||
|
||||
"#39 Set": {
|
||||
{
|
||||
Name: "Set",
|
||||
Schema: map[string]*Schema{
|
||||
"availability_zone": &Schema{
|
||||
Type: TypeString,
|
||||
|
@ -1588,7 +1624,8 @@ func TestSchemaMap_Diff(t *testing.T) {
|
|||
Err: false,
|
||||
},
|
||||
|
||||
"#40 Set": {
|
||||
{
|
||||
Name: "Set",
|
||||
Schema: map[string]*Schema{
|
||||
"instances": &Schema{
|
||||
Type: TypeSet,
|
||||
|
@ -1626,7 +1663,8 @@ func TestSchemaMap_Diff(t *testing.T) {
|
|||
Err: false,
|
||||
},
|
||||
|
||||
"#41 Set": {
|
||||
{
|
||||
Name: "Set",
|
||||
Schema: map[string]*Schema{
|
||||
"route": &Schema{
|
||||
Type: TypeSet,
|
||||
|
@ -1687,7 +1725,8 @@ func TestSchemaMap_Diff(t *testing.T) {
|
|||
Err: false,
|
||||
},
|
||||
|
||||
"#42 Set": {
|
||||
{
|
||||
Name: "Set",
|
||||
Schema: map[string]*Schema{
|
||||
"route": &Schema{
|
||||
Type: TypeSet,
|
||||
|
@ -1752,7 +1791,8 @@ func TestSchemaMap_Diff(t *testing.T) {
|
|||
Err: false,
|
||||
},
|
||||
|
||||
"#43 - Computed maps": {
|
||||
{
|
||||
Name: "Computed maps",
|
||||
Schema: map[string]*Schema{
|
||||
"vars": &Schema{
|
||||
Type: TypeMap,
|
||||
|
@ -1776,7 +1816,8 @@ func TestSchemaMap_Diff(t *testing.T) {
|
|||
Err: false,
|
||||
},
|
||||
|
||||
"#44 - Computed maps": {
|
||||
{
|
||||
Name: "Computed maps",
|
||||
Schema: map[string]*Schema{
|
||||
"vars": &Schema{
|
||||
Type: TypeMap,
|
||||
|
@ -1812,7 +1853,8 @@ func TestSchemaMap_Diff(t *testing.T) {
|
|||
Err: false,
|
||||
},
|
||||
|
||||
"#45 - Empty": {
|
||||
{
|
||||
Name: " - Empty",
|
||||
Schema: map[string]*Schema{},
|
||||
|
||||
State: &terraform.InstanceState{},
|
||||
|
@ -1824,7 +1866,8 @@ func TestSchemaMap_Diff(t *testing.T) {
|
|||
Err: false,
|
||||
},
|
||||
|
||||
"#46 - Float": {
|
||||
{
|
||||
Name: "Float",
|
||||
Schema: map[string]*Schema{
|
||||
"some_threshold": &Schema{
|
||||
Type: TypeFloat,
|
||||
|
@ -1853,7 +1896,8 @@ func TestSchemaMap_Diff(t *testing.T) {
|
|||
Err: false,
|
||||
},
|
||||
|
||||
"#47 - https://github.com/hashicorp/terraform/issues/824": {
|
||||
{
|
||||
Name: "https://github.com/hashicorp/terraform/issues/824",
|
||||
Schema: map[string]*Schema{
|
||||
"block_device": &Schema{
|
||||
Type: TypeSet,
|
||||
|
@ -1906,7 +1950,8 @@ func TestSchemaMap_Diff(t *testing.T) {
|
|||
Err: false,
|
||||
},
|
||||
|
||||
"#48 - Zero value in state shouldn't result in diff": {
|
||||
{
|
||||
Name: "Zero value in state shouldn't result in diff",
|
||||
Schema: map[string]*Schema{
|
||||
"port": &Schema{
|
||||
Type: TypeBool,
|
||||
|
@ -1928,7 +1973,8 @@ func TestSchemaMap_Diff(t *testing.T) {
|
|||
Err: false,
|
||||
},
|
||||
|
||||
"#49 Set - Same as #48 but for sets": {
|
||||
{
|
||||
Name: "Same as prev, but for sets",
|
||||
Schema: map[string]*Schema{
|
||||
"route": &Schema{
|
||||
Type: TypeSet,
|
||||
|
@ -1970,7 +2016,8 @@ func TestSchemaMap_Diff(t *testing.T) {
|
|||
Err: false,
|
||||
},
|
||||
|
||||
"#50 - A set computed element shouldn't cause a diff": {
|
||||
{
|
||||
Name: "A set computed element shouldn't cause a diff",
|
||||
Schema: map[string]*Schema{
|
||||
"active": &Schema{
|
||||
Type: TypeBool,
|
||||
|
@ -1992,7 +2039,8 @@ func TestSchemaMap_Diff(t *testing.T) {
|
|||
Err: false,
|
||||
},
|
||||
|
||||
"#51 - An empty set should show up in the diff": {
|
||||
{
|
||||
Name: "An empty set should show up in the diff",
|
||||
Schema: map[string]*Schema{
|
||||
"instances": &Schema{
|
||||
Type: TypeSet,
|
||||
|
@ -2033,7 +2081,8 @@ func TestSchemaMap_Diff(t *testing.T) {
|
|||
Err: false,
|
||||
},
|
||||
|
||||
"#52 - Map with empty value": {
|
||||
{
|
||||
Name: "Map with empty value",
|
||||
Schema: map[string]*Schema{
|
||||
"vars": &Schema{
|
||||
Type: TypeMap,
|
||||
|
@ -2064,7 +2113,8 @@ func TestSchemaMap_Diff(t *testing.T) {
|
|||
Err: false,
|
||||
},
|
||||
|
||||
"#53 - Unset bool, not in state": {
|
||||
{
|
||||
Name: "Unset bool, not in state",
|
||||
Schema: map[string]*Schema{
|
||||
"force": &Schema{
|
||||
Type: TypeBool,
|
||||
|
@ -2082,7 +2132,8 @@ func TestSchemaMap_Diff(t *testing.T) {
|
|||
Err: false,
|
||||
},
|
||||
|
||||
"#54 - Unset set, not in state": {
|
||||
{
|
||||
Name: "Unset set, not in state",
|
||||
Schema: map[string]*Schema{
|
||||
"metadata_keys": &Schema{
|
||||
Type: TypeSet,
|
||||
|
@ -2102,7 +2153,8 @@ func TestSchemaMap_Diff(t *testing.T) {
|
|||
Err: false,
|
||||
},
|
||||
|
||||
"#55 - Unset list in state, should not show up computed": {
|
||||
{
|
||||
Name: "Unset list in state, should not show up computed",
|
||||
Schema: map[string]*Schema{
|
||||
"metadata_keys": &Schema{
|
||||
Type: TypeList,
|
||||
|
@ -2126,7 +2178,8 @@ func TestSchemaMap_Diff(t *testing.T) {
|
|||
Err: false,
|
||||
},
|
||||
|
||||
"#56 - Set element computed substring": {
|
||||
{
|
||||
Name: "Set element computed substring",
|
||||
Schema: map[string]*Schema{
|
||||
"ports": &Schema{
|
||||
Type: TypeSet,
|
||||
|
@ -2161,7 +2214,8 @@ func TestSchemaMap_Diff(t *testing.T) {
|
|||
Err: false,
|
||||
},
|
||||
|
||||
"#57 Computed map without config that's known to be empty does not generate diff": {
|
||||
{
|
||||
Name: "Computed map without config that's known to be empty does not generate diff",
|
||||
Schema: map[string]*Schema{
|
||||
"tags": &Schema{
|
||||
Type: TypeMap,
|
||||
|
@ -2182,7 +2236,8 @@ func TestSchemaMap_Diff(t *testing.T) {
|
|||
Err: false,
|
||||
},
|
||||
|
||||
"#58 Set with hyphen keys": {
|
||||
{
|
||||
Name: "Set with hyphen keys",
|
||||
Schema: map[string]*Schema{
|
||||
"route": &Schema{
|
||||
Type: TypeSet,
|
||||
|
@ -2238,7 +2293,8 @@ func TestSchemaMap_Diff(t *testing.T) {
|
|||
Err: false,
|
||||
},
|
||||
|
||||
"#59: StateFunc in nested set (#1759)": {
|
||||
{
|
||||
Name: ": StateFunc in nested set (#1759)",
|
||||
Schema: map[string]*Schema{
|
||||
"service_account": &Schema{
|
||||
Type: TypeList,
|
||||
|
@ -2303,7 +2359,8 @@ func TestSchemaMap_Diff(t *testing.T) {
|
|||
Err: false,
|
||||
},
|
||||
|
||||
"#60 - Removing set elements": {
|
||||
{
|
||||
Name: "Removing set elements",
|
||||
Schema: map[string]*Schema{
|
||||
"instances": &Schema{
|
||||
Type: TypeSet,
|
||||
|
@ -2355,7 +2412,8 @@ func TestSchemaMap_Diff(t *testing.T) {
|
|||
Err: false,
|
||||
},
|
||||
|
||||
"Bools can be set with 0/1 in config, still get true/false": {
|
||||
{
|
||||
Name: "Bools can be set with 0/1 in config, still get true/false",
|
||||
Schema: map[string]*Schema{
|
||||
"one": &Schema{
|
||||
Type: TypeBool,
|
||||
|
@ -2405,7 +2463,8 @@ func TestSchemaMap_Diff(t *testing.T) {
|
|||
Err: false,
|
||||
},
|
||||
|
||||
"tainted in state w/ no attr changes is still a replacement": {
|
||||
{
|
||||
Name: "tainted in state w/ no attr changes is still a replacement",
|
||||
Schema: map[string]*Schema{},
|
||||
|
||||
State: &terraform.InstanceState{
|
||||
|
@ -2425,7 +2484,8 @@ func TestSchemaMap_Diff(t *testing.T) {
|
|||
Err: false,
|
||||
},
|
||||
|
||||
"Set ForceNew only marks the changing element as ForceNew": {
|
||||
{
|
||||
Name: "Set ForceNew only marks the changing element as ForceNew",
|
||||
Schema: map[string]*Schema{
|
||||
"ports": &Schema{
|
||||
Type: TypeSet,
|
||||
|
@ -2480,7 +2540,8 @@ func TestSchemaMap_Diff(t *testing.T) {
|
|||
},
|
||||
},
|
||||
|
||||
"removed optional items should trigger ForceNew": {
|
||||
{
|
||||
Name: "removed optional items should trigger ForceNew",
|
||||
Schema: map[string]*Schema{
|
||||
"description": &Schema{
|
||||
Type: TypeString,
|
||||
|
@ -2512,7 +2573,8 @@ func TestSchemaMap_Diff(t *testing.T) {
|
|||
},
|
||||
|
||||
// GH-7715
|
||||
"computed value for boolean field": {
|
||||
{
|
||||
Name: "computed value for boolean field",
|
||||
Schema: map[string]*Schema{
|
||||
"foo": &Schema{
|
||||
Type: TypeBool,
|
||||
|
@ -2548,27 +2610,27 @@ func TestSchemaMap_Diff(t *testing.T) {
|
|||
},
|
||||
}
|
||||
|
||||
for tn, tc := range cases {
|
||||
t.Run(tn, func(t *testing.T) {
|
||||
for i, tc := range cases {
|
||||
t.Run(fmt.Sprintf("%d-%s", i, tc.Name), func(t *testing.T) {
|
||||
c, err := config.NewRawConfig(tc.Config)
|
||||
if err != nil {
|
||||
t.Fatalf("#%q err: %s", tn, err)
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if len(tc.ConfigVariables) > 0 {
|
||||
if err := c.Interpolate(tc.ConfigVariables); err != nil {
|
||||
t.Fatalf("#%q err: %s", tn, err)
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
d, err := schemaMap(tc.Schema).Diff(
|
||||
tc.State, terraform.NewResourceConfig(c))
|
||||
if err != nil != tc.Err {
|
||||
t.Fatalf("#%q err: %s", tn, err)
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(tc.Diff, d) {
|
||||
t.Fatalf("#%q:\n\nexpected:\n%#v\n\ngot:\n%#v", tn, tc.Diff, d)
|
||||
t.Fatalf("expected:\n%#v\n\ngot:\n%#v", tc.Diff, d)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
@ -87,7 +87,9 @@ func TestContext2Validate_computedVar(t *testing.T) {
|
|||
t.Fatalf("bad: %#v", w)
|
||||
}
|
||||
if len(e) > 0 {
|
||||
t.Fatalf("bad: %#v", e)
|
||||
for _, err := range e {
|
||||
t.Errorf("bad: %s", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -821,3 +823,29 @@ func TestContext2Validate_interpolateComputedModuleVarDef(t *testing.T) {
|
|||
t.Fatal("err:", e)
|
||||
}
|
||||
}
|
||||
|
||||
// Computed values are lost when a map is output from a module
|
||||
func TestContext2Validate_interpolateMap(t *testing.T) {
|
||||
input := new(MockUIInput)
|
||||
|
||||
m := testModule(t, "issue-9549")
|
||||
p := testProvider("null")
|
||||
p.ApplyFn = testApplyFn
|
||||
p.DiffFn = testDiffFn
|
||||
|
||||
ctx := testContext2(t, &ContextOpts{
|
||||
Module: m,
|
||||
Providers: map[string]ResourceProviderFactory{
|
||||
"template": testProviderFuncFixed(p),
|
||||
},
|
||||
UIInput: input,
|
||||
})
|
||||
|
||||
w, e := ctx.Validate()
|
||||
if w != nil {
|
||||
t.Log("warnings:", w)
|
||||
}
|
||||
if e != nil {
|
||||
t.Fatal("err:", e)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,9 +24,7 @@ func TestEvalValidateResource_managedResource(t *testing.T) {
|
|||
}
|
||||
|
||||
p := ResourceProvider(mp)
|
||||
rc := &ResourceConfig{
|
||||
Raw: map[string]interface{}{"foo": "bar"},
|
||||
}
|
||||
rc := testResourceConfig(t, map[string]interface{}{"foo": "bar"})
|
||||
node := &EvalValidateResource{
|
||||
Provider: &p,
|
||||
Config: &rc,
|
||||
|
@ -61,9 +59,7 @@ func TestEvalValidateResource_dataSource(t *testing.T) {
|
|||
}
|
||||
|
||||
p := ResourceProvider(mp)
|
||||
rc := &ResourceConfig{
|
||||
Raw: map[string]interface{}{"foo": "bar"},
|
||||
}
|
||||
rc := testResourceConfig(t, map[string]interface{}{"foo": "bar"})
|
||||
node := &EvalValidateResource{
|
||||
Provider: &p,
|
||||
Config: &rc,
|
||||
|
|
|
@ -121,11 +121,15 @@ func (i *Interpolater) valueCountVar(
|
|||
|
||||
func unknownVariable() ast.Variable {
|
||||
return ast.Variable{
|
||||
Type: ast.TypeString,
|
||||
Type: ast.TypeUnknown,
|
||||
Value: config.UnknownVariableValue,
|
||||
}
|
||||
}
|
||||
|
||||
func unknownValue() string {
|
||||
return hil.UnknownValue
|
||||
}
|
||||
|
||||
func (i *Interpolater) valueModuleVar(
|
||||
scope *InterpolationScope,
|
||||
n string,
|
||||
|
@ -215,10 +219,7 @@ func (i *Interpolater) valueResourceVar(
|
|||
// If we're computing all dynamic fields, then module vars count
|
||||
// and we mark it as computed.
|
||||
if i.Operation == walkValidate {
|
||||
result[n] = ast.Variable{
|
||||
Value: config.UnknownVariableValue,
|
||||
Type: ast.TypeString,
|
||||
}
|
||||
result[n] = unknownVariable()
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -243,10 +244,7 @@ func (i *Interpolater) valueResourceVar(
|
|||
// This applies only to graph nodes that interpolate during the
|
||||
// config walk, e.g. providers.
|
||||
if i.Operation == walkInput {
|
||||
result[n] = ast.Variable{
|
||||
Value: config.UnknownVariableValue,
|
||||
Type: ast.TypeString,
|
||||
}
|
||||
result[n] = unknownVariable()
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
@ -306,7 +306,7 @@ func TestInterpolater_resourceVariableMissingDuringInput(t *testing.T) {
|
|||
|
||||
testInterpolate(t, i, scope, "aws_instance.web.foo", ast.Variable{
|
||||
Value: config.UnknownVariableValue,
|
||||
Type: ast.TypeString,
|
||||
Type: ast.TypeUnknown,
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -360,7 +360,7 @@ func TestInterpolater_resourceVariableMulti(t *testing.T) {
|
|||
|
||||
testInterpolate(t, i, scope, "aws_instance.web.*.foo", ast.Variable{
|
||||
Value: config.UnknownVariableValue,
|
||||
Type: ast.TypeString,
|
||||
Type: ast.TypeUnknown,
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -565,7 +565,7 @@ func TestInterpolator_resourceMultiAttributesComputed(t *testing.T) {
|
|||
|
||||
testInterpolate(t, i, scope, "aws_route53_zone.yada.name_servers", ast.Variable{
|
||||
Value: config.UnknownVariableValue,
|
||||
Type: ast.TypeString,
|
||||
Type: ast.TypeUnknown,
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -9,6 +9,7 @@ import (
|
|||
|
||||
"github.com/hashicorp/terraform/config"
|
||||
"github.com/mitchellh/copystructure"
|
||||
"github.com/mitchellh/reflectwalk"
|
||||
)
|
||||
|
||||
// ResourceProvisionerConfig is used to pair a provisioner
|
||||
|
@ -186,16 +187,17 @@ func (c *ResourceConfig) CheckSet(keys []string) []error {
|
|||
// Get looks up a configuration value by key and returns the value.
|
||||
//
|
||||
// The second return value is true if the get was successful. Get will
|
||||
// not succeed if the value is being computed.
|
||||
// return the raw value if the key is computed, so you should pair this
|
||||
// with IsComputed.
|
||||
func (c *ResourceConfig) Get(k string) (interface{}, bool) {
|
||||
// First try to get it from c.Config since that has interpolated values
|
||||
result, ok := c.get(k, c.Config)
|
||||
if ok {
|
||||
return result, ok
|
||||
// We aim to get a value from the configuration. If it is computed,
|
||||
// then we return the pure raw value.
|
||||
source := c.Config
|
||||
if c.IsComputed(k) {
|
||||
source = c.Raw
|
||||
}
|
||||
|
||||
// Otherwise, just get it from the raw config
|
||||
return c.get(k, c.Raw)
|
||||
return c.get(k, source)
|
||||
}
|
||||
|
||||
// GetRaw looks up a configuration value by key and returns the value,
|
||||
|
@ -209,9 +211,25 @@ func (c *ResourceConfig) GetRaw(k string) (interface{}, bool) {
|
|||
|
||||
// IsComputed returns whether the given key is computed or not.
|
||||
func (c *ResourceConfig) IsComputed(k string) bool {
|
||||
_, ok := c.get(k, c.Config)
|
||||
_, okRaw := c.get(k, c.Raw)
|
||||
return !ok && okRaw
|
||||
// The next thing we do is check the config if we get a computed
|
||||
// value out of it.
|
||||
v, ok := c.get(k, c.Config)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
// If value is nil, then it isn't computed
|
||||
if v == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// Test if the value contains an unknown value
|
||||
var w unknownCheckWalker
|
||||
if err := reflectwalk.Walk(v, &w); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return w.Unknown
|
||||
}
|
||||
|
||||
// IsSet checks if the key in the configuration is set. A key is set if
|
||||
|
@ -225,10 +243,8 @@ func (c *ResourceConfig) IsSet(k string) bool {
|
|||
return false
|
||||
}
|
||||
|
||||
for _, ck := range c.ComputedKeys {
|
||||
if ck == k {
|
||||
return true
|
||||
}
|
||||
if c.IsComputed(k) {
|
||||
return true
|
||||
}
|
||||
|
||||
if _, ok := c.Get(k); ok {
|
||||
|
@ -264,15 +280,26 @@ func (c *ResourceConfig) get(
|
|||
if !v.IsValid() {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
return v.Interface(), true
|
||||
}
|
||||
|
||||
return nil, false
|
||||
}
|
||||
|
||||
current = v.Interface()
|
||||
case reflect.Slice:
|
||||
previous = current
|
||||
|
||||
if part == "#" {
|
||||
// If any value in a list is computed, this whole thing
|
||||
// is computed and we can't read any part of it.
|
||||
for i := 0; i < cv.Len(); i++ {
|
||||
if v := cv.Index(i).Interface(); v == unknownValue() {
|
||||
return v, true
|
||||
}
|
||||
}
|
||||
|
||||
current = cv.Len()
|
||||
} else {
|
||||
i, err := strconv.ParseInt(part, 0, 0)
|
||||
|
@ -291,6 +318,7 @@ func (c *ResourceConfig) get(
|
|||
if prevMap, ok := previous.(map[string]interface{}); ok {
|
||||
return prevMap[actualKey], true
|
||||
}
|
||||
|
||||
return nil, false
|
||||
default:
|
||||
panic(fmt.Sprintf("Unknown kind: %s", cv.Kind()))
|
||||
|
@ -316,3 +344,16 @@ func (c *ResourceConfig) interpolateForce() {
|
|||
c.Raw = c.raw.RawMap()
|
||||
c.Config = c.raw.Config()
|
||||
}
|
||||
|
||||
// unknownCheckWalker
|
||||
type unknownCheckWalker struct {
|
||||
Unknown bool
|
||||
}
|
||||
|
||||
func (w *unknownCheckWalker) Primitive(v reflect.Value) error {
|
||||
if v.Interface() == unknownValue() {
|
||||
w.Unknown = true
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -1,189 +0,0 @@
|
|||
package terraform
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestResourceConfig_CheckSet(t *testing.T) {
|
||||
cases := []struct {
|
||||
Raw map[string]interface{}
|
||||
Computed []string
|
||||
Input []string
|
||||
Errs bool
|
||||
}{
|
||||
{
|
||||
map[string]interface{}{
|
||||
"foo": "bar",
|
||||
},
|
||||
nil,
|
||||
[]string{"foo"},
|
||||
false,
|
||||
},
|
||||
{
|
||||
map[string]interface{}{
|
||||
"foo": "bar",
|
||||
},
|
||||
nil,
|
||||
[]string{"foo", "bar"},
|
||||
true,
|
||||
},
|
||||
{
|
||||
map[string]interface{}{
|
||||
"foo": "bar",
|
||||
},
|
||||
[]string{"bar"},
|
||||
[]string{"foo", "bar"},
|
||||
false,
|
||||
},
|
||||
}
|
||||
|
||||
for i, tc := range cases {
|
||||
rc := &ResourceConfig{
|
||||
ComputedKeys: tc.Computed,
|
||||
Raw: tc.Raw,
|
||||
}
|
||||
|
||||
errs := rc.CheckSet(tc.Input)
|
||||
if tc.Errs != (len(errs) > 0) {
|
||||
t.Fatalf("bad: %d", i)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestResourceConfig_Get(t *testing.T) {
|
||||
cases := []struct {
|
||||
Raw map[string]interface{}
|
||||
Computed []string
|
||||
Input string
|
||||
Output interface{}
|
||||
OutputOk bool
|
||||
}{
|
||||
{
|
||||
map[string]interface{}{
|
||||
"foo": "bar",
|
||||
},
|
||||
nil,
|
||||
"foo",
|
||||
"bar",
|
||||
true,
|
||||
},
|
||||
{
|
||||
map[string]interface{}{},
|
||||
nil,
|
||||
"foo",
|
||||
nil,
|
||||
false,
|
||||
},
|
||||
{
|
||||
map[string]interface{}{
|
||||
"foo": map[interface{}]interface{}{
|
||||
"bar": "baz",
|
||||
},
|
||||
},
|
||||
nil,
|
||||
"foo.bar",
|
||||
"baz",
|
||||
true,
|
||||
},
|
||||
{
|
||||
map[string]interface{}{
|
||||
"foo": []string{
|
||||
"one",
|
||||
"two",
|
||||
},
|
||||
},
|
||||
nil,
|
||||
"foo.1",
|
||||
"two",
|
||||
true,
|
||||
},
|
||||
}
|
||||
|
||||
for i, tc := range cases {
|
||||
rc := &ResourceConfig{
|
||||
ComputedKeys: tc.Computed,
|
||||
Raw: tc.Raw,
|
||||
}
|
||||
|
||||
actual, ok := rc.Get(tc.Input)
|
||||
if tc.OutputOk != ok {
|
||||
t.Fatalf("bad ok: %d", i)
|
||||
}
|
||||
if !reflect.DeepEqual(tc.Output, actual) {
|
||||
t.Fatalf("bad %d: %#v", i, actual)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestResourceConfig_IsSet(t *testing.T) {
|
||||
cases := []struct {
|
||||
Raw map[string]interface{}
|
||||
Computed []string
|
||||
Input string
|
||||
Output bool
|
||||
}{
|
||||
{
|
||||
map[string]interface{}{
|
||||
"foo": "bar",
|
||||
},
|
||||
nil,
|
||||
"foo",
|
||||
true,
|
||||
},
|
||||
{
|
||||
map[string]interface{}{},
|
||||
nil,
|
||||
"foo",
|
||||
false,
|
||||
},
|
||||
{
|
||||
map[string]interface{}{},
|
||||
[]string{"foo"},
|
||||
"foo",
|
||||
true,
|
||||
},
|
||||
{
|
||||
map[string]interface{}{
|
||||
"foo": map[interface{}]interface{}{
|
||||
"bar": "baz",
|
||||
},
|
||||
},
|
||||
nil,
|
||||
"foo.bar",
|
||||
true,
|
||||
},
|
||||
}
|
||||
|
||||
for i, tc := range cases {
|
||||
rc := &ResourceConfig{
|
||||
ComputedKeys: tc.Computed,
|
||||
Raw: tc.Raw,
|
||||
}
|
||||
|
||||
actual := rc.IsSet(tc.Input)
|
||||
if actual != tc.Output {
|
||||
t.Fatalf("fail case: %d", i)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestResourceConfig_IsSet_nil(t *testing.T) {
|
||||
var rc *ResourceConfig
|
||||
|
||||
if rc.IsSet("foo") {
|
||||
t.Fatal("bad")
|
||||
}
|
||||
}
|
||||
|
||||
func TestResourceProviderFactoryFixed(t *testing.T) {
|
||||
p := new(MockResourceProvider)
|
||||
var f ResourceProviderFactory = ResourceProviderFactoryFixed(p)
|
||||
actual, err := f()
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
if actual != p {
|
||||
t.Fatal("should be identical")
|
||||
}
|
||||
}
|
|
@ -5,8 +5,10 @@ import (
|
|||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/hil"
|
||||
"github.com/hashicorp/hil/ast"
|
||||
"github.com/hashicorp/terraform/config"
|
||||
"github.com/mitchellh/reflectwalk"
|
||||
)
|
||||
|
||||
func TestInstanceInfo(t *testing.T) {
|
||||
|
@ -47,7 +49,7 @@ func TestInstanceInfo(t *testing.T) {
|
|||
func TestResourceConfigGet(t *testing.T) {
|
||||
cases := []struct {
|
||||
Config map[string]interface{}
|
||||
Vars map[string]string
|
||||
Vars map[string]interface{}
|
||||
Key string
|
||||
Value interface{}
|
||||
}{
|
||||
|
@ -57,6 +59,14 @@ func TestResourceConfigGet(t *testing.T) {
|
|||
Value: nil,
|
||||
},
|
||||
|
||||
{
|
||||
Config: map[string]interface{}{
|
||||
"foo": "bar",
|
||||
},
|
||||
Key: "foo",
|
||||
Value: "bar",
|
||||
},
|
||||
|
||||
{
|
||||
Config: map[string]interface{}{
|
||||
"foo": "${var.foo}",
|
||||
|
@ -69,9 +79,9 @@ func TestResourceConfigGet(t *testing.T) {
|
|||
Config: map[string]interface{}{
|
||||
"foo": "${var.foo}",
|
||||
},
|
||||
Vars: map[string]string{"foo": "bar"},
|
||||
Vars: map[string]interface{}{"foo": unknownValue()},
|
||||
Key: "foo",
|
||||
Value: "bar",
|
||||
Value: "${var.foo}",
|
||||
},
|
||||
|
||||
{
|
||||
|
@ -198,7 +208,12 @@ func TestResourceConfigGet(t *testing.T) {
|
|||
if tc.Vars != nil {
|
||||
vs := make(map[string]ast.Variable)
|
||||
for k, v := range tc.Vars {
|
||||
vs["var."+k] = ast.Variable{Value: v, Type: ast.TypeString}
|
||||
hilVar, err := hil.InterfaceToVariable(v)
|
||||
if err != nil {
|
||||
t.Fatalf("%#v to var: %s", v, err)
|
||||
}
|
||||
|
||||
vs["var."+k] = hilVar
|
||||
}
|
||||
|
||||
if err := rawC.Interpolate(vs); err != nil {
|
||||
|
@ -239,6 +254,240 @@ func TestResourceConfigGet(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestResourceConfigIsComputed(t *testing.T) {
|
||||
cases := []struct {
|
||||
Name string
|
||||
Config map[string]interface{}
|
||||
Vars map[string]interface{}
|
||||
Key string
|
||||
Result bool
|
||||
}{
|
||||
{
|
||||
Name: "basic value",
|
||||
Config: map[string]interface{}{
|
||||
"foo": "${var.foo}",
|
||||
},
|
||||
Vars: map[string]interface{}{
|
||||
"foo": unknownValue(),
|
||||
},
|
||||
Key: "foo",
|
||||
Result: true,
|
||||
},
|
||||
|
||||
{
|
||||
Name: "set with a computed element",
|
||||
Config: map[string]interface{}{
|
||||
"foo": "${var.foo}",
|
||||
},
|
||||
Vars: map[string]interface{}{
|
||||
"foo": []string{
|
||||
"a",
|
||||
unknownValue(),
|
||||
},
|
||||
},
|
||||
Key: "foo",
|
||||
Result: true,
|
||||
},
|
||||
|
||||
{
|
||||
Name: "set with no computed elements",
|
||||
Config: map[string]interface{}{
|
||||
"foo": "${var.foo}",
|
||||
},
|
||||
Vars: map[string]interface{}{
|
||||
"foo": []string{
|
||||
"a",
|
||||
"b",
|
||||
},
|
||||
},
|
||||
Key: "foo",
|
||||
Result: false,
|
||||
},
|
||||
|
||||
/*
|
||||
{
|
||||
Name: "set count with computed elements",
|
||||
Config: map[string]interface{}{
|
||||
"foo": "${var.foo}",
|
||||
},
|
||||
Vars: map[string]interface{}{
|
||||
"foo": []string{
|
||||
"a",
|
||||
unknownValue(),
|
||||
},
|
||||
},
|
||||
Key: "foo.#",
|
||||
Result: true,
|
||||
},
|
||||
*/
|
||||
|
||||
{
|
||||
Name: "set count with computed elements",
|
||||
Config: map[string]interface{}{
|
||||
"foo": []interface{}{"${var.foo}"},
|
||||
},
|
||||
Vars: map[string]interface{}{
|
||||
"foo": []string{
|
||||
"a",
|
||||
unknownValue(),
|
||||
},
|
||||
},
|
||||
Key: "foo.#",
|
||||
Result: true,
|
||||
},
|
||||
|
||||
{
|
||||
Name: "nested set with computed elements",
|
||||
Config: map[string]interface{}{
|
||||
"route": []map[string]interface{}{
|
||||
map[string]interface{}{
|
||||
"index": "1",
|
||||
"gateway": []interface{}{"${var.foo}"},
|
||||
},
|
||||
},
|
||||
},
|
||||
Vars: map[string]interface{}{
|
||||
"foo": unknownValue(),
|
||||
},
|
||||
Key: "route.0.gateway",
|
||||
Result: true,
|
||||
},
|
||||
}
|
||||
|
||||
for i, tc := range cases {
|
||||
t.Run(fmt.Sprintf("%d-%s", i, tc.Name), func(t *testing.T) {
|
||||
var rawC *config.RawConfig
|
||||
if tc.Config != nil {
|
||||
var err error
|
||||
rawC, err = config.NewRawConfig(tc.Config)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
if tc.Vars != nil {
|
||||
vs := make(map[string]ast.Variable)
|
||||
for k, v := range tc.Vars {
|
||||
hilVar, err := hil.InterfaceToVariable(v)
|
||||
if err != nil {
|
||||
t.Fatalf("%#v to var: %s", v, err)
|
||||
}
|
||||
|
||||
vs["var."+k] = hilVar
|
||||
}
|
||||
|
||||
if err := rawC.Interpolate(vs); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
rc := NewResourceConfig(rawC)
|
||||
rc.interpolateForce()
|
||||
|
||||
t.Logf("Config: %#v", rc)
|
||||
|
||||
actual := rc.IsComputed(tc.Key)
|
||||
if actual != tc.Result {
|
||||
t.Fatalf("bad: %#v", actual)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestResourceConfigCheckSet(t *testing.T) {
|
||||
cases := []struct {
|
||||
Name string
|
||||
Config map[string]interface{}
|
||||
Vars map[string]interface{}
|
||||
Input []string
|
||||
Errs bool
|
||||
}{
|
||||
{
|
||||
Name: "computed basic",
|
||||
Config: map[string]interface{}{
|
||||
"foo": "${var.foo}",
|
||||
},
|
||||
Vars: map[string]interface{}{
|
||||
"foo": unknownValue(),
|
||||
},
|
||||
Input: []string{"foo"},
|
||||
Errs: false,
|
||||
},
|
||||
|
||||
{
|
||||
Name: "basic",
|
||||
Config: map[string]interface{}{
|
||||
"foo": "bar",
|
||||
},
|
||||
Vars: nil,
|
||||
Input: []string{"foo"},
|
||||
Errs: false,
|
||||
},
|
||||
|
||||
{
|
||||
Name: "basic with not set",
|
||||
Config: map[string]interface{}{
|
||||
"foo": "bar",
|
||||
},
|
||||
Vars: nil,
|
||||
Input: []string{"foo", "bar"},
|
||||
Errs: true,
|
||||
},
|
||||
|
||||
{
|
||||
Name: "basic with one computed",
|
||||
Config: map[string]interface{}{
|
||||
"foo": "bar",
|
||||
"bar": "${var.foo}",
|
||||
},
|
||||
Vars: map[string]interface{}{
|
||||
"foo": unknownValue(),
|
||||
},
|
||||
Input: []string{"foo", "bar"},
|
||||
Errs: false,
|
||||
},
|
||||
}
|
||||
|
||||
for i, tc := range cases {
|
||||
t.Run(fmt.Sprintf("%d-%s", i, tc.Name), func(t *testing.T) {
|
||||
var rawC *config.RawConfig
|
||||
if tc.Config != nil {
|
||||
var err error
|
||||
rawC, err = config.NewRawConfig(tc.Config)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
if tc.Vars != nil {
|
||||
vs := make(map[string]ast.Variable)
|
||||
for k, v := range tc.Vars {
|
||||
hilVar, err := hil.InterfaceToVariable(v)
|
||||
if err != nil {
|
||||
t.Fatalf("%#v to var: %s", v, err)
|
||||
}
|
||||
|
||||
vs["var."+k] = hilVar
|
||||
}
|
||||
|
||||
if err := rawC.Interpolate(vs); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
rc := NewResourceConfig(rawC)
|
||||
rc.interpolateForce()
|
||||
|
||||
t.Logf("Config: %#v", rc)
|
||||
|
||||
errs := rc.CheckSet(tc.Input)
|
||||
if tc.Errs != (len(errs) > 0) {
|
||||
t.Fatalf("bad: %#v", errs)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestResourceConfigDeepCopy_nil(t *testing.T) {
|
||||
var nilRc *ResourceConfig
|
||||
actual := nilRc.DeepCopy()
|
||||
|
@ -282,6 +531,54 @@ func TestResourceConfigEqual_computedKeyOrder(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestUnknownCheckWalker(t *testing.T) {
|
||||
cases := []struct {
|
||||
Name string
|
||||
Input interface{}
|
||||
Result bool
|
||||
}{
|
||||
{
|
||||
"primitive",
|
||||
42,
|
||||
false,
|
||||
},
|
||||
|
||||
{
|
||||
"primitive computed",
|
||||
unknownValue(),
|
||||
true,
|
||||
},
|
||||
|
||||
{
|
||||
"list",
|
||||
[]interface{}{"foo", unknownValue()},
|
||||
true,
|
||||
},
|
||||
|
||||
{
|
||||
"nested list",
|
||||
[]interface{}{
|
||||
"foo",
|
||||
[]interface{}{unknownValue()},
|
||||
},
|
||||
true,
|
||||
},
|
||||
}
|
||||
|
||||
for i, tc := range cases {
|
||||
t.Run(fmt.Sprintf("%d-%s", i, tc.Name), func(t *testing.T) {
|
||||
var w unknownCheckWalker
|
||||
if err := reflectwalk.Walk(tc.Input, &w); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if w.Unknown != tc.Result {
|
||||
t.Fatalf("bad: %v", w.Unknown)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func testResourceConfig(
|
||||
t *testing.T, c map[string]interface{}) *ResourceConfig {
|
||||
raw, err := config.NewRawConfig(c)
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
module "mod" {
|
||||
source = "./mod"
|
||||
}
|
||||
|
||||
resource "template_file" "root_template" {
|
||||
template = "ext: ${module.mod.base_config["base_template"]}"
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
resource "template_file" "example" {
|
||||
template = "template text"
|
||||
}
|
||||
|
||||
output "base_config" {
|
||||
value = {
|
||||
base_template = "${template_file.example.rendered}"
|
||||
|
||||
# without this we fail with no entries
|
||||
extra = "value"
|
||||
}
|
||||
}
|
|
@ -54,6 +54,13 @@ const (
|
|||
TypeFloat
|
||||
TypeList
|
||||
TypeMap
|
||||
|
||||
// This is a special type used by Terraform to mark "unknown" values.
|
||||
// It is impossible for this type to be introduced into your HIL programs
|
||||
// unless you explicitly set a variable to this value. In that case,
|
||||
// any operation including the variable will return "TypeUnknown" as the
|
||||
// type.
|
||||
TypeUnknown
|
||||
)
|
||||
|
||||
func (t Type) Printable() string {
|
||||
|
@ -72,6 +79,8 @@ func (t Type) Printable() string {
|
|||
return "type list"
|
||||
case TypeMap:
|
||||
return "type map"
|
||||
case TypeUnknown:
|
||||
return "type unknown"
|
||||
default:
|
||||
return "unknown type"
|
||||
}
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
package ast
|
||||
|
||||
// IsUnknown reports whether a variable is unknown or contains any value
|
||||
// that is unknown. This will recurse into lists and maps and so on.
|
||||
func IsUnknown(v Variable) bool {
|
||||
// If it is unknown itself, return true
|
||||
if v.Type == TypeUnknown {
|
||||
return true
|
||||
}
|
||||
|
||||
// If it is a container type, check the values
|
||||
switch v.Type {
|
||||
case TypeList:
|
||||
for _, el := range v.Value.([]Variable) {
|
||||
if IsUnknown(el) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
case TypeMap:
|
||||
for _, el := range v.Value.(map[string]Variable) {
|
||||
if IsUnknown(el) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
default:
|
||||
}
|
||||
|
||||
// Not a container type or survive the above checks
|
||||
return false
|
||||
}
|
|
@ -5,6 +5,11 @@ import "fmt"
|
|||
func VariableListElementTypesAreHomogenous(variableName string, list []Variable) (Type, error) {
|
||||
listTypes := make(map[Type]struct{})
|
||||
for _, v := range list {
|
||||
// Allow unknown
|
||||
if v.Type == TypeUnknown {
|
||||
continue
|
||||
}
|
||||
|
||||
if _, ok := listTypes[v.Type]; ok {
|
||||
continue
|
||||
}
|
||||
|
@ -25,9 +30,15 @@ func VariableListElementTypesAreHomogenous(variableName string, list []Variable)
|
|||
func VariableMapValueTypesAreHomogenous(variableName string, vmap map[string]Variable) (Type, error) {
|
||||
valueTypes := make(map[Type]struct{})
|
||||
for _, v := range vmap {
|
||||
// Allow unknown
|
||||
if v.Type == TypeUnknown {
|
||||
continue
|
||||
}
|
||||
|
||||
if _, ok := valueTypes[v.Type]; ok {
|
||||
continue
|
||||
}
|
||||
|
||||
valueTypes[v.Type] = struct{}{}
|
||||
}
|
||||
|
||||
|
|
|
@ -44,6 +44,12 @@ func (v *TypeCheck) Visit(root ast.Node) error {
|
|||
defer v.lock.Unlock()
|
||||
defer v.reset()
|
||||
root.Accept(v.visit)
|
||||
|
||||
// If the resulting type is unknown, then just let the whole thing go.
|
||||
if v.err == errExitUnknown {
|
||||
v.err = nil
|
||||
}
|
||||
|
||||
return v.err
|
||||
}
|
||||
|
||||
|
@ -89,6 +95,10 @@ func (v *TypeCheck) visit(raw ast.Node) ast.Node {
|
|||
pos.Column, pos.Line, err)
|
||||
}
|
||||
|
||||
if v.StackPeek() == ast.TypeUnknown {
|
||||
v.err = errExitUnknown
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
|
@ -242,15 +252,14 @@ func (tc *typeCheckOutput) TypeCheck(v *TypeCheck) (ast.Node, error) {
|
|||
}
|
||||
|
||||
// If there is only one argument and it is a list, we evaluate to a list
|
||||
if len(types) == 1 && types[0] == ast.TypeList {
|
||||
v.StackPush(ast.TypeList)
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// If there is only one argument and it is a map, we evaluate to a map
|
||||
if len(types) == 1 && types[0] == ast.TypeMap {
|
||||
v.StackPush(ast.TypeMap)
|
||||
return n, nil
|
||||
if len(types) == 1 {
|
||||
switch t := types[0]; t {
|
||||
case ast.TypeList:
|
||||
fallthrough
|
||||
case ast.TypeMap:
|
||||
v.StackPush(t)
|
||||
return n, nil
|
||||
}
|
||||
}
|
||||
|
||||
// Otherwise, all concat args must be strings, so validate that
|
||||
|
@ -294,6 +303,13 @@ func (tc *typeCheckVariableAccess) TypeCheck(v *TypeCheck) (ast.Node, error) {
|
|||
"unknown variable accessed: %s", tc.n.Name)
|
||||
}
|
||||
|
||||
// Check if the variable contains any unknown types. If so, then
|
||||
// mark it as unknown.
|
||||
if ast.IsUnknown(variable) {
|
||||
v.StackPush(ast.TypeUnknown)
|
||||
return tc.n, nil
|
||||
}
|
||||
|
||||
// Add the type to the stack
|
||||
v.StackPush(variable.Type)
|
||||
|
||||
|
@ -399,3 +415,11 @@ func (v *TypeCheck) StackPop() ast.Type {
|
|||
x, v.Stack = v.Stack[len(v.Stack)-1], v.Stack[:len(v.Stack)-1]
|
||||
return x
|
||||
}
|
||||
|
||||
func (v *TypeCheck) StackPeek() ast.Type {
|
||||
if len(v.Stack) == 0 {
|
||||
return ast.TypeInvalid
|
||||
}
|
||||
|
||||
return v.Stack[len(v.Stack)-1]
|
||||
}
|
||||
|
|
|
@ -8,6 +8,11 @@ import (
|
|||
"github.com/mitchellh/mapstructure"
|
||||
)
|
||||
|
||||
// UnknownValue is a sentinel value that can be used to denote
|
||||
// that a value of a variable (or map element, list element, etc.)
|
||||
// is unknown. This will always have the type ast.TypeUnknown.
|
||||
const UnknownValue = "74D93920-ED26-11E3-AC10-0800200C9A66"
|
||||
|
||||
var hilMapstructureDecodeHookSlice []interface{}
|
||||
var hilMapstructureDecodeHookStringSlice []string
|
||||
var hilMapstructureDecodeHookMap map[string]interface{}
|
||||
|
@ -48,6 +53,12 @@ func InterfaceToVariable(input interface{}) (ast.Variable, error) {
|
|||
|
||||
var stringVal string
|
||||
if err := hilMapstructureWeakDecode(input, &stringVal); err == nil {
|
||||
// Special case the unknown value to turn into "unknown"
|
||||
if stringVal == UnknownValue {
|
||||
return ast.Variable{Type: ast.TypeUnknown}, nil
|
||||
}
|
||||
|
||||
// Otherwise return the string value
|
||||
return ast.Variable{
|
||||
Type: ast.TypeString,
|
||||
Value: stringVal,
|
||||
|
|
|
@ -2,6 +2,7 @@ package hil
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
|
@ -42,6 +43,10 @@ type EvaluationResult struct {
|
|||
// The error is described out of band in the accompanying error return value.
|
||||
var InvalidResult = EvaluationResult{Type: TypeInvalid, Value: nil}
|
||||
|
||||
// errExitUnknown is an internal error that when returned means the result
|
||||
// is an unknown value. We use this for early exit.
|
||||
var errExitUnknown = errors.New("unknown value")
|
||||
|
||||
func Eval(root ast.Node, config *EvalConfig) (EvaluationResult, error) {
|
||||
output, outputType, err := internalEval(root, config)
|
||||
if err != nil {
|
||||
|
@ -72,6 +77,11 @@ func Eval(root ast.Node, config *EvalConfig) (EvaluationResult, error) {
|
|||
Type: TypeString,
|
||||
Value: output,
|
||||
}, nil
|
||||
case ast.TypeUnknown:
|
||||
return EvaluationResult{
|
||||
Type: TypeUnknown,
|
||||
Value: UnknownValue,
|
||||
}, nil
|
||||
default:
|
||||
return InvalidResult, fmt.Errorf("unknown type %s as interpolation output", outputType)
|
||||
}
|
||||
|
@ -154,6 +164,12 @@ func (v *evalVisitor) Visit(root ast.Node) (interface{}, ast.Type, error) {
|
|||
result = new(ast.LiteralNode)
|
||||
}
|
||||
resultErr := v.err
|
||||
if resultErr == errExitUnknown {
|
||||
// This means the return value is unknown and we used the error
|
||||
// as an early exit mechanism. Reset since the value on the stack
|
||||
// should be the unknown value.
|
||||
resultErr = nil
|
||||
}
|
||||
|
||||
// Clear everything else so we aren't just dangling
|
||||
v.Stack.Reset()
|
||||
|
@ -188,6 +204,13 @@ func (v *evalVisitor) visit(raw ast.Node) ast.Node {
|
|||
Value: out,
|
||||
Typex: outType,
|
||||
})
|
||||
|
||||
if outType == ast.TypeUnknown {
|
||||
// Halt immediately
|
||||
v.err = errExitUnknown
|
||||
return raw
|
||||
}
|
||||
|
||||
return raw
|
||||
}
|
||||
|
||||
|
@ -330,11 +353,15 @@ func (v *evalOutput) Eval(s ast.Scope, stack *ast.Stack) (interface{}, ast.Type,
|
|||
}
|
||||
|
||||
// Special case the single list and map
|
||||
if len(nodes) == 1 && nodes[0].Typex == ast.TypeList {
|
||||
return nodes[0].Value, ast.TypeList, nil
|
||||
}
|
||||
if len(nodes) == 1 && nodes[0].Typex == ast.TypeMap {
|
||||
return nodes[0].Value, ast.TypeMap, nil
|
||||
if len(nodes) == 1 {
|
||||
switch t := nodes[0].Typex; t {
|
||||
case ast.TypeList:
|
||||
fallthrough
|
||||
case ast.TypeMap:
|
||||
fallthrough
|
||||
case ast.TypeUnknown:
|
||||
return nodes[0].Value, t, nil
|
||||
}
|
||||
}
|
||||
|
||||
// Otherwise concatenate the strings
|
||||
|
@ -362,5 +389,11 @@ func (v *evalVariableAccess) Eval(scope ast.Scope, _ *ast.Stack) (interface{}, a
|
|||
"unknown variable accessed: %s", v.Name)
|
||||
}
|
||||
|
||||
// Check if the variable contains any unknown types. If so, then
|
||||
// mark it as unknown and return that type.
|
||||
if ast.IsUnknown(variable) {
|
||||
return nil, ast.TypeUnknown, nil
|
||||
}
|
||||
|
||||
return variable.Value, variable.Type, nil
|
||||
}
|
||||
|
|
|
@ -11,4 +11,5 @@ const (
|
|||
TypeString EvalType = 1 << iota
|
||||
TypeList
|
||||
TypeMap
|
||||
TypeUnknown
|
||||
)
|
||||
|
|
|
@ -1386,16 +1386,16 @@
|
|||
"revisionTime": "2016-11-09T00:00:27Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "RYz/9y1RMZfg+oMgEyJIWiSl1dU=",
|
||||
"checksumSHA1": "mZhRldYjh9MAXzdi3zihMX0A/JU=",
|
||||
"path": "github.com/hashicorp/hil",
|
||||
"revision": "3e00ff29065d64c0f8e9ef7efed82686bbda81ca",
|
||||
"revisionTime": "2016-10-14T17:08:44Z"
|
||||
"revision": "ce4ab742a9dd2bb6e55050337333b2c56666e5a0",
|
||||
"revisionTime": "2016-10-27T15:25:34Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "WYIQ+nJPa191qpQIUsauF4wXYSw=",
|
||||
"checksumSHA1": "FFroNUb6Nn6xUQJMsVDTb4Cqzo4=",
|
||||
"path": "github.com/hashicorp/hil/ast",
|
||||
"revision": "3e00ff29065d64c0f8e9ef7efed82686bbda81ca",
|
||||
"revisionTime": "2016-10-14T17:08:44Z"
|
||||
"revision": "ce4ab742a9dd2bb6e55050337333b2c56666e5a0",
|
||||
"revisionTime": "2016-10-27T15:25:34Z"
|
||||
},
|
||||
{
|
||||
"path": "github.com/hashicorp/logutils",
|
||||
|
|
Loading…
Reference in New Issue