Skip to content

Commit

Permalink
Add some basic math interpolation functions
Browse files Browse the repository at this point in the history
Support the following math functions for interpolation:

* ceil
* floor
* max
* min

Fixes hashicorp#7409
  • Loading branch information
Jesse Szwedko authored and Mathieu Herbert committed Oct 30, 2016
1 parent 78724bf commit 2eb33b5
Show file tree
Hide file tree
Showing 3 changed files with 219 additions and 0 deletions.
65 changes: 65 additions & 0 deletions config/interpolate_funcs.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"errors"
"fmt"
"io/ioutil"
"math"
"net"
"regexp"
"sort"
Expand Down Expand Up @@ -54,6 +55,7 @@ func Funcs() map[string]ast.Function {
"base64decode": interpolationFuncBase64Decode(),
"base64encode": interpolationFuncBase64Encode(),
"base64sha256": interpolationFuncBase64Sha256(),
"ceil": interpolationFuncCeil(),
"cidrhost": interpolationFuncCidrHost(),
"cidrnetmask": interpolationFuncCidrNetmask(),
"cidrsubnet": interpolationFuncCidrSubnet(),
Expand All @@ -63,6 +65,7 @@ func Funcs() map[string]ast.Function {
"distinct": interpolationFuncDistinct(),
"element": interpolationFuncElement(),
"file": interpolationFuncFile(),
"floor": interpolationFuncFloor(),
"format": interpolationFuncFormat(),
"formatlist": interpolationFuncFormatList(),
"index": interpolationFuncIndex(),
Expand All @@ -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(),
Expand Down Expand Up @@ -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{
Expand Down
144 changes: 144 additions & 0 deletions config/interpolate_funcs_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{
Expand Down
10 changes: 10 additions & 0 deletions website/source/docs/configuration/interpolation.html.md
Original file line number Diff line number Diff line change
Expand Up @@ -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``.
Expand Down Expand Up @@ -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/).
Expand Down Expand Up @@ -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.

Expand Down

0 comments on commit 2eb33b5

Please sign in to comment.