func TestDetectVariables(t *testing.T) {
	cases := []struct {
		Input  string
		Result []InterpolatedVariable
	}{
		{
			"foo $${var.foo}",
			nil,
		},

		{
			"foo ${var.foo}",
			[]InterpolatedVariable{
				&UserVariable{
					Name: "foo",
					key:  "var.foo",
				},
			},
		},

		{
			"foo ${var.foo} ${var.bar}",
			[]InterpolatedVariable{
				&UserVariable{
					Name: "foo",
					key:  "var.foo",
				},
				&UserVariable{
					Name: "bar",
					key:  "var.bar",
				},
			},
		},
	}

	for _, tc := range cases {
		ast, err := lang.Parse(tc.Input)
		if err != nil {
			t.Fatalf("%s\n\nInput: %s", err, tc.Input)
		}

		actual, err := DetectVariables(ast)
		if err != nil {
			t.Fatalf("err: %s", err)
		}
		if !reflect.DeepEqual(actual, tc.Result) {
			t.Fatalf("bad: %#v\n\nInput: %s", actual, tc.Input)
		}
	}
}
func testFunction(t *testing.T, config testFunctionConfig) {
	for i, tc := range config.Cases {
		ast, err := lang.Parse(tc.Input)
		if err != nil {
			t.Fatalf("Case #%d: input: %#v\nerr: %s", i, tc.Input, err)
		}

		out, _, err := lang.Eval(ast, langEvalConfig(config.Vars))
		if err != nil != tc.Error {
			t.Fatalf("Case #%d:\ninput: %#v\nerr: %s", i, tc.Input, err)
		}

		if !reflect.DeepEqual(out, tc.Result) {
			t.Fatalf(
				"%d: bad output for input: %s\n\nOutput: %#v\nExpected: %#v",
				i, tc.Input, out, tc.Result)
		}
	}
}
// execute parses and executes a template using vars.
func execute(s string, vars map[string]interface{}) (string, error) {
	root, err := lang.Parse(s)
	if err != nil {
		return "", err
	}

	varmap := make(map[string]ast.Variable)
	for k, v := range vars {
		// As far as I can tell, v is always a string.
		// If it's not, tell the user gracefully.
		s, ok := v.(string)
		if !ok {
			return "", fmt.Errorf("unexpected type for variable %q: %T", k, v)
		}
		varmap[k] = ast.Variable{
			Value: s,
			Type:  ast.TypeString,
		}
	}

	cfg := lang.EvalConfig{
		GlobalScope: &ast.BasicScope{
			VarMap:  varmap,
			FuncMap: config.Funcs(),
		},
	}

	out, typ, err := lang.Eval(root, &cfg)
	if err != nil {
		return "", err
	}
	if typ != ast.TypeString {
		return "", fmt.Errorf("unexpected output ast.Type: %v", typ)
	}

	return out.(string), nil
}
func (w *interpolationWalker) Primitive(v reflect.Value) error {
	setV := v

	// We only care about strings
	if v.Kind() == reflect.Interface {
		setV = v
		v = v.Elem()
	}
	if v.Kind() != reflect.String {
		return nil
	}

	astRoot, err := lang.Parse(v.String())
	if err != nil {
		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 {
		w.ContextF(w.loc, astRoot)
	}

	if w.F == nil {
		return nil
	}

	replaceVal, err := w.F(astRoot)
	if err != nil {
		return fmt.Errorf(
			"%s in:\n\n%s",
			err, v.String())
	}

	if w.Replace {
		// We need to determine if we need to remove this element
		// if the result contains any "UnknownVariableValue" which is
		// set if it is computed. This behavior is different if we're
		// splitting (in a SliceElem) or not.
		remove := false
		if w.loc == reflectwalk.SliceElem && IsStringList(replaceVal) {
			parts := StringList(replaceVal).Slice()
			for _, p := range parts {
				if p == UnknownVariableValue {
					remove = true
					break
				}
			}
		} else if replaceVal == UnknownVariableValue {
			remove = true
		}
		if remove {
			w.removeCurrent()
			return nil
		}

		resultVal := reflect.ValueOf(replaceVal)
		switch w.loc {
		case reflectwalk.MapKey:
			m := w.cs[len(w.cs)-1]

			// Delete the old value
			var zero reflect.Value
			m.SetMapIndex(w.csData.(reflect.Value), zero)

			// Set the new key with the existing value
			m.SetMapIndex(resultVal, w.lastValue)

			// Set the key to be the new key
			w.csData = resultVal
		case reflectwalk.MapValue:
			// If we're in a map, then the only way to set a map value is
			// to set it directly.
			m := w.cs[len(w.cs)-1]
			mk := w.csData.(reflect.Value)
			m.SetMapIndex(mk, resultVal)
		default:
			// Otherwise, we should be addressable
			setV.Set(resultVal)
		}
	}

	return nil
}
Beispiel #5
0
// ParseInterpolations parses the string for Terraform-style interpolations and
// returns the resulting root AST node, or an error if the string is invalid.
func (v InterpolationString) ParseInterpolations() (tfast.Node, error) {
	return tflang.Parse(string(v))
}