config: convert to config/lang
This commit is contained in:
parent
e68fbceebc
commit
740c25d4ea
|
@ -8,6 +8,8 @@ import (
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/config/lang"
|
||||||
|
"github.com/hashicorp/terraform/config/lang/ast"
|
||||||
"github.com/hashicorp/terraform/flatmap"
|
"github.com/hashicorp/terraform/flatmap"
|
||||||
"github.com/hashicorp/terraform/helper/multierror"
|
"github.com/hashicorp/terraform/helper/multierror"
|
||||||
"github.com/mitchellh/mapstructure"
|
"github.com/mitchellh/mapstructure"
|
||||||
|
@ -169,7 +171,7 @@ func (c *Config) Validate() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
interp := false
|
interp := false
|
||||||
fn := func(i Interpolation) (string, error) {
|
fn := func(ast.Node) (string, error) {
|
||||||
interp = true
|
interp = true
|
||||||
return "", nil
|
return "", nil
|
||||||
}
|
}
|
||||||
|
@ -353,9 +355,18 @@ func (c *Config) Validate() error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Interpolate with a fixed number to verify that its a number
|
// Interpolate with a fixed number to verify that its a number.
|
||||||
r.RawCount.interpolate(func(Interpolation) (string, error) {
|
r.RawCount.interpolate(func(root ast.Node) (string, error) {
|
||||||
return "5", nil
|
// Execute the node but transform the AST so that it returns
|
||||||
|
// a fixed value of "5" for all interpolations.
|
||||||
|
var engine lang.Engine
|
||||||
|
out, _, err := engine.Execute(lang.FixedValueTransform(
|
||||||
|
root, &ast.LiteralNode{Value: "5", Type: ast.TypeString}))
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return out.(string), nil
|
||||||
})
|
})
|
||||||
_, err := strconv.ParseInt(r.RawCount.Value().(string), 0, 0)
|
_, err := strconv.ParseInt(r.RawCount.Value().(string), 0, 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -465,23 +476,32 @@ func (c *Config) rawConfigs() map[string]*RawConfig {
|
||||||
|
|
||||||
func (c *Config) validateVarContextFn(
|
func (c *Config) validateVarContextFn(
|
||||||
source string, errs *[]error) interpolationWalkerContextFunc {
|
source string, errs *[]error) interpolationWalkerContextFunc {
|
||||||
return func(loc reflectwalk.Location, i Interpolation) {
|
return func(loc reflectwalk.Location, node ast.Node) {
|
||||||
vi, ok := i.(*VariableInterpolation)
|
if loc == reflectwalk.SliceElem {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
vars, err := DetectVariables(node)
|
||||||
|
if err != nil {
|
||||||
|
// Ignore it since this will be caught during parse. This
|
||||||
|
// actually probably should never happen by the time this
|
||||||
|
// is called, but its okay.
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, v := range vars {
|
||||||
|
rv, ok := v.(*ResourceVariable)
|
||||||
if !ok {
|
if !ok {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
rv, ok := vi.Variable.(*ResourceVariable)
|
if rv.Multi && rv.Index == -1 {
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if rv.Multi && rv.Index == -1 && loc != reflectwalk.SliceElem {
|
|
||||||
*errs = append(*errs, fmt.Errorf(
|
*errs = append(*errs, fmt.Errorf(
|
||||||
"%s: multi-variable must be in a slice", source))
|
"%s: multi-variable must be in a slice", source))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (m *Module) mergerName() string {
|
func (m *Module) mergerName() string {
|
||||||
return m.Id()
|
return m.Id()
|
||||||
|
|
|
@ -3,9 +3,10 @@ package config
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"reflect"
|
"reflect"
|
||||||
"regexp"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/config/lang"
|
||||||
|
"github.com/hashicorp/terraform/config/lang/ast"
|
||||||
"github.com/mitchellh/reflectwalk"
|
"github.com/mitchellh/reflectwalk"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -14,10 +15,6 @@ import (
|
||||||
// a value that a user is very unlikely to use (such as UUID).
|
// a value that a user is very unlikely to use (such as UUID).
|
||||||
const InterpSplitDelim = `B780FFEC-B661-4EB8-9236-A01737AD98B6`
|
const InterpSplitDelim = `B780FFEC-B661-4EB8-9236-A01737AD98B6`
|
||||||
|
|
||||||
// interpRegexp is a regexp that matches interpolations such as ${foo.bar}
|
|
||||||
var interpRegexp *regexp.Regexp = regexp.MustCompile(
|
|
||||||
`(?i)(\$+)\{([\s*-.,\\/\(\):a-z0-9_"]+)\}`)
|
|
||||||
|
|
||||||
// interpolationWalker implements interfaces for the reflectwalk package
|
// interpolationWalker implements interfaces for the reflectwalk package
|
||||||
// (github.com/mitchellh/reflectwalk) that can be used to automatically
|
// (github.com/mitchellh/reflectwalk) that can be used to automatically
|
||||||
// execute a callback for an interpolation.
|
// execute a callback for an interpolation.
|
||||||
|
@ -50,7 +47,7 @@ type interpolationWalker struct {
|
||||||
//
|
//
|
||||||
// If Replace is set to false in interpolationWalker, then the replace
|
// If Replace is set to false in interpolationWalker, then the replace
|
||||||
// value can be anything as it will have no effect.
|
// value can be anything as it will have no effect.
|
||||||
type interpolationWalkerFunc func(Interpolation) (string, error)
|
type interpolationWalkerFunc func(ast.Node) (string, error)
|
||||||
|
|
||||||
// interpolationWalkerContextFunc is called by interpolationWalk if
|
// interpolationWalkerContextFunc is called by interpolationWalk if
|
||||||
// ContextF is set. This receives both the interpolation and the location
|
// ContextF is set. This receives both the interpolation and the location
|
||||||
|
@ -58,7 +55,7 @@ type interpolationWalkerFunc func(Interpolation) (string, error)
|
||||||
//
|
//
|
||||||
// This callback can be used to validate the location of the interpolation
|
// This callback can be used to validate the location of the interpolation
|
||||||
// within the configuration.
|
// within the configuration.
|
||||||
type interpolationWalkerContextFunc func(reflectwalk.Location, Interpolation)
|
type interpolationWalkerContextFunc func(reflectwalk.Location, ast.Node)
|
||||||
|
|
||||||
func (w *interpolationWalker) Enter(loc reflectwalk.Location) error {
|
func (w *interpolationWalker) Enter(loc reflectwalk.Location) error {
|
||||||
w.loc = loc
|
w.loc = loc
|
||||||
|
@ -121,45 +118,29 @@ func (w *interpolationWalker) Primitive(v reflect.Value) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// XXX: This can be a lot more efficient if we used a real
|
astRoot, err := lang.Parse(v.String())
|
||||||
// parser. A regexp is a hammer though that will get this working.
|
|
||||||
|
|
||||||
matches := interpRegexp.FindAllStringSubmatch(v.String(), -1)
|
|
||||||
if len(matches) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
result := v.String()
|
|
||||||
for _, match := range matches {
|
|
||||||
dollars := len(match[1])
|
|
||||||
|
|
||||||
// If there are even amounts of dollar signs, then it is escaped
|
|
||||||
if dollars%2 == 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// Interpolation found, instantiate it
|
|
||||||
key := match[2]
|
|
||||||
|
|
||||||
i, err := ExprParse(key)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If the AST we got is just a literal string value, then we ignore it
|
||||||
|
if _, ok := astRoot.(*ast.LiteralNode); ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
if w.ContextF != nil {
|
if w.ContextF != nil {
|
||||||
w.ContextF(w.loc, i)
|
w.ContextF(w.loc, astRoot)
|
||||||
}
|
}
|
||||||
|
|
||||||
if w.F == nil {
|
if w.F == nil {
|
||||||
continue
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
replaceVal, err := w.F(i)
|
replaceVal, err := w.F(astRoot)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf(
|
return fmt.Errorf(
|
||||||
"%s: %s",
|
"%s in:\n\n%s",
|
||||||
key,
|
err, v.String())
|
||||||
err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if w.Replace {
|
if w.Replace {
|
||||||
|
@ -184,13 +165,7 @@ func (w *interpolationWalker) Primitive(v reflect.Value) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Replace in our interpolation and continue on.
|
resultVal := reflect.ValueOf(replaceVal)
|
||||||
result = strings.Replace(result, match[0], replaceVal, -1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if w.Replace {
|
|
||||||
resultVal := reflect.ValueOf(result)
|
|
||||||
switch w.loc {
|
switch w.loc {
|
||||||
case reflectwalk.MapKey:
|
case reflectwalk.MapKey:
|
||||||
m := w.cs[len(w.cs)-1]
|
m := w.cs[len(w.cs)-1]
|
||||||
|
|
|
@ -1,16 +1,18 @@
|
||||||
package config
|
package config
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/config/lang/ast"
|
||||||
"github.com/mitchellh/reflectwalk"
|
"github.com/mitchellh/reflectwalk"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestInterpolationWalker_detect(t *testing.T) {
|
func TestInterpolationWalker_detect(t *testing.T) {
|
||||||
cases := []struct {
|
cases := []struct {
|
||||||
Input interface{}
|
Input interface{}
|
||||||
Result []Interpolation
|
Result []string
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
Input: map[string]interface{}{
|
Input: map[string]interface{}{
|
||||||
|
@ -23,13 +25,8 @@ func TestInterpolationWalker_detect(t *testing.T) {
|
||||||
Input: map[string]interface{}{
|
Input: map[string]interface{}{
|
||||||
"foo": "${var.foo}",
|
"foo": "${var.foo}",
|
||||||
},
|
},
|
||||||
Result: []Interpolation{
|
Result: []string{
|
||||||
&VariableInterpolation{
|
"Variable(var.foo)",
|
||||||
Variable: &UserVariable{
|
|
||||||
Name: "foo",
|
|
||||||
key: "var.foo",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -37,19 +34,8 @@ func TestInterpolationWalker_detect(t *testing.T) {
|
||||||
Input: map[string]interface{}{
|
Input: map[string]interface{}{
|
||||||
"foo": "${aws_instance.foo.*.num}",
|
"foo": "${aws_instance.foo.*.num}",
|
||||||
},
|
},
|
||||||
Result: []Interpolation{
|
Result: []string{
|
||||||
&VariableInterpolation{
|
"Variable(aws_instance.foo.*.num)",
|
||||||
Variable: &ResourceVariable{
|
|
||||||
Type: "aws_instance",
|
|
||||||
Name: "foo",
|
|
||||||
Field: "num",
|
|
||||||
|
|
||||||
Multi: true,
|
|
||||||
Index: -1,
|
|
||||||
|
|
||||||
key: "aws_instance.foo.*.num",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -57,18 +43,8 @@ func TestInterpolationWalker_detect(t *testing.T) {
|
||||||
Input: map[string]interface{}{
|
Input: map[string]interface{}{
|
||||||
"foo": "${lookup(var.foo)}",
|
"foo": "${lookup(var.foo)}",
|
||||||
},
|
},
|
||||||
Result: []Interpolation{
|
Result: []string{
|
||||||
&FunctionInterpolation{
|
"Call(lookup, Variable(var.foo))",
|
||||||
Func: nil,
|
|
||||||
Args: []Interpolation{
|
|
||||||
&VariableInterpolation{
|
|
||||||
Variable: &UserVariable{
|
|
||||||
Name: "foo",
|
|
||||||
key: "var.foo",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -76,15 +52,8 @@ func TestInterpolationWalker_detect(t *testing.T) {
|
||||||
Input: map[string]interface{}{
|
Input: map[string]interface{}{
|
||||||
"foo": `${file("test.txt")}`,
|
"foo": `${file("test.txt")}`,
|
||||||
},
|
},
|
||||||
Result: []Interpolation{
|
Result: []string{
|
||||||
&FunctionInterpolation{
|
"Call(file, Literal(TypeString, test.txt))",
|
||||||
Func: nil,
|
|
||||||
Args: []Interpolation{
|
|
||||||
&LiteralInterpolation{
|
|
||||||
Literal: "test.txt",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -92,15 +61,8 @@ func TestInterpolationWalker_detect(t *testing.T) {
|
||||||
Input: map[string]interface{}{
|
Input: map[string]interface{}{
|
||||||
"foo": `${file("foo/bar.txt")}`,
|
"foo": `${file("foo/bar.txt")}`,
|
||||||
},
|
},
|
||||||
Result: []Interpolation{
|
Result: []string{
|
||||||
&FunctionInterpolation{
|
"Call(file, Literal(TypeString, foo/bar.txt))",
|
||||||
Func: nil,
|
|
||||||
Args: []Interpolation{
|
|
||||||
&LiteralInterpolation{
|
|
||||||
Literal: "foo/bar.txt",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -108,25 +70,8 @@ func TestInterpolationWalker_detect(t *testing.T) {
|
||||||
Input: map[string]interface{}{
|
Input: map[string]interface{}{
|
||||||
"foo": `${join(",", foo.bar.*.id)}`,
|
"foo": `${join(",", foo.bar.*.id)}`,
|
||||||
},
|
},
|
||||||
Result: []Interpolation{
|
Result: []string{
|
||||||
&FunctionInterpolation{
|
"Call(join, Literal(TypeString, ,), Variable(foo.bar.*.id))",
|
||||||
Func: nil,
|
|
||||||
Args: []Interpolation{
|
|
||||||
&LiteralInterpolation{
|
|
||||||
Literal: ",",
|
|
||||||
},
|
|
||||||
&VariableInterpolation{
|
|
||||||
Variable: &ResourceVariable{
|
|
||||||
Type: "foo",
|
|
||||||
Name: "bar",
|
|
||||||
Field: "id",
|
|
||||||
Multi: true,
|
|
||||||
Index: -1,
|
|
||||||
key: "foo.bar.*.id",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -134,27 +79,16 @@ func TestInterpolationWalker_detect(t *testing.T) {
|
||||||
Input: map[string]interface{}{
|
Input: map[string]interface{}{
|
||||||
"foo": `${concat("localhost", ":8080")}`,
|
"foo": `${concat("localhost", ":8080")}`,
|
||||||
},
|
},
|
||||||
Result: []Interpolation{
|
Result: []string{
|
||||||
&FunctionInterpolation{
|
"Call(concat, Literal(TypeString, localhost), Literal(TypeString, :8080))",
|
||||||
Func: nil,
|
|
||||||
Args: []Interpolation{
|
|
||||||
&LiteralInterpolation{
|
|
||||||
Literal: "localhost",
|
|
||||||
},
|
|
||||||
&LiteralInterpolation{
|
|
||||||
Literal: ":8080",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for i, tc := range cases {
|
for i, tc := range cases {
|
||||||
var actual []Interpolation
|
var actual []string
|
||||||
|
detectFn := func(root ast.Node) (string, error) {
|
||||||
detectFn := func(i Interpolation) (string, error) {
|
actual = append(actual, fmt.Sprintf("%s", root))
|
||||||
actual = append(actual, i)
|
|
||||||
return "", nil
|
return "", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -163,14 +97,6 @@ func TestInterpolationWalker_detect(t *testing.T) {
|
||||||
t.Fatalf("err: %s", err)
|
t.Fatalf("err: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, a := range actual {
|
|
||||||
// This is jank, but reflect.DeepEqual never has functions
|
|
||||||
// being the same.
|
|
||||||
if f, ok := a.(*FunctionInterpolation); ok {
|
|
||||||
f.Func = nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !reflect.DeepEqual(actual, tc.Result) {
|
if !reflect.DeepEqual(actual, tc.Result) {
|
||||||
t.Fatalf("%d: bad:\n\n%#v", i, actual)
|
t.Fatalf("%d: bad:\n\n%#v", i, actual)
|
||||||
}
|
}
|
||||||
|
@ -198,7 +124,7 @@ func TestInterpolationWalker_replace(t *testing.T) {
|
||||||
"foo": "hello, ${var.foo}",
|
"foo": "hello, ${var.foo}",
|
||||||
},
|
},
|
||||||
Output: map[string]interface{}{
|
Output: map[string]interface{}{
|
||||||
"foo": "hello, bar",
|
"foo": "bar",
|
||||||
},
|
},
|
||||||
Value: "bar",
|
Value: "bar",
|
||||||
},
|
},
|
||||||
|
@ -247,7 +173,7 @@ func TestInterpolationWalker_replace(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
for i, tc := range cases {
|
for i, tc := range cases {
|
||||||
fn := func(i Interpolation) (string, error) {
|
fn := func(ast.Node) (string, error) {
|
||||||
return tc.Value, nil
|
return tc.Value, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,8 @@ import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/gob"
|
"encoding/gob"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/config/lang"
|
||||||
|
"github.com/hashicorp/terraform/config/lang/ast"
|
||||||
"github.com/mitchellh/copystructure"
|
"github.com/mitchellh/copystructure"
|
||||||
"github.com/mitchellh/reflectwalk"
|
"github.com/mitchellh/reflectwalk"
|
||||||
)
|
)
|
||||||
|
@ -26,7 +28,7 @@ const UnknownVariableValue = "74D93920-ED26-11E3-AC10-0800200C9A66"
|
||||||
type RawConfig struct {
|
type RawConfig struct {
|
||||||
Key string
|
Key string
|
||||||
Raw map[string]interface{}
|
Raw map[string]interface{}
|
||||||
Interpolations []Interpolation
|
Interpolations []ast.Node
|
||||||
Variables map[string]InterpolatedVariable
|
Variables map[string]InterpolatedVariable
|
||||||
|
|
||||||
config map[string]interface{}
|
config map[string]interface{}
|
||||||
|
@ -79,8 +81,23 @@ func (r *RawConfig) Config() map[string]interface{} {
|
||||||
//
|
//
|
||||||
// If a variable key is missing, this will panic.
|
// 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]string) error {
|
||||||
return r.interpolate(func(i Interpolation) (string, error) {
|
varMap := make(map[string]lang.Variable)
|
||||||
return i.Interpolate(vs)
|
for k, v := range vs {
|
||||||
|
varMap[k] = lang.Variable{Value: v, Type: ast.TypeString}
|
||||||
|
}
|
||||||
|
engine := &lang.Engine{
|
||||||
|
GlobalScope: &lang.Scope{
|
||||||
|
VarMap: varMap,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
return r.interpolate(func(root ast.Node) (string, error) {
|
||||||
|
out, _, err := engine.Execute(root)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return out.(string), nil
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -89,15 +106,19 @@ func (r *RawConfig) init() error {
|
||||||
r.Interpolations = nil
|
r.Interpolations = nil
|
||||||
r.Variables = nil
|
r.Variables = nil
|
||||||
|
|
||||||
fn := func(i Interpolation) (string, error) {
|
fn := func(node ast.Node) (string, error) {
|
||||||
r.Interpolations = append(r.Interpolations, i)
|
r.Interpolations = append(r.Interpolations, node)
|
||||||
|
vars, err := DetectVariables(node)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
for k, v := range i.Variables() {
|
for _, v := range vars {
|
||||||
if r.Variables == nil {
|
if r.Variables == nil {
|
||||||
r.Variables = make(map[string]InterpolatedVariable)
|
r.Variables = make(map[string]InterpolatedVariable)
|
||||||
}
|
}
|
||||||
|
|
||||||
r.Variables[k] = v
|
r.Variables[v.FullKey()] = v
|
||||||
}
|
}
|
||||||
|
|
||||||
return "", nil
|
return "", nil
|
||||||
|
|
Loading…
Reference in New Issue