update github.com/hashicorp/hil
This commit is contained in:
parent
5ed1b5fc89
commit
da09dcfc79
|
@ -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