Ejemplo n.º 1
0
func Example_variables() {
	input := "${var.test} - ${6 + 2}"

	tree, err := hil.Parse(input)
	if err != nil {
		log.Fatal(err)
	}

	config := &hil.EvalConfig{
		GlobalScope: &ast.BasicScope{
			VarMap: map[string]ast.Variable{
				"var.test": ast.Variable{
					Type:  ast.TypeString,
					Value: "TEST STRING",
				},
			},
		},
	}

	result, err := hil.Eval(tree, config)
	if err != nil {
		log.Fatal(err)
	}

	fmt.Printf("Type: %s\n", result.Type)
	fmt.Printf("Value: %s\n", result.Value)
	// Output:
	// Type: TypeString
	// Value: TEST STRING - 8
}
Ejemplo n.º 2
0
// Compile validates a prepared query template and returns an opaque compiled
// object that can be used later to render the template.
func Compile(query *structs.PreparedQuery) (*CompiledTemplate, error) {
	// Make sure it's a type we understand.
	if query.Template.Type != structs.QueryTemplateTypeNamePrefixMatch {
		return nil, fmt.Errorf("Bad Template.Type '%s'", query.Template.Type)
	}

	// Start compile.
	ct := &CompiledTemplate{
		trees: make(map[string]ast.Node),
	}

	// Make a copy of the query to use as the basis for rendering later.
	dup, err := copystructure.Copy(query)
	if err != nil {
		return nil, err
	}
	var ok bool
	ct.query, ok = dup.(*structs.PreparedQuery)
	if !ok {
		return nil, fmt.Errorf("Failed to copy query")
	}

	// Walk over all the string fields in the Service sub-structure and
	// parse them as HIL.
	parse := func(path string, v reflect.Value) error {
		tree, err := hil.Parse(v.String())
		if err != nil {
			return fmt.Errorf("Bad format '%s' in Service%s: %s", v.String(), path, err)
		}

		ct.trees[path] = tree
		return nil
	}
	if err := walk(&ct.query.Service, parse); err != nil {
		return nil, err
	}

	// If they supplied a regexp then compile it.
	if ct.query.Template.Regexp != "" {
		var err error
		ct.re, err = regexp.Compile(ct.query.Template.Regexp)
		if err != nil {
			return nil, fmt.Errorf("Bad Regexp: %s", err)
		}
	}

	// Finally do a test render with the supplied name prefix. This will
	// help catch errors before run time, and this is the most minimal
	// prefix it will be expected to run with. The results might not make
	// sense and create a valid service to lookup, but it should render
	// without any errors.
	if _, err = ct.Render(ct.query.Name); err != nil {
		return nil, err
	}

	return ct, nil
}
Ejemplo n.º 3
0
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 := hil.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)
		}
	}
}
// execute parses and executes a template using vars.
func execute(s string, vars map[string]interface{}) (string, error) {
	root, err := hil.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)
		}

		// Store the defaults (string and value)
		var val interface{} = s
		typ := ast.TypeString

		// If we can parse a float, then use that
		if v, err := strconv.ParseFloat(s, 64); err == nil {
			val = v
			typ = ast.TypeFloat
		}

		varmap[k] = ast.Variable{
			Value: val,
			Type:  typ,
		}
	}

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

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

	return result.Value.(string), nil
}
Ejemplo n.º 5
0
func testFunction(t *testing.T, config testFunctionConfig) {
	for i, tc := range config.Cases {
		ast, err := hil.Parse(tc.Input)
		if err != nil {
			t.Fatalf("Case #%d: input: %#v\nerr: %s", i, tc.Input, err)
		}

		result, err := hil.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(result.Value, tc.Result) {
			t.Fatalf("%d: bad output for input: %s\n\nOutput: %#v\nExpected: %#v",
				i, tc.Input, result.Value, tc.Result)
		}
	}
}
Ejemplo n.º 6
0
func Example_functions() {
	input := "${lower(var.test)} - ${6 + 2}"

	tree, err := hil.Parse(input)
	if err != nil {
		log.Fatal(err)
	}

	lowerCase := ast.Function{
		ArgTypes:   []ast.Type{ast.TypeString},
		ReturnType: ast.TypeString,
		Variadic:   false,
		Callback: func(inputs []interface{}) (interface{}, error) {
			input := inputs[0].(string)
			return strings.ToLower(input), nil
		},
	}

	config := &hil.EvalConfig{
		GlobalScope: &ast.BasicScope{
			VarMap: map[string]ast.Variable{
				"var.test": ast.Variable{
					Type:  ast.TypeString,
					Value: "TEST STRING",
				},
			},
			FuncMap: map[string]ast.Function{
				"lower": lowerCase,
			},
		},
	}

	result, err := hil.Eval(tree, config)
	if err != nil {
		log.Fatal(err)
	}

	fmt.Printf("Type: %s\n", result.Type)
	fmt.Printf("Value: %s\n", result.Value)
	// Output:
	// Type: TypeString
	// Value: test string - 8
}
Ejemplo n.º 7
0
func Example_basic() {
	input := "${6 + 2}"

	tree, err := hil.Parse(input)
	if err != nil {
		log.Fatal(err)
	}

	value, valueType, err := hil.Eval(tree, &hil.EvalConfig{})
	if err != nil {
		log.Fatal(err)
	}

	fmt.Printf("Type: %s\n", valueType)
	fmt.Printf("Value: %s\n", value)
	// Output:
	// Type: TypeString
	// Value: 8
}
Ejemplo n.º 8
0
func TestInterpolateFuncTimestamp(t *testing.T) {
	currentTime := time.Now().UTC()
	ast, err := hil.Parse("${timestamp()}")
	if err != nil {
		t.Fatalf("err: %s", err)
	}

	result, err := hil.Eval(ast, langEvalConfig(nil))
	if err != nil {
		t.Fatalf("err: %s", err)
	}
	resultTime, err := time.Parse(time.RFC3339, result.Value.(string))
	if err != nil {
		t.Fatalf("Error parsing timestamp: %s", err)
	}

	if resultTime.Sub(currentTime).Seconds() > 10.0 {
		t.Fatalf("Timestamp Diff too large. Expected: %s\nRecieved: %s", currentTime.Format(time.RFC3339), result.Value.(string))
	}
}
Ejemplo n.º 9
0
func TestInterpolateFuncUUID(t *testing.T) {
	results := make(map[string]bool)

	for i := 0; i < 100; i++ {
		ast, err := hil.Parse("${uuid()}")
		if err != nil {
			t.Fatalf("err: %s", err)
		}

		result, err := hil.Eval(ast, langEvalConfig(nil))
		if err != nil {
			t.Fatalf("err: %s", err)
		}

		if results[result.Value.(string)] {
			t.Fatalf("Got unexpected duplicate uuid: %s", result.Value)
		}

		results[result.Value.(string)] = true
	}
}
Ejemplo n.º 10
0
// DetectUserVariables parses the template for any ${var.foo}, ${var.bar},
// etc.. user variables. It returns a list of found variables with, example:
// []string{"foo", "bar"}. The returned list only contains unique names, so any
// user variable which declared multiple times is neglected, only the last
// occurence is being added.
func (t *Template) DetectUserVariables(prefix string) (map[string]string, error) {
	if !strings.HasSuffix(prefix, "_") {
		prefix = prefix + "_"
	}

	out, err := t.JsonOutput()
	if err != nil {
		return nil, err
	}

	// get AST first, it's capable of parsing json
	a, err := hil.Parse(out)
	if err != nil {
		return nil, err
	}

	// read the variables from the given AST. This is basically just iterating
	// over the AST node and does the heavy lifting for us
	vars, err := config.DetectVariables(a)
	if err != nil {
		return nil, err
	}
	// filter out duplicates
	userVars := make(map[string]string, 0)
	for _, v := range vars {
		// be sure we only get userVariables, as there is many ways of
		// declaring variables
		u, ok := v.(*config.UserVariable)
		if !ok {
			continue
		}

		if _, ok = userVars[u.Name]; !ok && strings.HasPrefix(u.Name, prefix) {
			userVars[u.Name] = ""
		}
	}

	return userVars, nil
}
Ejemplo n.º 11
0
// execute parses and executes a template using vars.
func execute(s string, vars map[string]interface{}) (string, error) {
	root, err := hil.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 := hil.EvalConfig{
		GlobalScope: &ast.BasicScope{
			VarMap:  varmap,
			FuncMap: config.Funcs(),
		},
	}

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

	return result.Value.(string), nil
}
Ejemplo n.º 12
0
func Eval(tmpl string, variables Variables) (string, error) {
	tree, err := hil.Parse(tmpl)
	if err != nil {
		return "", err
	}

	varMap := make(map[string]ast.Variable)
	for n, v := range variables {
		varMap[n] = ast.Variable{Type: ast.TypeString, Value: v}
	}
	config := &hil.EvalConfig{
		GlobalScope: &ast.BasicScope{VarMap: varMap},
	}

	out, _, err := hil.Eval(tree, config)
	if err != nil {
		return "", err
	}
	if s, ok := out.(string); ok {
		return s, nil
	} else {
		return "", fmt.Errorf("This is a bug: expected to find string, but found %s.\nPlease report that to %s", reflect.TypeOf(out), bugTracker)
	}
}
Ejemplo n.º 13
0
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 := hil.Parse(v.String())
	if err != nil {
		return err
	}

	// If the AST we got is just a literal string value with the same
	// value then we ignore it. We have to check if its the same value
	// because it is possible to input a string, get out a string, and
	// have it be different. For example: "foo-$${bar}" turns into
	// "foo-${bar}"
	if n, ok := astRoot.(*ast.LiteralNode); ok {
		if s, ok := n.Value.(string); ok && s == v.String() {
			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 {
			switch typedReplaceVal := replaceVal.(type) {
			case string:
				if typedReplaceVal == UnknownVariableValue {
					remove = true
				}
			case []interface{}:
				if hasUnknownValue(typedReplaceVal) {
					remove = true
				}
			}
		} else if replaceVal == UnknownVariableValue {
			remove = true
		}

		if remove {
			w.unknownKeys = append(w.unknownKeys, strings.Join(w.key, "."))
		}

		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
}