From 0fbd72a3558f29589e86d32afea6a4bc4ce4a31f Mon Sep 17 00:00:00 2001 From: Jesse Szwedko Date: Fri, 28 Oct 2016 17:49:31 +0000 Subject: [PATCH] Add some basic math interpolation functions Support the following math functions for interpolation: * ceil * floor * max * min Fixes #7409 --- config/interpolate_funcs.go | 65 ++++++++ config/interpolate_funcs_test.go | 144 ++++++++++++++++++ .../docs/configuration/interpolation.html.md | 10 ++ 3 files changed, 219 insertions(+) diff --git a/config/interpolate_funcs.go b/config/interpolate_funcs.go index b8bd804c67cf..102901cf57bf 100644 --- a/config/interpolate_funcs.go +++ b/config/interpolate_funcs.go @@ -10,6 +10,7 @@ import ( "errors" "fmt" "io/ioutil" + "math" "net" "regexp" "sort" @@ -54,6 +55,7 @@ func Funcs() map[string]ast.Function { "base64decode": interpolationFuncBase64Decode(), "base64encode": interpolationFuncBase64Encode(), "base64sha256": interpolationFuncBase64Sha256(), + "ceil": interpolationFuncCeil(), "cidrhost": interpolationFuncCidrHost(), "cidrnetmask": interpolationFuncCidrNetmask(), "cidrsubnet": interpolationFuncCidrSubnet(), @@ -63,6 +65,7 @@ func Funcs() map[string]ast.Function { "distinct": interpolationFuncDistinct(), "element": interpolationFuncElement(), "file": interpolationFuncFile(), + "floor": interpolationFuncFloor(), "format": interpolationFuncFormat(), "formatlist": interpolationFuncFormatList(), "index": interpolationFuncIndex(), @@ -72,8 +75,10 @@ func Funcs() map[string]ast.Function { "list": interpolationFuncList(), "lower": interpolationFuncLower(), "map": interpolationFuncMap(), + "max": interpolationFuncMax(), "md5": interpolationFuncMd5(), "merge": interpolationFuncMerge(), + "min": interpolationFuncMin(), "uuid": interpolationFuncUUID(), "replace": interpolationFuncReplace(), "sha1": interpolationFuncSha1(), @@ -387,6 +392,66 @@ func interpolationFuncFormat() ast.Function { } } +// interpolationFuncMax returns the maximum of the numeric arguments +func interpolationFuncMax() ast.Function { + return ast.Function{ + ArgTypes: []ast.Type{ast.TypeFloat}, + ReturnType: ast.TypeFloat, + Variadic: true, + VariadicType: ast.TypeFloat, + Callback: func(args []interface{}) (interface{}, error) { + max := args[0].(float64) + + for i := 1; i < len(args); i++ { + max = math.Max(max, args[i].(float64)) + } + + return max, nil + }, + } +} + +// interpolationFuncMin returns the minimum of the numeric arguments +func interpolationFuncMin() ast.Function { + return ast.Function{ + ArgTypes: []ast.Type{ast.TypeFloat}, + ReturnType: ast.TypeFloat, + Variadic: true, + VariadicType: ast.TypeFloat, + Callback: func(args []interface{}) (interface{}, error) { + min := args[0].(float64) + + for i := 1; i < len(args); i++ { + min = math.Min(min, args[i].(float64)) + } + + return min, nil + }, + } +} + +// interpolationFuncCeil returns the the least integer value greater than or equal to the argument +func interpolationFuncCeil() ast.Function { + return ast.Function{ + ArgTypes: []ast.Type{ast.TypeFloat}, + ReturnType: ast.TypeInt, + Callback: func(args []interface{}) (interface{}, error) { + return int(math.Ceil(args[0].(float64))), nil + }, + } +} + +// interpolationFuncFloorreturns returns the greatest integer value less than or equal to the argument +func interpolationFuncFloor() ast.Function { + return ast.Function{ + ArgTypes: []ast.Type{ast.TypeFloat}, + ReturnType: ast.TypeInt, + Callback: func(args []interface{}) (interface{}, error) { + return int(math.Floor(args[0].(float64))), nil + }, + } +} + func interpolationFuncZipMap() ast.Function { return ast.Function{ ArgTypes: []ast.Type{ diff --git a/config/interpolate_funcs_test.go b/config/interpolate_funcs_test.go index 89c147a90cd2..dab9bde3691c 100644 --- a/config/interpolate_funcs_test.go +++ b/config/interpolate_funcs_test.go @@ -222,6 +222,150 @@ func TestInterpolateFuncList(t *testing.T) { }) } +func TestInterpolateFuncMax(t *testing.T) { + testFunction(t, testFunctionConfig{ + Cases: []testFunctionCase{ + { + `${max()}`, + nil, + true, + }, + + { + `${max("")}`, + nil, + true, + }, + + { + `${max(-1, 0, 1)}`, + "1", + false, + }, + + { + `${max(1, 0, -1)}`, + "1", + false, + }, + + { + `${max(-1, -2)}`, + "-1", + false, + }, + + { + `${max(-1)}`, + "-1", + false, + }, + }, + }) +} + +func TestInterpolateFuncMin(t *testing.T) { + testFunction(t, testFunctionConfig{ + Cases: []testFunctionCase{ + { + `${min()}`, + nil, + true, + }, + + { + `${min("")}`, + nil, + true, + }, + + { + `${min(-1, 0, 1)}`, + "-1", + false, + }, + + { + `${min(1, 0, -1)}`, + "-1", + false, + }, + + { + `${min(-1, -2)}`, + "-2", + false, + }, + + { + `${min(-1)}`, + "-1", + false, + }, + }, + }) +} + +func TestInterpolateFuncFloor(t *testing.T) { + testFunction(t, testFunctionConfig{ + Cases: []testFunctionCase{ + { + `${floor()}`, + nil, + true, + }, + + { + `${floor("")}`, + nil, + true, + }, + + { + `${floor("-1.3")}`, // there appears to be a AST bug where the parsed argument ends up being -1 without the "s + "-2", + false, + }, + + { + `${floor(1.7)}`, + "1", + false, + }, + }, + }) +} + +func TestInterpolateFuncCeil(t *testing.T) { + testFunction(t, testFunctionConfig{ + Cases: []testFunctionCase{ + { + `${ceil()}`, + nil, + true, + }, + + { + `${ceil("")}`, + nil, + true, + }, + + { + `${ceil(-1.8)}`, + "-1", + false, + }, + + { + `${ceil(1.2)}`, + "2", + false, + }, + }, + }) +} + func TestInterpolateFuncMap(t *testing.T) { testFunction(t, testFunctionConfig{ Cases: []testFunctionCase{ diff --git a/website/source/docs/configuration/interpolation.html.md b/website/source/docs/configuration/interpolation.html.md index fe4774071e39..f0d3a963726c 100644 --- a/website/source/docs/configuration/interpolation.html.md +++ b/website/source/docs/configuration/interpolation.html.md @@ -89,6 +89,9 @@ The supported built-in functions are: **This is not equivalent** of `base64encode(sha256(string))` since `sha256()` returns hexadecimal representation. + * `ceil(float)` - Returns the the least integer value greater than or equal + to the argument. + * `cidrhost(iprange, hostnum)` - Takes an IP address range in CIDR notation and creates an IP address with the given host number. For example, ``cidrhost("10.0.0.0/8", 2)`` returns ``10.0.0.2``. @@ -137,6 +140,9 @@ The supported built-in functions are: module, you generally want to make the path relative to the module base, like this: `file("${path.module}/file")`. + * `floor(float)` - Returns the greatest integer value less than or equal to + the argument + * `format(format, args, ...)` - Formats a string according to the given format. The syntax for the format is standard `sprintf` syntax. Good documentation for the syntax can be [found here](https://golang.org/pkg/fmt/). @@ -197,11 +203,15 @@ The supported built-in functions are: * `map("hello", "world")` * `map("us-east", list("a", "b", "c"), "us-west", list("b", "c", "d"))` + * `max(float1, float2, ...)` - Returns the largest of the floats. + * `merge(map1, map2, ...)` - Returns the union of 2 or more maps. The maps are consumed in the order provided, and duplicate keys overwrite previous entries. * `${merge(map("a", "b"), map("c", "d"))}` returns `{"a": "b", "c": "d"}` + * `min(float1, float2, ...)` - Returns the smallest of the floats. + * `md5(string)` - Returns a (conventional) hexadecimal representation of the MD5 hash of the given string.