mirror of
https://github.com/opentofu/opentofu.git
synced 2025-02-25 18:45:20 -06:00
Merge pull request #797 from hashicorp/f-stronger-types
Force variables to be typed (internally)
This commit is contained in:
commit
d3c0543bf3
@ -81,7 +81,7 @@ func interpolationFuncJoin() lang.Function {
|
||||
|
||||
// interpolationFuncLookup implements the "lookup" function that allows
|
||||
// dynamic lookups of map types within a Terraform configuration.
|
||||
func interpolationFuncLookup(vs map[string]string) lang.Function {
|
||||
func interpolationFuncLookup(vs map[string]lang.Variable) lang.Function {
|
||||
return lang.Function{
|
||||
ArgTypes: []ast.Type{ast.TypeString, ast.TypeString},
|
||||
ReturnType: ast.TypeString,
|
||||
@ -93,8 +93,13 @@ func interpolationFuncLookup(vs map[string]string) lang.Function {
|
||||
"lookup in '%s' failed to find '%s'",
|
||||
args[0].(string), args[1].(string))
|
||||
}
|
||||
if v.Type != ast.TypeString {
|
||||
return "", fmt.Errorf(
|
||||
"lookup in '%s' for '%s' has bad type %s",
|
||||
args[0].(string), args[1].(string), v.Type)
|
||||
}
|
||||
|
||||
return v, nil
|
||||
return v.Value.(string), nil
|
||||
},
|
||||
}
|
||||
}
|
||||
|
@ -8,6 +8,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/terraform/config/lang"
|
||||
"github.com/hashicorp/terraform/config/lang/ast"
|
||||
)
|
||||
|
||||
func TestInterpolateFuncConcat(t *testing.T) {
|
||||
@ -108,7 +109,12 @@ func TestInterpolateFuncJoin(t *testing.T) {
|
||||
|
||||
func TestInterpolateFuncLookup(t *testing.T) {
|
||||
testFunction(t, testFunctionConfig{
|
||||
Vars: map[string]string{"var.foo.bar": "baz"},
|
||||
Vars: map[string]lang.Variable{
|
||||
"var.foo.bar": lang.Variable{
|
||||
Value: "baz",
|
||||
Type: ast.TypeString,
|
||||
},
|
||||
},
|
||||
Cases: []testFunctionCase{
|
||||
{
|
||||
`${lookup("foo", "bar")}`,
|
||||
@ -170,7 +176,7 @@ func TestInterpolateFuncElement(t *testing.T) {
|
||||
|
||||
type testFunctionConfig struct {
|
||||
Cases []testFunctionCase
|
||||
Vars map[string]string
|
||||
Vars map[string]lang.Variable
|
||||
}
|
||||
|
||||
type testFunctionCase struct {
|
||||
|
@ -1,6 +1,7 @@
|
||||
package ast
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
@ -26,3 +27,12 @@ func (n *Concat) Pos() Pos {
|
||||
func (n *Concat) GoString() string {
|
||||
return fmt.Sprintf("*%#v", *n)
|
||||
}
|
||||
|
||||
func (n *Concat) String() string {
|
||||
var b bytes.Buffer
|
||||
for _, expr := range n.Exprs {
|
||||
b.WriteString(fmt.Sprintf("%s", expr))
|
||||
}
|
||||
|
||||
return b.String()
|
||||
}
|
||||
|
42
config/lang/builtins.go
Normal file
42
config/lang/builtins.go
Normal file
@ -0,0 +1,42 @@
|
||||
package lang
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"github.com/hashicorp/terraform/config/lang/ast"
|
||||
)
|
||||
|
||||
// NOTE: All builtins are tested in engine_test.go
|
||||
|
||||
func registerBuiltins(scope *Scope) {
|
||||
if scope.FuncMap == nil {
|
||||
scope.FuncMap = make(map[string]Function)
|
||||
}
|
||||
scope.FuncMap["__builtin_IntToString"] = builtinIntToString()
|
||||
scope.FuncMap["__builtin_StringToInt"] = builtinStringToInt()
|
||||
}
|
||||
|
||||
func builtinIntToString() Function {
|
||||
return Function{
|
||||
ArgTypes: []ast.Type{ast.TypeInt},
|
||||
ReturnType: ast.TypeString,
|
||||
Callback: func(args []interface{}) (interface{}, error) {
|
||||
return strconv.FormatInt(int64(args[0].(int)), 10), nil
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func builtinStringToInt() Function {
|
||||
return Function{
|
||||
ArgTypes: []ast.Type{ast.TypeInt},
|
||||
ReturnType: ast.TypeString,
|
||||
Callback: func(args []interface{}) (interface{}, error) {
|
||||
v, err := strconv.ParseInt(args[0].(string), 0, 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return int(v), nil
|
||||
},
|
||||
}
|
||||
}
|
@ -9,9 +9,21 @@ import (
|
||||
|
||||
// TypeCheck implements ast.Visitor for type checking an AST tree.
|
||||
// It requires some configuration to look up the type of nodes.
|
||||
//
|
||||
// It also optionally will not type error and will insert an implicit
|
||||
// type conversions for specific types if specified by the Implicit
|
||||
// field. Note that this is kind of organizationally weird to put into
|
||||
// this structure but we'd rather do that than duplicate the type checking
|
||||
// logic multiple times.
|
||||
type TypeCheck struct {
|
||||
Scope *Scope
|
||||
|
||||
// Implicit is a map of implicit type conversions that we can do,
|
||||
// and that shouldn't error. The key of the first map is the from type,
|
||||
// the key of the second map is the to type, and the final string
|
||||
// value is the function to call (which must be registered in the Scope).
|
||||
Implicit map[ast.Type]map[ast.Type]string
|
||||
|
||||
stack []ast.Type
|
||||
err error
|
||||
lock sync.Mutex
|
||||
@ -61,6 +73,12 @@ func (v *TypeCheck) visitCall(n *ast.Call) {
|
||||
// Verify the args
|
||||
for i, expected := range function.ArgTypes {
|
||||
if args[i] != expected {
|
||||
cn := v.implicitConversion(args[i], expected, n.Args[i])
|
||||
if cn != nil {
|
||||
n.Args[i] = cn
|
||||
continue
|
||||
}
|
||||
|
||||
v.createErr(n, fmt.Sprintf(
|
||||
"%s: argument %d should be %s, got %s",
|
||||
n.Func, i+1, expected, args[i]))
|
||||
@ -73,9 +91,17 @@ func (v *TypeCheck) visitCall(n *ast.Call) {
|
||||
args = args[len(function.ArgTypes):]
|
||||
for i, t := range args {
|
||||
if t != function.VariadicType {
|
||||
realI := i + len(function.ArgTypes)
|
||||
cn := v.implicitConversion(
|
||||
t, function.VariadicType, n.Args[realI])
|
||||
if cn != nil {
|
||||
n.Args[realI] = cn
|
||||
continue
|
||||
}
|
||||
|
||||
v.createErr(n, fmt.Sprintf(
|
||||
"%s: argument %d should be %s, got %s",
|
||||
n.Func, i+len(function.ArgTypes),
|
||||
n.Func, realI,
|
||||
function.VariadicType, t))
|
||||
return
|
||||
}
|
||||
@ -95,6 +121,12 @@ func (v *TypeCheck) visitConcat(n *ast.Concat) {
|
||||
// All concat args must be strings, so validate that
|
||||
for i, t := range types {
|
||||
if t != ast.TypeString {
|
||||
cn := v.implicitConversion(t, ast.TypeString, n.Exprs[i])
|
||||
if cn != nil {
|
||||
n.Exprs[i] = cn
|
||||
continue
|
||||
}
|
||||
|
||||
v.createErr(n, fmt.Sprintf(
|
||||
"argument %d must be a string", i+1))
|
||||
return
|
||||
@ -123,7 +155,32 @@ func (v *TypeCheck) visitVariableAccess(n *ast.VariableAccess) {
|
||||
}
|
||||
|
||||
func (v *TypeCheck) createErr(n ast.Node, str string) {
|
||||
v.err = fmt.Errorf("%s: %s", n.Pos(), str)
|
||||
pos := n.Pos()
|
||||
v.err = fmt.Errorf("At column %d, line %d: %s",
|
||||
pos.Column, pos.Line, str)
|
||||
}
|
||||
|
||||
func (v *TypeCheck) implicitConversion(
|
||||
actual ast.Type, expected ast.Type, n ast.Node) ast.Node {
|
||||
if v.Implicit == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
fromMap, ok := v.Implicit[actual]
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
toFunc, ok := fromMap[expected]
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &ast.Call{
|
||||
Func: toFunc,
|
||||
Args: []ast.Node{n},
|
||||
Posx: n.Pos(),
|
||||
}
|
||||
}
|
||||
|
||||
func (v *TypeCheck) reset() {
|
||||
|
@ -174,3 +174,92 @@ func TestTypeCheck(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestTypeCheck_implicit(t *testing.T) {
|
||||
implicitMap := map[ast.Type]map[ast.Type]string{
|
||||
ast.TypeInt: {
|
||||
ast.TypeString: "intToString",
|
||||
},
|
||||
}
|
||||
|
||||
cases := []struct {
|
||||
Input string
|
||||
Scope *Scope
|
||||
Error bool
|
||||
}{
|
||||
{
|
||||
"foo ${bar}",
|
||||
&Scope{
|
||||
VarMap: map[string]Variable{
|
||||
"bar": Variable{
|
||||
Value: 42,
|
||||
Type: ast.TypeInt,
|
||||
},
|
||||
},
|
||||
},
|
||||
false,
|
||||
},
|
||||
|
||||
{
|
||||
"foo ${foo(42)}",
|
||||
&Scope{
|
||||
FuncMap: map[string]Function{
|
||||
"foo": Function{
|
||||
ArgTypes: []ast.Type{ast.TypeString},
|
||||
ReturnType: ast.TypeString,
|
||||
},
|
||||
},
|
||||
},
|
||||
false,
|
||||
},
|
||||
|
||||
{
|
||||
`foo ${foo("42", 42)}`,
|
||||
&Scope{
|
||||
FuncMap: map[string]Function{
|
||||
"foo": Function{
|
||||
ArgTypes: []ast.Type{ast.TypeString},
|
||||
Variadic: true,
|
||||
VariadicType: ast.TypeString,
|
||||
ReturnType: ast.TypeString,
|
||||
},
|
||||
},
|
||||
},
|
||||
false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
node, err := Parse(tc.Input)
|
||||
if err != nil {
|
||||
t.Fatalf("Error: %s\n\nInput: %s", err, tc.Input)
|
||||
}
|
||||
|
||||
// Modify the scope to add our conversion functions.
|
||||
if tc.Scope.FuncMap == nil {
|
||||
tc.Scope.FuncMap = make(map[string]Function)
|
||||
}
|
||||
tc.Scope.FuncMap["intToString"] = Function{
|
||||
ArgTypes: []ast.Type{ast.TypeInt},
|
||||
ReturnType: ast.TypeString,
|
||||
}
|
||||
|
||||
// Do the first pass...
|
||||
visitor := &TypeCheck{Scope: tc.Scope, Implicit: implicitMap}
|
||||
err = visitor.Visit(node)
|
||||
if (err != nil) != tc.Error {
|
||||
t.Fatalf("Error: %s\n\nInput: %s", err, tc.Input)
|
||||
}
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// If we didn't error, then the next type check should not fail
|
||||
// WITHOUT implicits.
|
||||
visitor = &TypeCheck{Scope: tc.Scope}
|
||||
err = visitor.Visit(node)
|
||||
if err != nil {
|
||||
t.Fatalf("Error: %s\n\nInput: %s", err, tc.Input)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -27,9 +27,20 @@ type SemanticChecker func(ast.Node) error
|
||||
// Execute executes the given ast.Node and returns its final value, its
|
||||
// type, and an error if one exists.
|
||||
func (e *Engine) Execute(root ast.Node) (interface{}, ast.Type, error) {
|
||||
// Copy the scope so we can add our builtins
|
||||
scope := e.scope()
|
||||
implicitMap := map[ast.Type]map[ast.Type]string{
|
||||
ast.TypeInt: {
|
||||
ast.TypeString: "__builtin_IntToString",
|
||||
},
|
||||
ast.TypeString: {
|
||||
ast.TypeInt: "__builtin_StringToInt",
|
||||
},
|
||||
}
|
||||
|
||||
// Build our own semantic checks that we always run
|
||||
tv := &TypeCheck{Scope: e.GlobalScope}
|
||||
ic := &IdentifierCheck{Scope: e.GlobalScope}
|
||||
tv := &TypeCheck{Scope: scope, Implicit: implicitMap}
|
||||
ic := &IdentifierCheck{Scope: scope}
|
||||
|
||||
// Build up the semantic checks for execution
|
||||
checks := make(
|
||||
@ -46,10 +57,20 @@ func (e *Engine) Execute(root ast.Node) (interface{}, ast.Type, error) {
|
||||
}
|
||||
|
||||
// Execute
|
||||
v := &executeVisitor{Scope: e.GlobalScope}
|
||||
v := &executeVisitor{Scope: scope}
|
||||
return v.Visit(root)
|
||||
}
|
||||
|
||||
func (e *Engine) scope() *Scope {
|
||||
var scope Scope
|
||||
if e.GlobalScope != nil {
|
||||
scope = *e.GlobalScope
|
||||
}
|
||||
|
||||
registerBuiltins(&scope)
|
||||
return &scope
|
||||
}
|
||||
|
||||
// executeVisitor is the visitor used to do the actual execution of
|
||||
// a program. Note at this point it is assumed that the types check out
|
||||
// and the identifiers exist.
|
||||
|
@ -2,6 +2,7 @@ package lang
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/terraform/config/lang/ast"
|
||||
@ -77,6 +78,41 @@ func TestEngineExecute(t *testing.T) {
|
||||
"foo foobar",
|
||||
ast.TypeString,
|
||||
},
|
||||
|
||||
// Testing implicit type conversions
|
||||
|
||||
{
|
||||
"foo ${bar}",
|
||||
&Scope{
|
||||
VarMap: map[string]Variable{
|
||||
"bar": Variable{
|
||||
Value: 42,
|
||||
Type: ast.TypeInt,
|
||||
},
|
||||
},
|
||||
},
|
||||
false,
|
||||
"foo 42",
|
||||
ast.TypeString,
|
||||
},
|
||||
|
||||
{
|
||||
`foo ${foo("42")}`,
|
||||
&Scope{
|
||||
FuncMap: map[string]Function{
|
||||
"foo": Function{
|
||||
ArgTypes: []ast.Type{ast.TypeInt},
|
||||
ReturnType: ast.TypeString,
|
||||
Callback: func(args []interface{}) (interface{}, error) {
|
||||
return strconv.FormatInt(int64(args[0].(int)), 10), nil
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
false,
|
||||
"foo 42",
|
||||
ast.TypeString,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
|
@ -40,6 +40,23 @@ top:
|
||||
| literalModeTop
|
||||
{
|
||||
parserResult = $1
|
||||
|
||||
// We want to make sure that the top value is always a Concat
|
||||
// so that the return value is always a string type from an
|
||||
// interpolation.
|
||||
//
|
||||
// The logic for checking for a LiteralNode is a little annoying
|
||||
// because functionally the AST is the same, but we do that because
|
||||
// it makes for an easy literal check later (to check if a string
|
||||
// has any interpolations).
|
||||
if _, ok := $1.(*ast.Concat); !ok {
|
||||
if n, ok := $1.(*ast.LiteralNode); !ok || n.Type != ast.TypeString {
|
||||
parserResult = &ast.Concat{
|
||||
Exprs: []ast.Node{$1},
|
||||
Posx: $1.Pos(),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
literalModeTop:
|
||||
|
@ -149,23 +149,33 @@ func TestParse(t *testing.T) {
|
||||
{
|
||||
"${foo()}",
|
||||
false,
|
||||
&ast.Call{
|
||||
Func: "foo",
|
||||
Args: nil,
|
||||
&ast.Concat{
|
||||
Posx: ast.Pos{Column: 3, Line: 1},
|
||||
Exprs: []ast.Node{
|
||||
&ast.Call{
|
||||
Func: "foo",
|
||||
Args: nil,
|
||||
Posx: ast.Pos{Column: 3, Line: 1},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
"${foo(bar)}",
|
||||
false,
|
||||
&ast.Call{
|
||||
Func: "foo",
|
||||
&ast.Concat{
|
||||
Posx: ast.Pos{Column: 3, Line: 1},
|
||||
Args: []ast.Node{
|
||||
&ast.VariableAccess{
|
||||
Name: "bar",
|
||||
Posx: ast.Pos{Column: 7, Line: 1},
|
||||
Exprs: []ast.Node{
|
||||
&ast.Call{
|
||||
Func: "foo",
|
||||
Posx: ast.Pos{Column: 3, Line: 1},
|
||||
Args: []ast.Node{
|
||||
&ast.VariableAccess{
|
||||
Name: "bar",
|
||||
Posx: ast.Pos{Column: 7, Line: 1},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -174,17 +184,22 @@ func TestParse(t *testing.T) {
|
||||
{
|
||||
"${foo(bar, baz)}",
|
||||
false,
|
||||
&ast.Call{
|
||||
Func: "foo",
|
||||
&ast.Concat{
|
||||
Posx: ast.Pos{Column: 3, Line: 1},
|
||||
Args: []ast.Node{
|
||||
&ast.VariableAccess{
|
||||
Name: "bar",
|
||||
Posx: ast.Pos{Column: 7, Line: 1},
|
||||
},
|
||||
&ast.VariableAccess{
|
||||
Name: "baz",
|
||||
Posx: ast.Pos{Column: 11, Line: 1},
|
||||
Exprs: []ast.Node{
|
||||
&ast.Call{
|
||||
Func: "foo",
|
||||
Posx: ast.Pos{Column: 3, Line: 1},
|
||||
Args: []ast.Node{
|
||||
&ast.VariableAccess{
|
||||
Name: "bar",
|
||||
Posx: ast.Pos{Column: 7, Line: 1},
|
||||
},
|
||||
&ast.VariableAccess{
|
||||
Name: "baz",
|
||||
Posx: ast.Pos{Column: 11, Line: 1},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -193,17 +208,22 @@ func TestParse(t *testing.T) {
|
||||
{
|
||||
"${foo(bar(baz))}",
|
||||
false,
|
||||
&ast.Call{
|
||||
Func: "foo",
|
||||
&ast.Concat{
|
||||
Posx: ast.Pos{Column: 3, Line: 1},
|
||||
Args: []ast.Node{
|
||||
Exprs: []ast.Node{
|
||||
&ast.Call{
|
||||
Func: "bar",
|
||||
Posx: ast.Pos{Column: 7, Line: 1},
|
||||
Func: "foo",
|
||||
Posx: ast.Pos{Column: 3, Line: 1},
|
||||
Args: []ast.Node{
|
||||
&ast.VariableAccess{
|
||||
Name: "baz",
|
||||
Posx: ast.Pos{Column: 11, Line: 1},
|
||||
&ast.Call{
|
||||
Func: "bar",
|
||||
Posx: ast.Pos{Column: 7, Line: 1},
|
||||
Args: []ast.Node{
|
||||
&ast.VariableAccess{
|
||||
Name: "baz",
|
||||
Posx: ast.Pos{Column: 11, Line: 1},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -251,6 +271,12 @@ func TestParse(t *testing.T) {
|
||||
true,
|
||||
nil,
|
||||
},
|
||||
|
||||
{
|
||||
"${var",
|
||||
true,
|
||||
nil,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
|
@ -48,7 +48,7 @@ const parserEofCode = 1
|
||||
const parserErrCode = 2
|
||||
const parserMaxDepth = 200
|
||||
|
||||
//line lang.y:134
|
||||
//line lang.y:151
|
||||
|
||||
//line yacctab:1
|
||||
var parserExca = []int{
|
||||
@ -354,14 +354,31 @@ parserdefault:
|
||||
//line lang.y:41
|
||||
{
|
||||
parserResult = parserS[parserpt-0].node
|
||||
|
||||
// We want to make sure that the top value is always a Concat
|
||||
// so that the return value is always a string type from an
|
||||
// interpolation.
|
||||
//
|
||||
// The logic for checking for a LiteralNode is a little annoying
|
||||
// because functionally the AST is the same, but we do that because
|
||||
// it makes for an easy literal check later (to check if a string
|
||||
// has any interpolations).
|
||||
if _, ok := parserS[parserpt-0].node.(*ast.Concat); !ok {
|
||||
if n, ok := parserS[parserpt-0].node.(*ast.LiteralNode); !ok || n.Type != ast.TypeString {
|
||||
parserResult = &ast.Concat{
|
||||
Exprs: []ast.Node{parserS[parserpt-0].node},
|
||||
Posx: parserS[parserpt-0].node.Pos(),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
case 3:
|
||||
//line lang.y:47
|
||||
//line lang.y:64
|
||||
{
|
||||
parserVAL.node = parserS[parserpt-0].node
|
||||
}
|
||||
case 4:
|
||||
//line lang.y:51
|
||||
//line lang.y:68
|
||||
{
|
||||
var result []ast.Node
|
||||
if c, ok := parserS[parserpt-1].node.(*ast.Concat); ok {
|
||||
@ -376,27 +393,27 @@ parserdefault:
|
||||
}
|
||||
}
|
||||
case 5:
|
||||
//line lang.y:67
|
||||
//line lang.y:84
|
||||
{
|
||||
parserVAL.node = parserS[parserpt-0].node
|
||||
}
|
||||
case 6:
|
||||
//line lang.y:71
|
||||
//line lang.y:88
|
||||
{
|
||||
parserVAL.node = parserS[parserpt-0].node
|
||||
}
|
||||
case 7:
|
||||
//line lang.y:77
|
||||
//line lang.y:94
|
||||
{
|
||||
parserVAL.node = parserS[parserpt-1].node
|
||||
}
|
||||
case 8:
|
||||
//line lang.y:83
|
||||
//line lang.y:100
|
||||
{
|
||||
parserVAL.node = parserS[parserpt-0].node
|
||||
}
|
||||
case 9:
|
||||
//line lang.y:87
|
||||
//line lang.y:104
|
||||
{
|
||||
parserVAL.node = &ast.LiteralNode{
|
||||
Value: parserS[parserpt-0].token.Value.(int),
|
||||
@ -405,7 +422,7 @@ parserdefault:
|
||||
}
|
||||
}
|
||||
case 10:
|
||||
//line lang.y:95
|
||||
//line lang.y:112
|
||||
{
|
||||
parserVAL.node = &ast.LiteralNode{
|
||||
Value: parserS[parserpt-0].token.Value.(float64),
|
||||
@ -414,32 +431,32 @@ parserdefault:
|
||||
}
|
||||
}
|
||||
case 11:
|
||||
//line lang.y:103
|
||||
//line lang.y:120
|
||||
{
|
||||
parserVAL.node = &ast.VariableAccess{Name: parserS[parserpt-0].token.Value.(string), Posx: parserS[parserpt-0].token.Pos}
|
||||
}
|
||||
case 12:
|
||||
//line lang.y:107
|
||||
//line lang.y:124
|
||||
{
|
||||
parserVAL.node = &ast.Call{Func: parserS[parserpt-3].token.Value.(string), Args: parserS[parserpt-1].nodeList, Posx: parserS[parserpt-3].token.Pos}
|
||||
}
|
||||
case 13:
|
||||
//line lang.y:112
|
||||
//line lang.y:129
|
||||
{
|
||||
parserVAL.nodeList = nil
|
||||
}
|
||||
case 14:
|
||||
//line lang.y:116
|
||||
//line lang.y:133
|
||||
{
|
||||
parserVAL.nodeList = append(parserS[parserpt-2].nodeList, parserS[parserpt-0].node)
|
||||
}
|
||||
case 15:
|
||||
//line lang.y:120
|
||||
//line lang.y:137
|
||||
{
|
||||
parserVAL.nodeList = append(parserVAL.nodeList, parserS[parserpt-0].node)
|
||||
}
|
||||
case 16:
|
||||
//line lang.y:126
|
||||
//line lang.y:143
|
||||
{
|
||||
parserVAL.node = &ast.LiteralNode{
|
||||
Value: parserS[parserpt-0].token.Value.(string),
|
||||
|
@ -35,25 +35,25 @@ state 2
|
||||
state 3
|
||||
literalModeTop: literalModeValue. (3)
|
||||
|
||||
. reduce 3 (src line 45)
|
||||
. reduce 3 (src line 62)
|
||||
|
||||
|
||||
state 4
|
||||
literalModeValue: literal. (5)
|
||||
|
||||
. reduce 5 (src line 65)
|
||||
. reduce 5 (src line 82)
|
||||
|
||||
|
||||
state 5
|
||||
literalModeValue: interpolation. (6)
|
||||
|
||||
. reduce 6 (src line 70)
|
||||
. reduce 6 (src line 87)
|
||||
|
||||
|
||||
state 6
|
||||
literal: STRING. (16)
|
||||
|
||||
. reduce 16 (src line 124)
|
||||
. reduce 16 (src line 141)
|
||||
|
||||
|
||||
state 7
|
||||
@ -75,7 +75,7 @@ state 7
|
||||
state 8
|
||||
literalModeTop: literalModeTop literalModeValue. (4)
|
||||
|
||||
. reduce 4 (src line 50)
|
||||
. reduce 4 (src line 67)
|
||||
|
||||
|
||||
state 9
|
||||
@ -91,7 +91,7 @@ state 10
|
||||
|
||||
PROGRAM_BRACKET_LEFT shift 7
|
||||
STRING shift 6
|
||||
. reduce 8 (src line 81)
|
||||
. reduce 8 (src line 98)
|
||||
|
||||
interpolation goto 5
|
||||
literal goto 4
|
||||
@ -100,13 +100,13 @@ state 10
|
||||
state 11
|
||||
expr: INTEGER. (9)
|
||||
|
||||
. reduce 9 (src line 86)
|
||||
. reduce 9 (src line 103)
|
||||
|
||||
|
||||
state 12
|
||||
expr: FLOAT. (10)
|
||||
|
||||
. reduce 10 (src line 94)
|
||||
. reduce 10 (src line 111)
|
||||
|
||||
|
||||
state 13
|
||||
@ -114,13 +114,13 @@ state 13
|
||||
expr: IDENTIFIER.PAREN_LEFT args PAREN_RIGHT
|
||||
|
||||
PAREN_LEFT shift 15
|
||||
. reduce 11 (src line 102)
|
||||
. reduce 11 (src line 119)
|
||||
|
||||
|
||||
state 14
|
||||
interpolation: PROGRAM_BRACKET_LEFT expr PROGRAM_BRACKET_RIGHT. (7)
|
||||
|
||||
. reduce 7 (src line 75)
|
||||
. reduce 7 (src line 92)
|
||||
|
||||
|
||||
state 15
|
||||
@ -132,7 +132,7 @@ state 15
|
||||
INTEGER shift 11
|
||||
FLOAT shift 12
|
||||
STRING shift 6
|
||||
. reduce 13 (src line 111)
|
||||
. reduce 13 (src line 128)
|
||||
|
||||
expr goto 17
|
||||
interpolation goto 5
|
||||
@ -153,13 +153,13 @@ state 16
|
||||
state 17
|
||||
args: expr. (15)
|
||||
|
||||
. reduce 15 (src line 119)
|
||||
. reduce 15 (src line 136)
|
||||
|
||||
|
||||
state 18
|
||||
expr: IDENTIFIER PAREN_LEFT args PAREN_RIGHT. (12)
|
||||
|
||||
. reduce 12 (src line 106)
|
||||
. reduce 12 (src line 123)
|
||||
|
||||
|
||||
state 19
|
||||
@ -181,7 +181,7 @@ state 19
|
||||
state 20
|
||||
args: args COMMA expr. (14)
|
||||
|
||||
. reduce 14 (src line 115)
|
||||
. reduce 14 (src line 132)
|
||||
|
||||
|
||||
14 terminals, 8 nonterminals
|
||||
|
@ -80,7 +80,7 @@ func (r *RawConfig) Config() map[string]interface{} {
|
||||
// Any prior calls to Interpolate are replaced with this one.
|
||||
//
|
||||
// If a variable key is missing, this will panic.
|
||||
func (r *RawConfig) Interpolate(vs map[string]string) error {
|
||||
func (r *RawConfig) Interpolate(vs map[string]lang.Variable) error {
|
||||
engine := langEngine(vs)
|
||||
return r.interpolate(func(root ast.Node) (string, error) {
|
||||
out, _, err := engine.Execute(root)
|
||||
@ -203,12 +203,7 @@ type gobRawConfig struct {
|
||||
}
|
||||
|
||||
// langEngine returns the lang.Engine to use for evaluating configurations.
|
||||
func langEngine(vs map[string]string) *lang.Engine {
|
||||
varMap := make(map[string]lang.Variable)
|
||||
for k, v := range vs {
|
||||
varMap[k] = lang.Variable{Value: v, Type: ast.TypeString}
|
||||
}
|
||||
|
||||
func langEngine(vs map[string]lang.Variable) *lang.Engine {
|
||||
funcMap := make(map[string]lang.Function)
|
||||
for k, v := range Funcs {
|
||||
funcMap[k] = v
|
||||
@ -217,7 +212,7 @@ func langEngine(vs map[string]string) *lang.Engine {
|
||||
|
||||
return &lang.Engine{
|
||||
GlobalScope: &lang.Scope{
|
||||
VarMap: varMap,
|
||||
VarMap: vs,
|
||||
FuncMap: funcMap,
|
||||
},
|
||||
}
|
||||
|
@ -4,6 +4,9 @@ import (
|
||||
"encoding/gob"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/terraform/config/lang"
|
||||
"github.com/hashicorp/terraform/config/lang/ast"
|
||||
)
|
||||
|
||||
func TestNewRawConfig(t *testing.T) {
|
||||
@ -40,7 +43,12 @@ func TestRawConfig(t *testing.T) {
|
||||
t.Fatalf("bad: %#v", rc.Config())
|
||||
}
|
||||
|
||||
vars := map[string]string{"var.bar": "baz"}
|
||||
vars := map[string]lang.Variable{
|
||||
"var.bar": lang.Variable{
|
||||
Value: "baz",
|
||||
Type: ast.TypeString,
|
||||
},
|
||||
}
|
||||
if err := rc.Interpolate(vars); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
@ -68,7 +76,12 @@ func TestRawConfig_double(t *testing.T) {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
vars := map[string]string{"var.bar": "baz"}
|
||||
vars := map[string]lang.Variable{
|
||||
"var.bar": lang.Variable{
|
||||
Value: "baz",
|
||||
Type: ast.TypeString,
|
||||
},
|
||||
}
|
||||
if err := rc.Interpolate(vars); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
@ -82,7 +95,12 @@ func TestRawConfig_double(t *testing.T) {
|
||||
t.Fatalf("bad: %#v", actual)
|
||||
}
|
||||
|
||||
vars = map[string]string{"var.bar": "what"}
|
||||
vars = map[string]lang.Variable{
|
||||
"var.bar": lang.Variable{
|
||||
Value: "what",
|
||||
Type: ast.TypeString,
|
||||
},
|
||||
}
|
||||
if err := rc.Interpolate(vars); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
@ -97,6 +115,16 @@ func TestRawConfig_double(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestRawConfig_syntax(t *testing.T) {
|
||||
raw := map[string]interface{}{
|
||||
"foo": "${var",
|
||||
}
|
||||
|
||||
if _, err := NewRawConfig(raw); err == nil {
|
||||
t.Fatal("should error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRawConfig_unknown(t *testing.T) {
|
||||
raw := map[string]interface{}{
|
||||
"foo": "${var.bar}",
|
||||
@ -107,7 +135,12 @@ func TestRawConfig_unknown(t *testing.T) {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
vars := map[string]string{"var.bar": UnknownVariableValue}
|
||||
vars := map[string]lang.Variable{
|
||||
"var.bar": lang.Variable{
|
||||
Value: UnknownVariableValue,
|
||||
Type: ast.TypeString,
|
||||
},
|
||||
}
|
||||
if err := rc.Interpolate(vars); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
@ -145,7 +178,12 @@ func TestRawConfigValue(t *testing.T) {
|
||||
t.Fatalf("err: %#v", rc.Value())
|
||||
}
|
||||
|
||||
vars := map[string]string{"var.bar": "baz"}
|
||||
vars := map[string]lang.Variable{
|
||||
"var.bar": lang.Variable{
|
||||
Value: "baz",
|
||||
Type: ast.TypeString,
|
||||
},
|
||||
}
|
||||
if err := rc.Interpolate(vars); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
@ -8,6 +8,8 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/terraform/config"
|
||||
"github.com/hashicorp/terraform/config/lang"
|
||||
"github.com/hashicorp/terraform/config/lang/ast"
|
||||
"github.com/hashicorp/terraform/terraform"
|
||||
)
|
||||
|
||||
@ -21,7 +23,12 @@ func testConfig(
|
||||
}
|
||||
|
||||
if len(vs) > 0 {
|
||||
if err := rc.Interpolate(vs); err != nil {
|
||||
vars := make(map[string]lang.Variable)
|
||||
for k, v := range vs {
|
||||
vars[k] = lang.Variable{Value: v, Type: ast.TypeString}
|
||||
}
|
||||
|
||||
if err := rc.Interpolate(vars); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
}
|
||||
|
@ -5,6 +5,8 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/terraform/config"
|
||||
"github.com/hashicorp/terraform/config/lang"
|
||||
"github.com/hashicorp/terraform/config/lang/ast"
|
||||
"github.com/hashicorp/terraform/terraform"
|
||||
)
|
||||
|
||||
@ -1808,7 +1810,12 @@ func TestSchemaMap_Diff(t *testing.T) {
|
||||
}
|
||||
|
||||
if len(tc.ConfigVariables) > 0 {
|
||||
if err := c.Interpolate(tc.ConfigVariables); err != nil {
|
||||
vars := make(map[string]lang.Variable)
|
||||
for k, v := range tc.ConfigVariables {
|
||||
vars[k] = lang.Variable{Value: v, Type: ast.TypeString}
|
||||
}
|
||||
|
||||
if err := c.Interpolate(vars); err != nil {
|
||||
t.Fatalf("#%d err: %s", i, err)
|
||||
}
|
||||
}
|
||||
@ -2580,7 +2587,12 @@ func TestSchemaMap_Validate(t *testing.T) {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
if tc.Vars != nil {
|
||||
if err := c.Interpolate(tc.Vars); err != nil {
|
||||
vars := make(map[string]lang.Variable)
|
||||
for k, v := range tc.Vars {
|
||||
vars[k] = lang.Variable{Value: v, Type: ast.TypeString}
|
||||
}
|
||||
|
||||
if err := c.Interpolate(vars); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
}
|
||||
|
@ -11,6 +11,8 @@ import (
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/hashicorp/terraform/config"
|
||||
"github.com/hashicorp/terraform/config/lang"
|
||||
"github.com/hashicorp/terraform/config/lang/ast"
|
||||
"github.com/hashicorp/terraform/config/module"
|
||||
"github.com/hashicorp/terraform/depgraph"
|
||||
"github.com/hashicorp/terraform/helper/multierror"
|
||||
@ -1520,9 +1522,12 @@ func (c *walkContext) computeVars(
|
||||
}
|
||||
|
||||
// Copy the default variables
|
||||
vs := make(map[string]string)
|
||||
vs := make(map[string]lang.Variable)
|
||||
for k, v := range c.defaultVariables {
|
||||
vs[k] = v
|
||||
vs[k] = lang.Variable{
|
||||
Value: v,
|
||||
Type: ast.TypeString,
|
||||
}
|
||||
}
|
||||
|
||||
// Next, the actual computed variables
|
||||
@ -1532,12 +1537,18 @@ func (c *walkContext) computeVars(
|
||||
switch v.Type {
|
||||
case config.CountValueIndex:
|
||||
if r != nil {
|
||||
vs[n] = strconv.FormatInt(int64(r.CountIndex), 10)
|
||||
vs[n] = lang.Variable{
|
||||
Value: int(r.CountIndex),
|
||||
Type: ast.TypeInt,
|
||||
}
|
||||
}
|
||||
}
|
||||
case *config.ModuleVariable:
|
||||
if c.Operation == walkValidate {
|
||||
vs[n] = config.UnknownVariableValue
|
||||
vs[n] = lang.Variable{
|
||||
Value: config.UnknownVariableValue,
|
||||
Type: ast.TypeString,
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
@ -1546,7 +1557,10 @@ func (c *walkContext) computeVars(
|
||||
return err
|
||||
}
|
||||
|
||||
vs[n] = value
|
||||
vs[n] = lang.Variable{
|
||||
Value: value,
|
||||
Type: ast.TypeString,
|
||||
}
|
||||
case *config.PathVariable:
|
||||
switch v.Type {
|
||||
case config.PathValueCwd:
|
||||
@ -1557,17 +1571,29 @@ func (c *walkContext) computeVars(
|
||||
v.FullKey(), err)
|
||||
}
|
||||
|
||||
vs[n] = wd
|
||||
vs[n] = lang.Variable{
|
||||
Value: wd,
|
||||
Type: ast.TypeString,
|
||||
}
|
||||
case config.PathValueModule:
|
||||
if t := c.Context.module.Child(c.Path[1:]); t != nil {
|
||||
vs[n] = t.Config().Dir
|
||||
vs[n] = lang.Variable{
|
||||
Value: t.Config().Dir,
|
||||
Type: ast.TypeString,
|
||||
}
|
||||
}
|
||||
case config.PathValueRoot:
|
||||
vs[n] = c.Context.module.Config().Dir
|
||||
vs[n] = lang.Variable{
|
||||
Value: c.Context.module.Config().Dir,
|
||||
Type: ast.TypeString,
|
||||
}
|
||||
}
|
||||
case *config.ResourceVariable:
|
||||
if c.Operation == walkValidate {
|
||||
vs[n] = config.UnknownVariableValue
|
||||
vs[n] = lang.Variable{
|
||||
Value: config.UnknownVariableValue,
|
||||
Type: ast.TypeString,
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
@ -1582,16 +1608,25 @@ func (c *walkContext) computeVars(
|
||||
return err
|
||||
}
|
||||
|
||||
vs[n] = attr
|
||||
vs[n] = lang.Variable{
|
||||
Value: attr,
|
||||
Type: ast.TypeString,
|
||||
}
|
||||
case *config.UserVariable:
|
||||
val, ok := c.Variables[v.Name]
|
||||
if ok {
|
||||
vs[n] = val
|
||||
vs[n] = lang.Variable{
|
||||
Value: val,
|
||||
Type: ast.TypeString,
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if _, ok := vs[n]; !ok && c.Operation == walkValidate {
|
||||
vs[n] = config.UnknownVariableValue
|
||||
vs[n] = lang.Variable{
|
||||
Value: config.UnknownVariableValue,
|
||||
Type: ast.TypeString,
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
@ -1599,7 +1634,10 @@ func (c *walkContext) computeVars(
|
||||
// those are map overrides. Include those.
|
||||
for k, val := range c.Variables {
|
||||
if strings.HasPrefix(k, v.Name+".") {
|
||||
vs["var."+k] = val
|
||||
vs["var."+k] = lang.Variable{
|
||||
Value: val,
|
||||
Type: ast.TypeString,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user