mirror of
https://github.com/opentofu/opentofu.git
synced 2025-01-04 13:17:43 -06:00
9d11d427a8
This hopefully makes it clearer that it works with sets rather than real numbers, avoiding confusion with "sum", "min", "max", etc.
3015 lines
55 KiB
Go
3015 lines
55 KiB
Go
package config
|
|
|
|
import (
|
|
"fmt"
|
|
"io/ioutil"
|
|
"os"
|
|
"reflect"
|
|
"testing"
|
|
"time"
|
|
|
|
"path/filepath"
|
|
|
|
"github.com/hashicorp/hil"
|
|
"github.com/hashicorp/hil/ast"
|
|
"github.com/mitchellh/go-homedir"
|
|
"golang.org/x/crypto/bcrypt"
|
|
)
|
|
|
|
func TestInterpolateFuncZipMap(t *testing.T) {
|
|
testFunction(t, testFunctionConfig{
|
|
Cases: []testFunctionCase{
|
|
{
|
|
`${zipmap(var.list, var.list2)}`,
|
|
map[string]interface{}{
|
|
"Hello": "bar",
|
|
"World": "baz",
|
|
},
|
|
false,
|
|
},
|
|
{
|
|
`${zipmap(var.list, var.nonstrings)}`,
|
|
map[string]interface{}{
|
|
"Hello": []interface{}{"bar", "baz"},
|
|
"World": []interface{}{"boo", "foo"},
|
|
},
|
|
false,
|
|
},
|
|
{
|
|
`${zipmap(var.nonstrings, var.list2)}`,
|
|
nil,
|
|
true,
|
|
},
|
|
{
|
|
`${zipmap(var.list, var.differentlengthlist)}`,
|
|
nil,
|
|
true,
|
|
},
|
|
},
|
|
Vars: map[string]ast.Variable{
|
|
"var.list": {
|
|
Type: ast.TypeList,
|
|
Value: []ast.Variable{
|
|
{
|
|
Type: ast.TypeString,
|
|
Value: "Hello",
|
|
},
|
|
{
|
|
Type: ast.TypeString,
|
|
Value: "World",
|
|
},
|
|
},
|
|
},
|
|
"var.list2": {
|
|
Type: ast.TypeList,
|
|
Value: []ast.Variable{
|
|
{
|
|
Type: ast.TypeString,
|
|
Value: "bar",
|
|
},
|
|
{
|
|
Type: ast.TypeString,
|
|
Value: "baz",
|
|
},
|
|
},
|
|
},
|
|
"var.differentlengthlist": {
|
|
Type: ast.TypeList,
|
|
Value: []ast.Variable{
|
|
{
|
|
Type: ast.TypeString,
|
|
Value: "bar",
|
|
},
|
|
{
|
|
Type: ast.TypeString,
|
|
Value: "baz",
|
|
},
|
|
{
|
|
Type: ast.TypeString,
|
|
Value: "boo",
|
|
},
|
|
},
|
|
},
|
|
"var.nonstrings": {
|
|
Type: ast.TypeList,
|
|
Value: []ast.Variable{
|
|
{
|
|
Type: ast.TypeList,
|
|
Value: []ast.Variable{
|
|
{
|
|
Type: ast.TypeString,
|
|
Value: "bar",
|
|
},
|
|
{
|
|
Type: ast.TypeString,
|
|
Value: "baz",
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Type: ast.TypeList,
|
|
Value: []ast.Variable{
|
|
{
|
|
Type: ast.TypeString,
|
|
Value: "boo",
|
|
},
|
|
{
|
|
Type: ast.TypeString,
|
|
Value: "foo",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestInterpolateFuncList(t *testing.T) {
|
|
testFunction(t, testFunctionConfig{
|
|
Cases: []testFunctionCase{
|
|
// empty input returns empty list
|
|
{
|
|
`${list()}`,
|
|
[]interface{}{},
|
|
false,
|
|
},
|
|
|
|
// single input returns list of length 1
|
|
{
|
|
`${list("hello")}`,
|
|
[]interface{}{"hello"},
|
|
false,
|
|
},
|
|
|
|
// two inputs returns list of length 2
|
|
{
|
|
`${list("hello", "world")}`,
|
|
[]interface{}{"hello", "world"},
|
|
false,
|
|
},
|
|
|
|
// not a string input gives error
|
|
{
|
|
`${list("hello", 42)}`,
|
|
nil,
|
|
true,
|
|
},
|
|
|
|
// list of lists
|
|
{
|
|
`${list("${var.list}", "${var.list2}")}`,
|
|
[]interface{}{[]interface{}{"Hello", "World"}, []interface{}{"bar", "baz"}},
|
|
false,
|
|
},
|
|
|
|
// list of maps
|
|
{
|
|
`${list("${var.map}", "${var.map2}")}`,
|
|
[]interface{}{map[string]interface{}{"key": "bar"}, map[string]interface{}{"key2": "baz"}},
|
|
false,
|
|
},
|
|
|
|
// error on a heterogeneous list
|
|
{
|
|
`${list("first", "${var.list}")}`,
|
|
nil,
|
|
true,
|
|
},
|
|
},
|
|
Vars: map[string]ast.Variable{
|
|
"var.list": {
|
|
Type: ast.TypeList,
|
|
Value: []ast.Variable{
|
|
{
|
|
Type: ast.TypeString,
|
|
Value: "Hello",
|
|
},
|
|
{
|
|
Type: ast.TypeString,
|
|
Value: "World",
|
|
},
|
|
},
|
|
},
|
|
"var.list2": {
|
|
Type: ast.TypeList,
|
|
Value: []ast.Variable{
|
|
{
|
|
Type: ast.TypeString,
|
|
Value: "bar",
|
|
},
|
|
{
|
|
Type: ast.TypeString,
|
|
Value: "baz",
|
|
},
|
|
},
|
|
},
|
|
|
|
"var.map": {
|
|
Type: ast.TypeMap,
|
|
Value: map[string]ast.Variable{
|
|
"key": {
|
|
Type: ast.TypeString,
|
|
Value: "bar",
|
|
},
|
|
},
|
|
},
|
|
"var.map2": {
|
|
Type: ast.TypeMap,
|
|
Value: map[string]ast.Variable{
|
|
"key2": {
|
|
Type: ast.TypeString,
|
|
Value: "baz",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
})
|
|
}
|
|
|
|
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 TestInterpolateFuncPow(t *testing.T) {
|
|
testFunction(t, testFunctionConfig{
|
|
Cases: []testFunctionCase{
|
|
{
|
|
`${pow(1, 0)}`,
|
|
"1",
|
|
false,
|
|
},
|
|
{
|
|
`${pow(1, 1)}`,
|
|
"1",
|
|
false,
|
|
},
|
|
|
|
{
|
|
`${pow(2, 0)}`,
|
|
"1",
|
|
false,
|
|
},
|
|
{
|
|
`${pow(2, 1)}`,
|
|
"2",
|
|
false,
|
|
},
|
|
{
|
|
`${pow(3, 2)}`,
|
|
"9",
|
|
false,
|
|
},
|
|
{
|
|
`${pow(-3, 2)}`,
|
|
"9",
|
|
false,
|
|
},
|
|
{
|
|
`${pow(2, -2)}`,
|
|
"0.25",
|
|
false,
|
|
},
|
|
{
|
|
`${pow(0, 2)}`,
|
|
"0",
|
|
false,
|
|
},
|
|
{
|
|
`${pow("invalid-input", 2)}`,
|
|
nil,
|
|
true,
|
|
},
|
|
{
|
|
`${pow(2, "invalid-input")}`,
|
|
nil,
|
|
true,
|
|
},
|
|
{
|
|
`${pow(2)}`,
|
|
nil,
|
|
true,
|
|
},
|
|
},
|
|
})
|
|
}
|
|
|
|
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 TestInterpolateFuncLog(t *testing.T) {
|
|
testFunction(t, testFunctionConfig{
|
|
Cases: []testFunctionCase{
|
|
{
|
|
`${log(1, 10)}`,
|
|
"0",
|
|
false,
|
|
},
|
|
{
|
|
`${log(10, 10)}`,
|
|
"1",
|
|
false,
|
|
},
|
|
|
|
{
|
|
`${log(0, 10)}`,
|
|
"-Inf",
|
|
false,
|
|
},
|
|
{
|
|
`${log(10, 0)}`,
|
|
"-0",
|
|
false,
|
|
},
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestInterpolateFuncChomp(t *testing.T) {
|
|
testFunction(t, testFunctionConfig{
|
|
Cases: []testFunctionCase{
|
|
{
|
|
`${chomp()}`,
|
|
nil,
|
|
true,
|
|
},
|
|
|
|
{
|
|
`${chomp("hello world")}`,
|
|
"hello world",
|
|
false,
|
|
},
|
|
|
|
{
|
|
fmt.Sprintf(`${chomp("%s")}`, "goodbye\ncruel\nworld"),
|
|
"goodbye\ncruel\nworld",
|
|
false,
|
|
},
|
|
|
|
{
|
|
fmt.Sprintf(`${chomp("%s")}`, "goodbye\r\nwindows\r\nworld"),
|
|
"goodbye\r\nwindows\r\nworld",
|
|
false,
|
|
},
|
|
|
|
{
|
|
fmt.Sprintf(`${chomp("%s")}`, "goodbye\ncruel\nworld\n"),
|
|
"goodbye\ncruel\nworld",
|
|
false,
|
|
},
|
|
|
|
{
|
|
fmt.Sprintf(`${chomp("%s")}`, "goodbye\ncruel\nworld\n\n\n\n"),
|
|
"goodbye\ncruel\nworld",
|
|
false,
|
|
},
|
|
|
|
{
|
|
fmt.Sprintf(`${chomp("%s")}`, "goodbye\r\nwindows\r\nworld\r\n"),
|
|
"goodbye\r\nwindows\r\nworld",
|
|
false,
|
|
},
|
|
|
|
{
|
|
fmt.Sprintf(`${chomp("%s")}`, "goodbye\r\nwindows\r\nworld\r\n\r\n\r\n\r\n"),
|
|
"goodbye\r\nwindows\r\nworld",
|
|
false,
|
|
},
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestInterpolateFuncMap(t *testing.T) {
|
|
testFunction(t, testFunctionConfig{
|
|
Cases: []testFunctionCase{
|
|
// empty input returns empty map
|
|
{
|
|
`${map()}`,
|
|
map[string]interface{}{},
|
|
false,
|
|
},
|
|
|
|
// odd args is error
|
|
{
|
|
`${map("odd")}`,
|
|
nil,
|
|
true,
|
|
},
|
|
|
|
// two args returns map w/ one k/v
|
|
{
|
|
`${map("hello", "world")}`,
|
|
map[string]interface{}{"hello": "world"},
|
|
false,
|
|
},
|
|
|
|
// four args get two k/v
|
|
{
|
|
`${map("hello", "world", "what's", "up?")}`,
|
|
map[string]interface{}{"hello": "world", "what's": "up?"},
|
|
false,
|
|
},
|
|
|
|
// map of lists is okay
|
|
{
|
|
`${map("hello", list("world"), "what's", list("up?"))}`,
|
|
map[string]interface{}{
|
|
"hello": []interface{}{"world"},
|
|
"what's": []interface{}{"up?"},
|
|
},
|
|
false,
|
|
},
|
|
|
|
// map of maps is okay
|
|
{
|
|
`${map("hello", map("there", "world"), "what's", map("really", "up?"))}`,
|
|
map[string]interface{}{
|
|
"hello": map[string]interface{}{"there": "world"},
|
|
"what's": map[string]interface{}{"really": "up?"},
|
|
},
|
|
false,
|
|
},
|
|
|
|
// keys have to be strings
|
|
{
|
|
`${map(list("listkey"), "val")}`,
|
|
nil,
|
|
true,
|
|
},
|
|
|
|
// types have to match
|
|
{
|
|
`${map("some", "strings", "also", list("lists"))}`,
|
|
nil,
|
|
true,
|
|
},
|
|
|
|
// duplicate keys are an error
|
|
{
|
|
`${map("key", "val", "key", "again")}`,
|
|
nil,
|
|
true,
|
|
},
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestInterpolateFuncCompact(t *testing.T) {
|
|
testFunction(t, testFunctionConfig{
|
|
Cases: []testFunctionCase{
|
|
// empty string within array
|
|
{
|
|
`${compact(split(",", "a,,b"))}`,
|
|
[]interface{}{"a", "b"},
|
|
false,
|
|
},
|
|
|
|
// empty string at the end of array
|
|
{
|
|
`${compact(split(",", "a,b,"))}`,
|
|
[]interface{}{"a", "b"},
|
|
false,
|
|
},
|
|
|
|
// single empty string
|
|
{
|
|
`${compact(split(",", ""))}`,
|
|
[]interface{}{},
|
|
false,
|
|
},
|
|
|
|
// errrors on list of lists
|
|
{
|
|
`${compact(list(list("a"), list("b")))}`,
|
|
nil,
|
|
true,
|
|
},
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestInterpolateFuncCidrHost(t *testing.T) {
|
|
testFunction(t, testFunctionConfig{
|
|
Cases: []testFunctionCase{
|
|
{
|
|
`${cidrhost("192.168.1.0/24", 5)}`,
|
|
"192.168.1.5",
|
|
false,
|
|
},
|
|
{
|
|
`${cidrhost("192.168.1.0/24", -5)}`,
|
|
"192.168.1.251",
|
|
false,
|
|
},
|
|
{
|
|
`${cidrhost("192.168.1.0/24", -256)}`,
|
|
"192.168.1.0",
|
|
false,
|
|
},
|
|
{
|
|
`${cidrhost("192.168.1.0/30", 255)}`,
|
|
nil,
|
|
true, // 255 doesn't fit in two bits
|
|
},
|
|
{
|
|
`${cidrhost("192.168.1.0/30", -255)}`,
|
|
nil,
|
|
true, // 255 doesn't fit in two bits
|
|
},
|
|
{
|
|
`${cidrhost("not-a-cidr", 6)}`,
|
|
nil,
|
|
true, // not a valid CIDR mask
|
|
},
|
|
{
|
|
`${cidrhost("10.256.0.0/8", 6)}`,
|
|
nil,
|
|
true, // can't have an octet >255
|
|
},
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestInterpolateFuncCidrNetmask(t *testing.T) {
|
|
testFunction(t, testFunctionConfig{
|
|
Cases: []testFunctionCase{
|
|
{
|
|
`${cidrnetmask("192.168.1.0/24")}`,
|
|
"255.255.255.0",
|
|
false,
|
|
},
|
|
{
|
|
`${cidrnetmask("192.168.1.0/32")}`,
|
|
"255.255.255.255",
|
|
false,
|
|
},
|
|
{
|
|
`${cidrnetmask("0.0.0.0/0")}`,
|
|
"0.0.0.0",
|
|
false,
|
|
},
|
|
{
|
|
// This doesn't really make sense for IPv6 networks
|
|
// but it ought to do something sensible anyway.
|
|
`${cidrnetmask("1::/64")}`,
|
|
"ffff:ffff:ffff:ffff::",
|
|
false,
|
|
},
|
|
{
|
|
`${cidrnetmask("not-a-cidr")}`,
|
|
nil,
|
|
true, // not a valid CIDR mask
|
|
},
|
|
{
|
|
`${cidrnetmask("10.256.0.0/8")}`,
|
|
nil,
|
|
true, // can't have an octet >255
|
|
},
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestInterpolateFuncCidrSubnet(t *testing.T) {
|
|
testFunction(t, testFunctionConfig{
|
|
Cases: []testFunctionCase{
|
|
{
|
|
`${cidrsubnet("192.168.2.0/20", 4, 6)}`,
|
|
"192.168.6.0/24",
|
|
false,
|
|
},
|
|
{
|
|
`${cidrsubnet("fe80::/48", 16, 6)}`,
|
|
"fe80:0:0:6::/64",
|
|
false,
|
|
},
|
|
{
|
|
// IPv4 address encoded in IPv6 syntax gets normalized
|
|
`${cidrsubnet("::ffff:192.168.0.0/112", 8, 6)}`,
|
|
"192.168.6.0/24",
|
|
false,
|
|
},
|
|
{
|
|
`${cidrsubnet("192.168.0.0/30", 4, 6)}`,
|
|
nil,
|
|
true, // not enough bits left
|
|
},
|
|
{
|
|
`${cidrsubnet("192.168.0.0/16", 2, 16)}`,
|
|
nil,
|
|
true, // can't encode 16 in 2 bits
|
|
},
|
|
{
|
|
`${cidrsubnet("not-a-cidr", 4, 6)}`,
|
|
nil,
|
|
true, // not a valid CIDR mask
|
|
},
|
|
{
|
|
`${cidrsubnet("10.256.0.0/8", 4, 6)}`,
|
|
nil,
|
|
true, // can't have an octet >255
|
|
},
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestInterpolateFuncCoalesce(t *testing.T) {
|
|
testFunction(t, testFunctionConfig{
|
|
Cases: []testFunctionCase{
|
|
{
|
|
`${coalesce("first", "second", "third")}`,
|
|
"first",
|
|
false,
|
|
},
|
|
{
|
|
`${coalesce("", "second", "third")}`,
|
|
"second",
|
|
false,
|
|
},
|
|
{
|
|
`${coalesce("", "", "")}`,
|
|
"",
|
|
false,
|
|
},
|
|
{
|
|
`${coalesce("foo")}`,
|
|
nil,
|
|
true,
|
|
},
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestInterpolateFuncCoalesceList(t *testing.T) {
|
|
testFunction(t, testFunctionConfig{
|
|
Cases: []testFunctionCase{
|
|
{
|
|
`${coalescelist(list("first"), list("second"), list("third"))}`,
|
|
[]interface{}{"first"},
|
|
false,
|
|
},
|
|
{
|
|
`${coalescelist(list(), list("second"), list("third"))}`,
|
|
[]interface{}{"second"},
|
|
false,
|
|
},
|
|
{
|
|
`${coalescelist(list(), list(), list())}`,
|
|
[]interface{}{},
|
|
false,
|
|
},
|
|
{
|
|
`${coalescelist(list("foo"))}`,
|
|
nil,
|
|
true,
|
|
},
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestInterpolateFuncConcat(t *testing.T) {
|
|
testFunction(t, testFunctionConfig{
|
|
Cases: []testFunctionCase{
|
|
// String + list
|
|
// no longer supported, now returns an error
|
|
{
|
|
`${concat("a", split(",", "b,c"))}`,
|
|
nil,
|
|
true,
|
|
},
|
|
|
|
// List + string
|
|
// no longer supported, now returns an error
|
|
{
|
|
`${concat(split(",", "a,b"), "c")}`,
|
|
nil,
|
|
true,
|
|
},
|
|
|
|
// Single list
|
|
{
|
|
`${concat(split(",", ",foo,"))}`,
|
|
[]interface{}{"", "foo", ""},
|
|
false,
|
|
},
|
|
{
|
|
`${concat(split(",", "a,b,c"))}`,
|
|
[]interface{}{"a", "b", "c"},
|
|
false,
|
|
},
|
|
|
|
// Two lists
|
|
{
|
|
`${concat(split(",", "a,b,c"), split(",", "d,e"))}`,
|
|
[]interface{}{"a", "b", "c", "d", "e"},
|
|
false,
|
|
},
|
|
// Two lists with different separators
|
|
{
|
|
`${concat(split(",", "a,b,c"), split(" ", "d e"))}`,
|
|
[]interface{}{"a", "b", "c", "d", "e"},
|
|
false,
|
|
},
|
|
|
|
// More lists
|
|
{
|
|
`${concat(split(",", "a,b"), split(",", "c,d"), split(",", "e,f"), split(",", "0,1"))}`,
|
|
[]interface{}{"a", "b", "c", "d", "e", "f", "0", "1"},
|
|
false,
|
|
},
|
|
|
|
// list vars
|
|
{
|
|
`${concat("${var.list}", "${var.list}")}`,
|
|
[]interface{}{"a", "b", "a", "b"},
|
|
false,
|
|
},
|
|
// lists of lists
|
|
{
|
|
`${concat("${var.lists}", "${var.lists}")}`,
|
|
[]interface{}{[]interface{}{"c", "d"}, []interface{}{"c", "d"}},
|
|
false,
|
|
},
|
|
|
|
// lists of maps
|
|
{
|
|
`${concat("${var.maps}", "${var.maps}")}`,
|
|
[]interface{}{map[string]interface{}{"key1": "a", "key2": "b"}, map[string]interface{}{"key1": "a", "key2": "b"}},
|
|
false,
|
|
},
|
|
|
|
// multiple strings
|
|
// no longer supported, now returns an error
|
|
{
|
|
`${concat("string1", "string2")}`,
|
|
nil,
|
|
true,
|
|
},
|
|
|
|
// mismatched types
|
|
{
|
|
`${concat("${var.lists}", "${var.maps}")}`,
|
|
nil,
|
|
true,
|
|
},
|
|
},
|
|
Vars: map[string]ast.Variable{
|
|
"var.list": {
|
|
Type: ast.TypeList,
|
|
Value: []ast.Variable{
|
|
{
|
|
Type: ast.TypeString,
|
|
Value: "a",
|
|
},
|
|
{
|
|
Type: ast.TypeString,
|
|
Value: "b",
|
|
},
|
|
},
|
|
},
|
|
"var.lists": {
|
|
Type: ast.TypeList,
|
|
Value: []ast.Variable{
|
|
{
|
|
Type: ast.TypeList,
|
|
Value: []ast.Variable{
|
|
{
|
|
Type: ast.TypeString,
|
|
Value: "c",
|
|
},
|
|
{
|
|
Type: ast.TypeString,
|
|
Value: "d",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
"var.maps": {
|
|
Type: ast.TypeList,
|
|
Value: []ast.Variable{
|
|
{
|
|
Type: ast.TypeMap,
|
|
Value: map[string]ast.Variable{
|
|
"key1": {
|
|
Type: ast.TypeString,
|
|
Value: "a",
|
|
},
|
|
"key2": {
|
|
Type: ast.TypeString,
|
|
Value: "b",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestInterpolateFuncContains(t *testing.T) {
|
|
testFunction(t, testFunctionConfig{
|
|
Vars: map[string]ast.Variable{
|
|
"var.listOfStrings": interfaceToVariableSwallowError([]string{"notfoo", "stillnotfoo", "bar"}),
|
|
"var.listOfInts": interfaceToVariableSwallowError([]int{1, 2, 3}),
|
|
},
|
|
Cases: []testFunctionCase{
|
|
{
|
|
`${contains(var.listOfStrings, "bar")}`,
|
|
"true",
|
|
false,
|
|
},
|
|
|
|
{
|
|
`${contains(var.listOfStrings, "foo")}`,
|
|
"false",
|
|
false,
|
|
},
|
|
{
|
|
`${contains(var.listOfInts, 1)}`,
|
|
"true",
|
|
false,
|
|
},
|
|
{
|
|
`${contains(var.listOfInts, 10)}`,
|
|
"false",
|
|
false,
|
|
},
|
|
{
|
|
`${contains(var.listOfInts, "2")}`,
|
|
"true",
|
|
false,
|
|
},
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestInterpolateFuncMerge(t *testing.T) {
|
|
testFunction(t, testFunctionConfig{
|
|
Cases: []testFunctionCase{
|
|
// basic merge
|
|
{
|
|
`${merge(map("a", "b"), map("c", "d"))}`,
|
|
map[string]interface{}{"a": "b", "c": "d"},
|
|
false,
|
|
},
|
|
|
|
// merge with conflicts is ok, last in wins.
|
|
{
|
|
`${merge(map("a", "b", "c", "X"), map("c", "d"))}`,
|
|
map[string]interface{}{"a": "b", "c": "d"},
|
|
false,
|
|
},
|
|
|
|
// merge variadic
|
|
{
|
|
`${merge(map("a", "b"), map("c", "d"), map("e", "f"))}`,
|
|
map[string]interface{}{"a": "b", "c": "d", "e": "f"},
|
|
false,
|
|
},
|
|
|
|
// merge with variables
|
|
{
|
|
`${merge(var.maps[0], map("c", "d"))}`,
|
|
map[string]interface{}{"key1": "a", "key2": "b", "c": "d"},
|
|
false,
|
|
},
|
|
|
|
// only accept maps
|
|
{
|
|
`${merge(map("a", "b"), list("c", "d"))}`,
|
|
nil,
|
|
true,
|
|
},
|
|
|
|
// merge maps of maps
|
|
{
|
|
`${merge(map("a", var.maps[0]), map("b", var.maps[1]))}`,
|
|
map[string]interface{}{
|
|
"b": map[string]interface{}{"key3": "d", "key4": "c"},
|
|
"a": map[string]interface{}{"key1": "a", "key2": "b"},
|
|
},
|
|
false,
|
|
},
|
|
// merge maps of lists
|
|
{
|
|
`${merge(map("a", list("b")), map("c", list("d", "e")))}`,
|
|
map[string]interface{}{"a": []interface{}{"b"}, "c": []interface{}{"d", "e"}},
|
|
false,
|
|
},
|
|
// merge map of various kinds
|
|
{
|
|
`${merge(map("a", var.maps[0]), map("b", list("c", "d")))}`,
|
|
map[string]interface{}{"a": map[string]interface{}{"key1": "a", "key2": "b"}, "b": []interface{}{"c", "d"}},
|
|
false,
|
|
},
|
|
},
|
|
Vars: map[string]ast.Variable{
|
|
"var.maps": {
|
|
Type: ast.TypeList,
|
|
Value: []ast.Variable{
|
|
{
|
|
Type: ast.TypeMap,
|
|
Value: map[string]ast.Variable{
|
|
"key1": {
|
|
Type: ast.TypeString,
|
|
Value: "a",
|
|
},
|
|
"key2": {
|
|
Type: ast.TypeString,
|
|
Value: "b",
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Type: ast.TypeMap,
|
|
Value: map[string]ast.Variable{
|
|
"key3": {
|
|
Type: ast.TypeString,
|
|
Value: "d",
|
|
},
|
|
"key4": {
|
|
Type: ast.TypeString,
|
|
Value: "c",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
})
|
|
|
|
}
|
|
|
|
func TestInterpolateFuncDirname(t *testing.T) {
|
|
testFunction(t, testFunctionConfig{
|
|
Cases: []testFunctionCase{
|
|
{
|
|
`${dirname("/foo/bar/baz")}`,
|
|
"/foo/bar",
|
|
false,
|
|
},
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestInterpolateFuncDistinct(t *testing.T) {
|
|
testFunction(t, testFunctionConfig{
|
|
Cases: []testFunctionCase{
|
|
// 3 duplicates
|
|
{
|
|
`${distinct(concat(split(",", "user1,user2,user3"), split(",", "user1,user2,user3")))}`,
|
|
[]interface{}{"user1", "user2", "user3"},
|
|
false,
|
|
},
|
|
// 1 duplicate
|
|
{
|
|
`${distinct(concat(split(",", "user1,user2,user3"), split(",", "user1,user4")))}`,
|
|
[]interface{}{"user1", "user2", "user3", "user4"},
|
|
false,
|
|
},
|
|
// too many args
|
|
{
|
|
`${distinct(concat(split(",", "user1,user2,user3"), split(",", "user1,user4")), "foo")}`,
|
|
nil,
|
|
true,
|
|
},
|
|
// non-flat list is an error
|
|
{
|
|
`${distinct(list(list("a"), list("a")))}`,
|
|
nil,
|
|
true,
|
|
},
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestInterpolateFuncMatchKeys(t *testing.T) {
|
|
testFunction(t, testFunctionConfig{
|
|
Cases: []testFunctionCase{
|
|
// normal usage
|
|
{
|
|
`${matchkeys(list("a", "b", "c"), list("ref1", "ref2", "ref3"), list("ref2"))}`,
|
|
[]interface{}{"b"},
|
|
false,
|
|
},
|
|
// normal usage 2, check the order
|
|
{
|
|
`${matchkeys(list("a", "b", "c"), list("ref1", "ref2", "ref3"), list("ref2", "ref1"))}`,
|
|
[]interface{}{"a", "b"},
|
|
false,
|
|
},
|
|
// duplicate item in searchset
|
|
{
|
|
`${matchkeys(list("a", "b", "c"), list("ref1", "ref2", "ref3"), list("ref2", "ref2"))}`,
|
|
[]interface{}{"b"},
|
|
false,
|
|
},
|
|
// no matches
|
|
{
|
|
`${matchkeys(list("a", "b", "c"), list("ref1", "ref2", "ref3"), list("ref4"))}`,
|
|
[]interface{}{},
|
|
false,
|
|
},
|
|
// no matches 2
|
|
{
|
|
`${matchkeys(list("a", "b", "c"), list("ref1", "ref2", "ref3"), list())}`,
|
|
[]interface{}{},
|
|
false,
|
|
},
|
|
// zero case
|
|
{
|
|
`${matchkeys(list(), list(), list("nope"))}`,
|
|
[]interface{}{},
|
|
false,
|
|
},
|
|
// complex values
|
|
{
|
|
`${matchkeys(list(list("a", "a")), list("a"), list("a"))}`,
|
|
[]interface{}{[]interface{}{"a", "a"}},
|
|
false,
|
|
},
|
|
// errors
|
|
// different types
|
|
{
|
|
`${matchkeys(list("a"), list(1), list("a"))}`,
|
|
nil,
|
|
true,
|
|
},
|
|
// different types
|
|
{
|
|
`${matchkeys(list("a"), list(list("a"), list("a")), list("a"))}`,
|
|
nil,
|
|
true,
|
|
},
|
|
// lists of different length is an error
|
|
{
|
|
`${matchkeys(list("a"), list("a", "b"), list("a"))}`,
|
|
nil,
|
|
true,
|
|
},
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestInterpolateFuncFile(t *testing.T) {
|
|
tf, err := ioutil.TempFile("", "tf")
|
|
if err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
path := tf.Name()
|
|
tf.Write([]byte("foo"))
|
|
tf.Close()
|
|
defer os.Remove(path)
|
|
|
|
testFunction(t, testFunctionConfig{
|
|
Cases: []testFunctionCase{
|
|
{
|
|
fmt.Sprintf(`${file("%s")}`, path),
|
|
"foo",
|
|
false,
|
|
},
|
|
|
|
// Invalid path
|
|
{
|
|
`${file("/i/dont/exist")}`,
|
|
nil,
|
|
true,
|
|
},
|
|
|
|
// Too many args
|
|
{
|
|
`${file("foo", "bar")}`,
|
|
nil,
|
|
true,
|
|
},
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestInterpolateFuncFormat(t *testing.T) {
|
|
testFunction(t, testFunctionConfig{
|
|
Cases: []testFunctionCase{
|
|
{
|
|
`${format("hello")}`,
|
|
"hello",
|
|
false,
|
|
},
|
|
|
|
{
|
|
`${format("hello %s", "world")}`,
|
|
"hello world",
|
|
false,
|
|
},
|
|
|
|
{
|
|
`${format("hello %d", 42)}`,
|
|
"hello 42",
|
|
false,
|
|
},
|
|
|
|
{
|
|
`${format("hello %05d", 42)}`,
|
|
"hello 00042",
|
|
false,
|
|
},
|
|
|
|
{
|
|
`${format("hello %05d", 12345)}`,
|
|
"hello 12345",
|
|
false,
|
|
},
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestInterpolateFuncFormatList(t *testing.T) {
|
|
testFunction(t, testFunctionConfig{
|
|
Cases: []testFunctionCase{
|
|
// formatlist requires at least one list
|
|
{
|
|
`${formatlist("hello")}`,
|
|
nil,
|
|
true,
|
|
},
|
|
{
|
|
`${formatlist("hello %s", "world")}`,
|
|
nil,
|
|
true,
|
|
},
|
|
// formatlist applies to each list element in turn
|
|
{
|
|
`${formatlist("<%s>", split(",", "A,B"))}`,
|
|
[]interface{}{"<A>", "<B>"},
|
|
false,
|
|
},
|
|
// formatlist repeats scalar elements
|
|
{
|
|
`${join(", ", formatlist("%s=%s", "x", split(",", "A,B,C")))}`,
|
|
"x=A, x=B, x=C",
|
|
false,
|
|
},
|
|
// Multiple lists are walked in parallel
|
|
{
|
|
`${join(", ", formatlist("%s=%s", split(",", "A,B,C"), split(",", "1,2,3")))}`,
|
|
"A=1, B=2, C=3",
|
|
false,
|
|
},
|
|
// Mismatched list lengths generate an error
|
|
{
|
|
`${formatlist("%s=%2s", split(",", "A,B,C,D"), split(",", "1,2,3"))}`,
|
|
nil,
|
|
true,
|
|
},
|
|
// Works with lists of length 1 [GH-2240]
|
|
{
|
|
`${formatlist("%s.id", split(",", "demo-rest-elb"))}`,
|
|
[]interface{}{"demo-rest-elb.id"},
|
|
false,
|
|
},
|
|
// Works with empty lists [GH-7607]
|
|
{
|
|
`${formatlist("%s", var.emptylist)}`,
|
|
[]interface{}{},
|
|
false,
|
|
},
|
|
},
|
|
Vars: map[string]ast.Variable{
|
|
"var.emptylist": {
|
|
Type: ast.TypeList,
|
|
Value: []ast.Variable{},
|
|
},
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestInterpolateFuncIndex(t *testing.T) {
|
|
testFunction(t, testFunctionConfig{
|
|
Vars: map[string]ast.Variable{
|
|
"var.list1": interfaceToVariableSwallowError([]string{"notfoo", "stillnotfoo", "bar"}),
|
|
"var.list2": interfaceToVariableSwallowError([]string{"foo"}),
|
|
"var.list3": interfaceToVariableSwallowError([]string{"foo", "spam", "bar", "eggs"}),
|
|
},
|
|
Cases: []testFunctionCase{
|
|
{
|
|
`${index("test", "")}`,
|
|
nil,
|
|
true,
|
|
},
|
|
|
|
{
|
|
`${index(var.list1, "foo")}`,
|
|
nil,
|
|
true,
|
|
},
|
|
|
|
{
|
|
`${index(var.list2, "foo")}`,
|
|
"0",
|
|
false,
|
|
},
|
|
|
|
{
|
|
`${index(var.list3, "bar")}`,
|
|
"2",
|
|
false,
|
|
},
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestInterpolateFuncIndent(t *testing.T) {
|
|
testFunction(t, testFunctionConfig{
|
|
Cases: []testFunctionCase{
|
|
{
|
|
`${indent(4, "Fleas:
|
|
Adam
|
|
Had'em
|
|
|
|
E.E. Cummings")}`,
|
|
"Fleas:\n Adam\n Had'em\n \n E.E. Cummings",
|
|
false,
|
|
},
|
|
{
|
|
`${indent(4, "oneliner")}`,
|
|
"oneliner",
|
|
false,
|
|
},
|
|
{
|
|
`${indent(4, "#!/usr/bin/env bash
|
|
date
|
|
pwd")}`,
|
|
"#!/usr/bin/env bash\n date\n pwd",
|
|
false,
|
|
},
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestInterpolateFuncJoin(t *testing.T) {
|
|
testFunction(t, testFunctionConfig{
|
|
Vars: map[string]ast.Variable{
|
|
"var.a_list": interfaceToVariableSwallowError([]string{"foo"}),
|
|
"var.a_longer_list": interfaceToVariableSwallowError([]string{"foo", "bar", "baz"}),
|
|
"var.list_of_lists": interfaceToVariableSwallowError([]interface{}{[]string{"foo"}, []string{"bar"}, []string{"baz"}}),
|
|
},
|
|
Cases: []testFunctionCase{
|
|
{
|
|
`${join(",")}`,
|
|
nil,
|
|
true,
|
|
},
|
|
|
|
{
|
|
`${join(",", var.a_list)}`,
|
|
"foo",
|
|
false,
|
|
},
|
|
|
|
{
|
|
`${join(".", var.a_longer_list)}`,
|
|
"foo.bar.baz",
|
|
false,
|
|
},
|
|
|
|
{
|
|
`${join(".", var.list_of_lists)}`,
|
|
nil,
|
|
true,
|
|
},
|
|
{
|
|
`${join(".", list(list("nested")))}`,
|
|
nil,
|
|
true,
|
|
},
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestInterpolateFuncJSONEncode(t *testing.T) {
|
|
testFunction(t, testFunctionConfig{
|
|
Vars: map[string]ast.Variable{
|
|
"easy": ast.Variable{
|
|
Value: "test",
|
|
Type: ast.TypeString,
|
|
},
|
|
"hard": ast.Variable{
|
|
Value: " foo \\ \n \t \" bar ",
|
|
Type: ast.TypeString,
|
|
},
|
|
"list": interfaceToVariableSwallowError([]string{"foo", "bar\tbaz"}),
|
|
"emptylist": ast.Variable{
|
|
Value: []ast.Variable{},
|
|
Type: ast.TypeList,
|
|
},
|
|
"map": interfaceToVariableSwallowError(map[string]string{
|
|
"foo": "bar",
|
|
"ba \n z": "q\\x",
|
|
}),
|
|
"emptymap": interfaceToVariableSwallowError(map[string]string{}),
|
|
"nestedlist": interfaceToVariableSwallowError([][]string{{"foo"}}),
|
|
"nestedmap": interfaceToVariableSwallowError(map[string][]string{"foo": {"bar"}}),
|
|
},
|
|
Cases: []testFunctionCase{
|
|
{
|
|
`${jsonencode("test")}`,
|
|
`"test"`,
|
|
false,
|
|
},
|
|
{
|
|
`${jsonencode(easy)}`,
|
|
`"test"`,
|
|
false,
|
|
},
|
|
{
|
|
`${jsonencode(hard)}`,
|
|
`" foo \\ \n \t \" bar "`,
|
|
false,
|
|
},
|
|
{
|
|
`${jsonencode("")}`,
|
|
`""`,
|
|
false,
|
|
},
|
|
{
|
|
`${jsonencode()}`,
|
|
nil,
|
|
true,
|
|
},
|
|
{
|
|
`${jsonencode(list)}`,
|
|
`["foo","bar\tbaz"]`,
|
|
false,
|
|
},
|
|
{
|
|
`${jsonencode(emptylist)}`,
|
|
`[]`,
|
|
false,
|
|
},
|
|
{
|
|
`${jsonencode(map)}`,
|
|
`{"ba \n z":"q\\x","foo":"bar"}`,
|
|
false,
|
|
},
|
|
{
|
|
`${jsonencode(emptymap)}`,
|
|
`{}`,
|
|
false,
|
|
},
|
|
{
|
|
`${jsonencode(nestedlist)}`,
|
|
`[["foo"]]`,
|
|
false,
|
|
},
|
|
{
|
|
`${jsonencode(nestedmap)}`,
|
|
`{"foo":["bar"]}`,
|
|
false,
|
|
},
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestInterpolateFuncReplace(t *testing.T) {
|
|
testFunction(t, testFunctionConfig{
|
|
Cases: []testFunctionCase{
|
|
// Regular search and replace
|
|
{
|
|
`${replace("hello", "hel", "bel")}`,
|
|
"bello",
|
|
false,
|
|
},
|
|
|
|
// Search string doesn't match
|
|
{
|
|
`${replace("hello", "nope", "bel")}`,
|
|
"hello",
|
|
false,
|
|
},
|
|
|
|
// Regular expression
|
|
{
|
|
`${replace("hello", "/l/", "L")}`,
|
|
"heLLo",
|
|
false,
|
|
},
|
|
|
|
{
|
|
`${replace("helo", "/(l)/", "$1$1")}`,
|
|
"hello",
|
|
false,
|
|
},
|
|
|
|
// Bad regexp
|
|
{
|
|
`${replace("helo", "/(l/", "$1$1")}`,
|
|
nil,
|
|
true,
|
|
},
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestInterpolateFuncLength(t *testing.T) {
|
|
testFunction(t, testFunctionConfig{
|
|
Cases: []testFunctionCase{
|
|
// Raw strings
|
|
{
|
|
`${length("")}`,
|
|
"0",
|
|
false,
|
|
},
|
|
{
|
|
`${length("a")}`,
|
|
"1",
|
|
false,
|
|
},
|
|
{
|
|
`${length(" ")}`,
|
|
"1",
|
|
false,
|
|
},
|
|
{
|
|
`${length(" a ,")}`,
|
|
"4",
|
|
false,
|
|
},
|
|
{
|
|
`${length("aaa")}`,
|
|
"3",
|
|
false,
|
|
},
|
|
|
|
// Lists
|
|
{
|
|
`${length(split(",", "a"))}`,
|
|
"1",
|
|
false,
|
|
},
|
|
{
|
|
`${length(split(",", "foo,"))}`,
|
|
"2",
|
|
false,
|
|
},
|
|
{
|
|
`${length(split(",", ",foo,"))}`,
|
|
"3",
|
|
false,
|
|
},
|
|
{
|
|
`${length(split(",", "foo,bar"))}`,
|
|
"2",
|
|
false,
|
|
},
|
|
{
|
|
`${length(split(".", "one.two.three.four.five"))}`,
|
|
"5",
|
|
false,
|
|
},
|
|
// Want length 0 if we split an empty string then compact
|
|
{
|
|
`${length(compact(split(",", "")))}`,
|
|
"0",
|
|
false,
|
|
},
|
|
// Works for maps
|
|
{
|
|
`${length(map("k", "v"))}`,
|
|
"1",
|
|
false,
|
|
},
|
|
{
|
|
`${length(map("k1", "v1", "k2", "v2"))}`,
|
|
"2",
|
|
false,
|
|
},
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestInterpolateFuncSignum(t *testing.T) {
|
|
testFunction(t, testFunctionConfig{
|
|
Cases: []testFunctionCase{
|
|
{
|
|
`${signum()}`,
|
|
nil,
|
|
true,
|
|
},
|
|
|
|
{
|
|
`${signum("")}`,
|
|
nil,
|
|
true,
|
|
},
|
|
|
|
{
|
|
`${signum(0)}`,
|
|
"0",
|
|
false,
|
|
},
|
|
|
|
{
|
|
`${signum(15)}`,
|
|
"1",
|
|
false,
|
|
},
|
|
|
|
{
|
|
`${signum(-29)}`,
|
|
"-1",
|
|
false,
|
|
},
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestInterpolateFuncSlice(t *testing.T) {
|
|
testFunction(t, testFunctionConfig{
|
|
Cases: []testFunctionCase{
|
|
// Negative from index
|
|
{
|
|
`${slice(list("a"), -1, 0)}`,
|
|
nil,
|
|
true,
|
|
},
|
|
// From index > to index
|
|
{
|
|
`${slice(list("a", "b", "c"), 2, 1)}`,
|
|
nil,
|
|
true,
|
|
},
|
|
// To index too large
|
|
{
|
|
`${slice(var.list_of_strings, 1, 4)}`,
|
|
nil,
|
|
true,
|
|
},
|
|
// Empty slice
|
|
{
|
|
`${slice(var.list_of_strings, 1, 1)}`,
|
|
[]interface{}{},
|
|
false,
|
|
},
|
|
{
|
|
`${slice(var.list_of_strings, 1, 2)}`,
|
|
[]interface{}{"b"},
|
|
false,
|
|
},
|
|
{
|
|
`${slice(var.list_of_strings, 0, length(var.list_of_strings) - 1)}`,
|
|
[]interface{}{"a", "b"},
|
|
false,
|
|
},
|
|
},
|
|
Vars: map[string]ast.Variable{
|
|
"var.list_of_strings": {
|
|
Type: ast.TypeList,
|
|
Value: []ast.Variable{
|
|
{
|
|
Type: ast.TypeString,
|
|
Value: "a",
|
|
},
|
|
{
|
|
Type: ast.TypeString,
|
|
Value: "b",
|
|
},
|
|
{
|
|
Type: ast.TypeString,
|
|
Value: "c",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestInterpolateFuncSort(t *testing.T) {
|
|
testFunction(t, testFunctionConfig{
|
|
Vars: map[string]ast.Variable{
|
|
"var.strings": ast.Variable{
|
|
Type: ast.TypeList,
|
|
Value: []ast.Variable{
|
|
{Type: ast.TypeString, Value: "c"},
|
|
{Type: ast.TypeString, Value: "a"},
|
|
{Type: ast.TypeString, Value: "b"},
|
|
},
|
|
},
|
|
"var.notstrings": ast.Variable{
|
|
Type: ast.TypeList,
|
|
Value: []ast.Variable{
|
|
{Type: ast.TypeList, Value: []ast.Variable{}},
|
|
{Type: ast.TypeString, Value: "b"},
|
|
},
|
|
},
|
|
},
|
|
Cases: []testFunctionCase{
|
|
{
|
|
`${sort(var.strings)}`,
|
|
[]interface{}{"a", "b", "c"},
|
|
false,
|
|
},
|
|
{
|
|
`${sort(var.notstrings)}`,
|
|
nil,
|
|
true,
|
|
},
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestInterpolateFuncSplit(t *testing.T) {
|
|
testFunction(t, testFunctionConfig{
|
|
Cases: []testFunctionCase{
|
|
{
|
|
`${split(",")}`,
|
|
nil,
|
|
true,
|
|
},
|
|
|
|
{
|
|
`${split(",", "")}`,
|
|
[]interface{}{""},
|
|
false,
|
|
},
|
|
|
|
{
|
|
`${split(",", "foo")}`,
|
|
[]interface{}{"foo"},
|
|
false,
|
|
},
|
|
|
|
{
|
|
`${split(",", ",,,")}`,
|
|
[]interface{}{"", "", "", ""},
|
|
false,
|
|
},
|
|
|
|
{
|
|
`${split(",", "foo,")}`,
|
|
[]interface{}{"foo", ""},
|
|
false,
|
|
},
|
|
|
|
{
|
|
`${split(",", ",foo,")}`,
|
|
[]interface{}{"", "foo", ""},
|
|
false,
|
|
},
|
|
|
|
{
|
|
`${split(".", "foo.bar.baz")}`,
|
|
[]interface{}{"foo", "bar", "baz"},
|
|
false,
|
|
},
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestInterpolateFuncLookup(t *testing.T) {
|
|
testFunction(t, testFunctionConfig{
|
|
Vars: map[string]ast.Variable{
|
|
"var.foo": {
|
|
Type: ast.TypeMap,
|
|
Value: map[string]ast.Variable{
|
|
"bar": {
|
|
Type: ast.TypeString,
|
|
Value: "baz",
|
|
},
|
|
},
|
|
},
|
|
"var.map_of_lists": ast.Variable{
|
|
Type: ast.TypeMap,
|
|
Value: map[string]ast.Variable{
|
|
"bar": {
|
|
Type: ast.TypeList,
|
|
Value: []ast.Variable{
|
|
{
|
|
Type: ast.TypeString,
|
|
Value: "baz",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
Cases: []testFunctionCase{
|
|
{
|
|
`${lookup(var.foo, "bar")}`,
|
|
"baz",
|
|
false,
|
|
},
|
|
|
|
// Invalid key
|
|
{
|
|
`${lookup(var.foo, "baz")}`,
|
|
nil,
|
|
true,
|
|
},
|
|
|
|
// Supplied default with valid key
|
|
{
|
|
`${lookup(var.foo, "bar", "")}`,
|
|
"baz",
|
|
false,
|
|
},
|
|
|
|
// Supplied default with invalid key
|
|
{
|
|
`${lookup(var.foo, "zip", "")}`,
|
|
"",
|
|
false,
|
|
},
|
|
|
|
// Too many args
|
|
{
|
|
`${lookup(var.foo, "bar", "", "abc")}`,
|
|
nil,
|
|
true,
|
|
},
|
|
|
|
// Cannot lookup into map of lists
|
|
{
|
|
`${lookup(var.map_of_lists, "bar")}`,
|
|
nil,
|
|
true,
|
|
},
|
|
|
|
// Non-empty default
|
|
{
|
|
`${lookup(var.foo, "zap", "xyz")}`,
|
|
"xyz",
|
|
false,
|
|
},
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestInterpolateFuncKeys(t *testing.T) {
|
|
testFunction(t, testFunctionConfig{
|
|
Vars: map[string]ast.Variable{
|
|
"var.foo": ast.Variable{
|
|
Type: ast.TypeMap,
|
|
Value: map[string]ast.Variable{
|
|
"bar": ast.Variable{
|
|
Value: "baz",
|
|
Type: ast.TypeString,
|
|
},
|
|
"qux": ast.Variable{
|
|
Value: "quack",
|
|
Type: ast.TypeString,
|
|
},
|
|
},
|
|
},
|
|
"var.str": ast.Variable{
|
|
Value: "astring",
|
|
Type: ast.TypeString,
|
|
},
|
|
},
|
|
Cases: []testFunctionCase{
|
|
{
|
|
`${keys(var.foo)}`,
|
|
[]interface{}{"bar", "qux"},
|
|
false,
|
|
},
|
|
|
|
// Invalid key
|
|
{
|
|
`${keys(var.not)}`,
|
|
nil,
|
|
true,
|
|
},
|
|
|
|
// Too many args
|
|
{
|
|
`${keys(var.foo, "bar")}`,
|
|
nil,
|
|
true,
|
|
},
|
|
|
|
// Not a map
|
|
{
|
|
`${keys(var.str)}`,
|
|
nil,
|
|
true,
|
|
},
|
|
},
|
|
})
|
|
}
|
|
|
|
// Confirm that keys return in sorted order, and values return in the order of
|
|
// their sorted keys.
|
|
func TestInterpolateFuncKeyValOrder(t *testing.T) {
|
|
testFunction(t, testFunctionConfig{
|
|
Vars: map[string]ast.Variable{
|
|
"var.foo": ast.Variable{
|
|
Type: ast.TypeMap,
|
|
Value: map[string]ast.Variable{
|
|
"D": ast.Variable{
|
|
Value: "2",
|
|
Type: ast.TypeString,
|
|
},
|
|
"C": ast.Variable{
|
|
Value: "Y",
|
|
Type: ast.TypeString,
|
|
},
|
|
"A": ast.Variable{
|
|
Value: "X",
|
|
Type: ast.TypeString,
|
|
},
|
|
"10": ast.Variable{
|
|
Value: "Z",
|
|
Type: ast.TypeString,
|
|
},
|
|
"1": ast.Variable{
|
|
Value: "4",
|
|
Type: ast.TypeString,
|
|
},
|
|
"3": ast.Variable{
|
|
Value: "W",
|
|
Type: ast.TypeString,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
Cases: []testFunctionCase{
|
|
{
|
|
`${keys(var.foo)}`,
|
|
[]interface{}{"1", "10", "3", "A", "C", "D"},
|
|
false,
|
|
},
|
|
|
|
{
|
|
`${values(var.foo)}`,
|
|
[]interface{}{"4", "Z", "W", "X", "Y", "2"},
|
|
false,
|
|
},
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestInterpolateFuncValues(t *testing.T) {
|
|
testFunction(t, testFunctionConfig{
|
|
Vars: map[string]ast.Variable{
|
|
"var.foo": ast.Variable{
|
|
Type: ast.TypeMap,
|
|
Value: map[string]ast.Variable{
|
|
"bar": ast.Variable{
|
|
Value: "quack",
|
|
Type: ast.TypeString,
|
|
},
|
|
"qux": ast.Variable{
|
|
Value: "baz",
|
|
Type: ast.TypeString,
|
|
},
|
|
},
|
|
},
|
|
"var.str": ast.Variable{
|
|
Value: "astring",
|
|
Type: ast.TypeString,
|
|
},
|
|
},
|
|
Cases: []testFunctionCase{
|
|
{
|
|
`${values(var.foo)}`,
|
|
[]interface{}{"quack", "baz"},
|
|
false,
|
|
},
|
|
|
|
// Invalid key
|
|
{
|
|
`${values(var.not)}`,
|
|
nil,
|
|
true,
|
|
},
|
|
|
|
// Too many args
|
|
{
|
|
`${values(var.foo, "bar")}`,
|
|
nil,
|
|
true,
|
|
},
|
|
|
|
// Not a map
|
|
{
|
|
`${values(var.str)}`,
|
|
nil,
|
|
true,
|
|
},
|
|
|
|
// Map of lists
|
|
{
|
|
`${values(map("one", list()))}`,
|
|
nil,
|
|
true,
|
|
},
|
|
},
|
|
})
|
|
}
|
|
|
|
func interfaceToVariableSwallowError(input interface{}) ast.Variable {
|
|
variable, _ := hil.InterfaceToVariable(input)
|
|
return variable
|
|
}
|
|
|
|
func TestInterpolateFuncElement(t *testing.T) {
|
|
testFunction(t, testFunctionConfig{
|
|
Vars: map[string]ast.Variable{
|
|
"var.a_list": interfaceToVariableSwallowError([]string{"foo", "baz"}),
|
|
"var.a_short_list": interfaceToVariableSwallowError([]string{"foo"}),
|
|
"var.empty_list": interfaceToVariableSwallowError([]interface{}{}),
|
|
"var.a_nested_list": interfaceToVariableSwallowError([]interface{}{[]string{"foo"}, []string{"baz"}}),
|
|
},
|
|
Cases: []testFunctionCase{
|
|
{
|
|
`${element(var.a_list, "1")}`,
|
|
"baz",
|
|
false,
|
|
},
|
|
|
|
{
|
|
`${element(var.a_short_list, "0")}`,
|
|
"foo",
|
|
false,
|
|
},
|
|
|
|
// Invalid index should wrap vs. out-of-bounds
|
|
{
|
|
`${element(var.a_list, "2")}`,
|
|
"foo",
|
|
false,
|
|
},
|
|
|
|
// Negative number should fail
|
|
{
|
|
`${element(var.a_short_list, "-1")}`,
|
|
nil,
|
|
true,
|
|
},
|
|
|
|
// Empty list should fail
|
|
{
|
|
`${element(var.empty_list, 0)}`,
|
|
nil,
|
|
true,
|
|
},
|
|
|
|
// Too many args
|
|
{
|
|
`${element(var.a_list, "0", "2")}`,
|
|
nil,
|
|
true,
|
|
},
|
|
|
|
// Only works on single-level lists
|
|
{
|
|
`${element(var.a_nested_list, "0")}`,
|
|
nil,
|
|
true,
|
|
},
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestInterpolateFuncChunklist(t *testing.T) {
|
|
testFunction(t, testFunctionConfig{
|
|
Cases: []testFunctionCase{
|
|
// normal usage
|
|
{
|
|
`${chunklist(list("a", "b", "c"), 1)}`,
|
|
[]interface{}{
|
|
[]interface{}{"a"},
|
|
[]interface{}{"b"},
|
|
[]interface{}{"c"},
|
|
},
|
|
false,
|
|
},
|
|
// `size` is pair and the list has an impair number of items
|
|
{
|
|
`${chunklist(list("a", "b", "c"), 2)}`,
|
|
[]interface{}{
|
|
[]interface{}{"a", "b"},
|
|
[]interface{}{"c"},
|
|
},
|
|
false,
|
|
},
|
|
// list made of the same list, since size is 0
|
|
{
|
|
`${chunklist(list("a", "b", "c"), 0)}`,
|
|
[]interface{}{[]interface{}{"a", "b", "c"}},
|
|
false,
|
|
},
|
|
// negative size of chunks
|
|
{
|
|
`${chunklist(list("a", "b", "c"), -1)}`,
|
|
nil,
|
|
true,
|
|
},
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestInterpolateFuncBasename(t *testing.T) {
|
|
testFunction(t, testFunctionConfig{
|
|
Cases: []testFunctionCase{
|
|
{
|
|
`${basename("/foo/bar/baz")}`,
|
|
"baz",
|
|
false,
|
|
},
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestInterpolateFuncBase64Encode(t *testing.T) {
|
|
testFunction(t, testFunctionConfig{
|
|
Cases: []testFunctionCase{
|
|
// Regular base64 encoding
|
|
{
|
|
`${base64encode("abc123!?$*&()'-=@~")}`,
|
|
"YWJjMTIzIT8kKiYoKSctPUB+",
|
|
false,
|
|
},
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestInterpolateFuncBase64Decode(t *testing.T) {
|
|
testFunction(t, testFunctionConfig{
|
|
Cases: []testFunctionCase{
|
|
// Regular base64 decoding
|
|
{
|
|
`${base64decode("YWJjMTIzIT8kKiYoKSctPUB+")}`,
|
|
"abc123!?$*&()'-=@~",
|
|
false,
|
|
},
|
|
|
|
// Invalid base64 data decoding
|
|
{
|
|
`${base64decode("this-is-an-invalid-base64-data")}`,
|
|
nil,
|
|
true,
|
|
},
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestInterpolateFuncLower(t *testing.T) {
|
|
testFunction(t, testFunctionConfig{
|
|
Cases: []testFunctionCase{
|
|
{
|
|
`${lower("HELLO")}`,
|
|
"hello",
|
|
false,
|
|
},
|
|
|
|
{
|
|
`${lower("")}`,
|
|
"",
|
|
false,
|
|
},
|
|
|
|
{
|
|
`${lower()}`,
|
|
nil,
|
|
true,
|
|
},
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestInterpolateFuncUpper(t *testing.T) {
|
|
testFunction(t, testFunctionConfig{
|
|
Cases: []testFunctionCase{
|
|
{
|
|
`${upper("hello")}`,
|
|
"HELLO",
|
|
false,
|
|
},
|
|
|
|
{
|
|
`${upper("")}`,
|
|
"",
|
|
false,
|
|
},
|
|
|
|
{
|
|
`${upper()}`,
|
|
nil,
|
|
true,
|
|
},
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestInterpolateFuncSha1(t *testing.T) {
|
|
testFunction(t, testFunctionConfig{
|
|
Cases: []testFunctionCase{
|
|
{
|
|
`${sha1("test")}`,
|
|
"a94a8fe5ccb19ba61c4c0873d391e987982fbbd3",
|
|
false,
|
|
},
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestInterpolateFuncSha256(t *testing.T) {
|
|
testFunction(t, testFunctionConfig{
|
|
Cases: []testFunctionCase{
|
|
{ // hexadecimal representation of sha256 sum
|
|
`${sha256("test")}`,
|
|
"9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",
|
|
false,
|
|
},
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestInterpolateFuncSha512(t *testing.T) {
|
|
testFunction(t, testFunctionConfig{
|
|
Cases: []testFunctionCase{
|
|
{
|
|
`${sha512("test")}`,
|
|
"ee26b0dd4af7e749aa1a8ee3c10ae9923f618980772e473f8819a5d4940e0db27ac185f8a0e1d5f84f88bc887fd67b143732c304cc5fa9ad8e6f57f50028a8ff",
|
|
false,
|
|
},
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestInterpolateFuncTitle(t *testing.T) {
|
|
testFunction(t, testFunctionConfig{
|
|
Cases: []testFunctionCase{
|
|
{
|
|
`${title("hello")}`,
|
|
"Hello",
|
|
false,
|
|
},
|
|
|
|
{
|
|
`${title("hello world")}`,
|
|
"Hello World",
|
|
false,
|
|
},
|
|
|
|
{
|
|
`${title("")}`,
|
|
"",
|
|
false,
|
|
},
|
|
|
|
{
|
|
`${title()}`,
|
|
nil,
|
|
true,
|
|
},
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestInterpolateFuncTrimSpace(t *testing.T) {
|
|
testFunction(t, testFunctionConfig{
|
|
Cases: []testFunctionCase{
|
|
{
|
|
`${trimspace(" test ")}`,
|
|
"test",
|
|
false,
|
|
},
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestInterpolateFuncBase64Gzip(t *testing.T) {
|
|
testFunction(t, testFunctionConfig{
|
|
Cases: []testFunctionCase{
|
|
{
|
|
`${base64gzip("test")}`,
|
|
"H4sIAAAAAAAA/ypJLS4BAAAA//8BAAD//wx+f9gEAAAA",
|
|
false,
|
|
},
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestInterpolateFuncBase64Sha256(t *testing.T) {
|
|
testFunction(t, testFunctionConfig{
|
|
Cases: []testFunctionCase{
|
|
{
|
|
`${base64sha256("test")}`,
|
|
"n4bQgYhMfWWaL+qgxVrQFaO/TxsrC4Is0V1sFbDwCgg=",
|
|
false,
|
|
},
|
|
{ // This will differ because we're base64-encoding hex represantiation, not raw bytes
|
|
`${base64encode(sha256("test"))}`,
|
|
"OWY4NmQwODE4ODRjN2Q2NTlhMmZlYWEwYzU1YWQwMTVhM2JmNGYxYjJiMGI4MjJjZDE1ZDZjMTViMGYwMGEwOA==",
|
|
false,
|
|
},
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestInterpolateFuncBase64Sha512(t *testing.T) {
|
|
testFunction(t, testFunctionConfig{
|
|
Cases: []testFunctionCase{
|
|
{
|
|
`${base64sha512("test")}`,
|
|
"7iaw3Ur350mqGo7jwQrpkj9hiYB3Lkc/iBml1JQODbJ6wYX4oOHV+E+IvIh/1nsUNzLDBMxfqa2Ob1f1ACio/w==",
|
|
false,
|
|
},
|
|
{ // This will differ because we're base64-encoding hex represantiation, not raw bytes
|
|
`${base64encode(sha512("test"))}`,
|
|
"ZWUyNmIwZGQ0YWY3ZTc0OWFhMWE4ZWUzYzEwYWU5OTIzZjYxODk4MDc3MmU0NzNmODgxOWE1ZDQ5NDBlMGRiMjdhYzE4NWY4YTBlMWQ1Zjg0Zjg4YmM4ODdmZDY3YjE0MzczMmMzMDRjYzVmYTlhZDhlNmY1N2Y1MDAyOGE4ZmY=",
|
|
false,
|
|
},
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestInterpolateFuncMd5(t *testing.T) {
|
|
testFunction(t, testFunctionConfig{
|
|
Cases: []testFunctionCase{
|
|
{
|
|
`${md5("tada")}`,
|
|
"ce47d07243bb6eaf5e1322c81baf9bbf",
|
|
false,
|
|
},
|
|
{ // Confirm that we're not trimming any whitespaces
|
|
`${md5(" tada ")}`,
|
|
"aadf191a583e53062de2d02c008141c4",
|
|
false,
|
|
},
|
|
{ // We accept empty string too
|
|
`${md5("")}`,
|
|
"d41d8cd98f00b204e9800998ecf8427e",
|
|
false,
|
|
},
|
|
},
|
|
})
|
|
}
|
|
|
|
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
|
|
}
|
|
}
|
|
|
|
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\nReceived: %s", currentTime.Format(time.RFC3339), result.Value.(string))
|
|
}
|
|
}
|
|
|
|
func TestInterpolateFuncTimeAdd(t *testing.T) {
|
|
testFunction(t, testFunctionConfig{
|
|
Cases: []testFunctionCase{
|
|
{
|
|
`${timeadd("2017-11-22T00:00:00Z", "1s")}`,
|
|
"2017-11-22T00:00:01Z",
|
|
false,
|
|
},
|
|
{
|
|
`${timeadd("2017-11-22T00:00:00Z", "10m1s")}`,
|
|
"2017-11-22T00:10:01Z",
|
|
false,
|
|
},
|
|
{ // also support subtraction
|
|
`${timeadd("2017-11-22T00:00:00Z", "-1h")}`,
|
|
"2017-11-21T23:00:00Z",
|
|
false,
|
|
},
|
|
{ // Invalid format timestamp
|
|
`${timeadd("2017-11-22", "-1h")}`,
|
|
nil,
|
|
true,
|
|
},
|
|
{ // Invalid format duration (day is not supported by ParseDuration)
|
|
`${timeadd("2017-11-22T00:00:00Z", "1d")}`,
|
|
nil,
|
|
true,
|
|
},
|
|
},
|
|
})
|
|
}
|
|
|
|
type testFunctionConfig struct {
|
|
Cases []testFunctionCase
|
|
Vars map[string]ast.Variable
|
|
}
|
|
|
|
type testFunctionCase struct {
|
|
Input string
|
|
Result interface{}
|
|
Error bool
|
|
}
|
|
|
|
func testFunction(t *testing.T, config testFunctionConfig) {
|
|
t.Helper()
|
|
for _, tc := range config.Cases {
|
|
t.Run(tc.Input, func(t *testing.T) {
|
|
ast, err := hil.Parse(tc.Input)
|
|
if err != nil {
|
|
t.Fatalf("unexpected parse error: %s", err)
|
|
}
|
|
|
|
result, err := hil.Eval(ast, langEvalConfig(config.Vars))
|
|
if err != nil != tc.Error {
|
|
t.Fatalf("unexpected eval error: %s", err)
|
|
}
|
|
|
|
if !reflect.DeepEqual(result.Value, tc.Result) {
|
|
t.Errorf("wrong result\ngiven: %s\ngot: %#v\nwant: %#v", tc.Input, result.Value, tc.Result)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestInterpolateFuncPathExpand(t *testing.T) {
|
|
homePath, err := homedir.Dir()
|
|
if err != nil {
|
|
t.Fatalf("Error getting home directory: %v", err)
|
|
}
|
|
testFunction(t, testFunctionConfig{
|
|
Cases: []testFunctionCase{
|
|
{
|
|
`${pathexpand("~/test-file")}`,
|
|
filepath.Join(homePath, "test-file"),
|
|
false,
|
|
},
|
|
{
|
|
`${pathexpand("~/another/test/file")}`,
|
|
filepath.Join(homePath, "another/test/file"),
|
|
false,
|
|
},
|
|
{
|
|
`${pathexpand("/root/file")}`,
|
|
"/root/file",
|
|
false,
|
|
},
|
|
{
|
|
`${pathexpand("/")}`,
|
|
"/",
|
|
false,
|
|
},
|
|
{
|
|
`${pathexpand()}`,
|
|
nil,
|
|
true,
|
|
},
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestInterpolateFuncSubstr(t *testing.T) {
|
|
testFunction(t, testFunctionConfig{
|
|
Cases: []testFunctionCase{
|
|
{
|
|
`${substr("foobar", 0, 0)}`,
|
|
"",
|
|
false,
|
|
},
|
|
{
|
|
`${substr("foobar", 0, -1)}`,
|
|
"foobar",
|
|
false,
|
|
},
|
|
{
|
|
`${substr("foobar", 0, 3)}`,
|
|
"foo",
|
|
false,
|
|
},
|
|
{
|
|
`${substr("foobar", 3, 3)}`,
|
|
"bar",
|
|
false,
|
|
},
|
|
{
|
|
`${substr("foobar", -3, 3)}`,
|
|
"bar",
|
|
false,
|
|
},
|
|
|
|
// empty string
|
|
{
|
|
`${substr("", 0, 0)}`,
|
|
"",
|
|
false,
|
|
},
|
|
|
|
// invalid offset
|
|
{
|
|
`${substr("", 1, 0)}`,
|
|
nil,
|
|
true,
|
|
},
|
|
{
|
|
`${substr("foo", -4, -1)}`,
|
|
nil,
|
|
true,
|
|
},
|
|
|
|
// invalid length
|
|
{
|
|
`${substr("", 0, 1)}`,
|
|
nil,
|
|
true,
|
|
},
|
|
{
|
|
`${substr("", 0, -2)}`,
|
|
nil,
|
|
true,
|
|
},
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestInterpolateFuncBcrypt(t *testing.T) {
|
|
node, err := hil.Parse(`${bcrypt("test")}`)
|
|
if err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
|
|
result, err := hil.Eval(node, langEvalConfig(nil))
|
|
if err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
err = bcrypt.CompareHashAndPassword([]byte(result.Value.(string)), []byte("test"))
|
|
|
|
if err != nil {
|
|
t.Fatalf("Error comparing hash and password: %s", err)
|
|
}
|
|
|
|
testFunction(t, testFunctionConfig{
|
|
Cases: []testFunctionCase{
|
|
//Negative test for more than two parameters
|
|
{
|
|
`${bcrypt("test", 15, 12)}`,
|
|
nil,
|
|
true,
|
|
},
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestInterpolateFuncFlatten(t *testing.T) {
|
|
testFunction(t, testFunctionConfig{
|
|
Cases: []testFunctionCase{
|
|
// empty string within array
|
|
{
|
|
`${flatten(split(",", "a,,b"))}`,
|
|
[]interface{}{"a", "", "b"},
|
|
false,
|
|
},
|
|
|
|
// typical array
|
|
{
|
|
`${flatten(split(",", "a,b,c"))}`,
|
|
[]interface{}{"a", "b", "c"},
|
|
false,
|
|
},
|
|
|
|
// empty array
|
|
{
|
|
`${flatten(split(",", ""))}`,
|
|
[]interface{}{""},
|
|
false,
|
|
},
|
|
|
|
// list of lists
|
|
{
|
|
`${flatten(list(list("a"), list("b")))}`,
|
|
[]interface{}{"a", "b"},
|
|
false,
|
|
},
|
|
// list of lists of lists
|
|
{
|
|
`${flatten(list(list("a"), list(list("b","c"))))}`,
|
|
[]interface{}{"a", "b", "c"},
|
|
false,
|
|
},
|
|
// list of strings
|
|
{
|
|
`${flatten(list("a", "b", "c"))}`,
|
|
[]interface{}{"a", "b", "c"},
|
|
false,
|
|
},
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestInterpolateFuncURLEncode(t *testing.T) {
|
|
testFunction(t, testFunctionConfig{
|
|
Cases: []testFunctionCase{
|
|
{
|
|
`${urlencode("abc123-_")}`,
|
|
"abc123-_",
|
|
false,
|
|
},
|
|
{
|
|
`${urlencode("foo:bar@localhost?foo=bar&bar=baz")}`,
|
|
"foo%3Abar%40localhost%3Ffoo%3Dbar%26bar%3Dbaz",
|
|
false,
|
|
},
|
|
{
|
|
`${urlencode("mailto:email?subject=this+is+my+subject")}`,
|
|
"mailto%3Aemail%3Fsubject%3Dthis%2Bis%2Bmy%2Bsubject",
|
|
false,
|
|
},
|
|
{
|
|
`${urlencode("foo/bar")}`,
|
|
"foo%2Fbar",
|
|
false,
|
|
},
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestInterpolateFuncTranspose(t *testing.T) {
|
|
testFunction(t, testFunctionConfig{
|
|
Vars: map[string]ast.Variable{
|
|
"var.map": ast.Variable{
|
|
Type: ast.TypeMap,
|
|
Value: map[string]ast.Variable{
|
|
"key1": ast.Variable{
|
|
Type: ast.TypeList,
|
|
Value: []ast.Variable{
|
|
{Type: ast.TypeString, Value: "a"},
|
|
{Type: ast.TypeString, Value: "b"},
|
|
},
|
|
},
|
|
"key2": ast.Variable{
|
|
Type: ast.TypeList,
|
|
Value: []ast.Variable{
|
|
{Type: ast.TypeString, Value: "a"},
|
|
{Type: ast.TypeString, Value: "b"},
|
|
{Type: ast.TypeString, Value: "c"},
|
|
},
|
|
},
|
|
"key3": ast.Variable{
|
|
Type: ast.TypeList,
|
|
Value: []ast.Variable{
|
|
{Type: ast.TypeString, Value: "c"},
|
|
},
|
|
},
|
|
"key4": ast.Variable{
|
|
Type: ast.TypeList,
|
|
Value: []ast.Variable{},
|
|
},
|
|
}},
|
|
"var.badmap": ast.Variable{
|
|
Type: ast.TypeMap,
|
|
Value: map[string]ast.Variable{
|
|
"key1": ast.Variable{
|
|
Type: ast.TypeList,
|
|
Value: []ast.Variable{
|
|
{Type: ast.TypeList, Value: []ast.Variable{}},
|
|
{Type: ast.TypeList, Value: []ast.Variable{}},
|
|
},
|
|
},
|
|
}},
|
|
"var.worsemap": ast.Variable{
|
|
Type: ast.TypeMap,
|
|
Value: map[string]ast.Variable{
|
|
"key1": ast.Variable{
|
|
Type: ast.TypeString,
|
|
Value: "not-a-list",
|
|
},
|
|
}},
|
|
},
|
|
Cases: []testFunctionCase{
|
|
{
|
|
`${transpose(var.map)}`,
|
|
map[string]interface{}{
|
|
"a": []interface{}{"key1", "key2"},
|
|
"b": []interface{}{"key1", "key2"},
|
|
"c": []interface{}{"key2", "key3"},
|
|
},
|
|
false,
|
|
},
|
|
{
|
|
`${transpose(var.badmap)}`,
|
|
nil,
|
|
true,
|
|
},
|
|
{
|
|
`${transpose(var.worsemap)}`,
|
|
nil,
|
|
true,
|
|
},
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestInterpolateFuncAbs(t *testing.T) {
|
|
testFunction(t, testFunctionConfig{
|
|
Cases: []testFunctionCase{
|
|
{
|
|
`${abs()}`,
|
|
nil,
|
|
true,
|
|
},
|
|
{
|
|
`${abs("")}`,
|
|
nil,
|
|
true,
|
|
},
|
|
{
|
|
`${abs(0)}`,
|
|
"0",
|
|
false,
|
|
},
|
|
{
|
|
`${abs(1)}`,
|
|
"1",
|
|
false,
|
|
},
|
|
{
|
|
`${abs(-1)}`,
|
|
"1",
|
|
false,
|
|
},
|
|
{
|
|
`${abs(1.0)}`,
|
|
"1",
|
|
false,
|
|
},
|
|
{
|
|
`${abs(-1.0)}`,
|
|
"1",
|
|
false,
|
|
},
|
|
{
|
|
`${abs(-3.14)}`,
|
|
"3.14",
|
|
false,
|
|
},
|
|
{
|
|
`${abs(-42.001)}`,
|
|
"42.001",
|
|
false,
|
|
},
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestInterpolateFuncRsaDecrypt(t *testing.T) {
|
|
testFunction(t, testFunctionConfig{
|
|
Vars: map[string]ast.Variable{
|
|
"var.cipher_base64": ast.Variable{
|
|
Type: ast.TypeString,
|
|
Value: "eczGaDhXDbOFRZGhjx2etVzWbRqWDlmq0bvNt284JHVbwCgObiuyX9uV0LSAMY707IEgMkExJqXmsB4OWKxvB7epRB9G/3+F+pcrQpODlDuL9oDUAsa65zEpYF0Wbn7Oh7nrMQncyUPpyr9WUlALl0gRWytOA23S+y5joa4M34KFpawFgoqTu/2EEH4Xl1zo+0fy73fEto+nfkUY+meuyGZ1nUx/+DljP7ZqxHBFSlLODmtuTMdswUbHbXbWneW51D7Jm7xB8nSdiA2JQNK5+Sg5x8aNfgvFTt/m2w2+qpsyFa5Wjeu6fZmXSl840CA07aXbk9vN4I81WmJyblD/ZA==",
|
|
},
|
|
"var.private_key": ast.Variable{
|
|
Type: ast.TypeString,
|
|
Value: `
|
|
-----BEGIN RSA PRIVATE KEY-----
|
|
MIIEowIBAAKCAQEAgUElV5mwqkloIrM8ZNZ72gSCcnSJt7+/Usa5G+D15YQUAdf9
|
|
c1zEekTfHgDP+04nw/uFNFaE5v1RbHaPxhZYVg5ZErNCa/hzn+x10xzcepeS3KPV
|
|
Xcxae4MR0BEegvqZqJzN9loXsNL/c3H/B+2Gle3hTxjlWFb3F5qLgR+4Mf4ruhER
|
|
1v6eHQa/nchi03MBpT4UeJ7MrL92hTJYLdpSyCqmr8yjxkKJDVC2uRrr+sTSxfh7
|
|
r6v24u/vp/QTmBIAlNPgadVAZw17iNNb7vjV7Gwl/5gHXonCUKURaV++dBNLrHIZ
|
|
pqcAM8wHRph8mD1EfL9hsz77pHewxolBATV+7QIDAQABAoIBAC1rK+kFW3vrAYm3
|
|
+8/fQnQQw5nec4o6+crng6JVQXLeH32qXShNf8kLLG/Jj0vaYcTPPDZw9JCKkTMQ
|
|
0mKj9XR/5DLbBMsV6eNXXuvJJ3x4iKW5eD9WkLD4FKlNarBRyO7j8sfPTqXW7uat
|
|
NxWdFH7YsSRvNh/9pyQHLWA5OituidMrYbc3EUx8B1GPNyJ9W8Q8znNYLfwYOjU4
|
|
Wv1SLE6qGQQH9Q0WzA2WUf8jklCYyMYTIywAjGb8kbAJlKhmj2t2Igjmqtwt1PYc
|
|
pGlqbtQBDUiWXt5S4YX/1maIQ/49yeNUajjpbJiH3DbhJbHwFTzP3pZ9P9GHOzlG
|
|
kYR+wSECgYEAw/Xida8kSv8n86V3qSY/I+fYQ5V+jDtXIE+JhRnS8xzbOzz3v0WS
|
|
Oo5H+o4nJx5eL3Ghb3Gcm0Jn46dHrxinHbm+3RjXv/X6tlbxIYjRSQfHOTSMCTvd
|
|
qcliF5vC6RCLXuc7R+IWR1Ky6eDEZGtrvt3DyeYABsp9fRUFR/6NluUCgYEAqNsw
|
|
1aSl7WJa27F0DoJdlU9LWerpXcazlJcIdOz/S9QDmSK3RDQTdqfTxRmrxiYI9LEs
|
|
mkOkvzlnnOBMpnZ3ZOU5qIRfprecRIi37KDAOHWGnlC0EWGgl46YLb7/jXiWf0AG
|
|
Y+DfJJNd9i6TbIDWu8254/erAS6bKMhW/3q7f2kCgYAZ7Id/BiKJAWRpqTRBXlvw
|
|
BhXoKvjI2HjYP21z/EyZ+PFPzur/lNaZhIUlMnUfibbwE9pFggQzzf8scM7c7Sf+
|
|
mLoVSdoQ/Rujz7CqvQzi2nKSsM7t0curUIb3lJWee5/UeEaxZcmIufoNUrzohAWH
|
|
BJOIPDM4ssUTLRq7wYM9uQKBgHCBau5OP8gE6mjKuXsZXWUoahpFLKwwwmJUp2vQ
|
|
pOFPJ/6WZOlqkTVT6QPAcPUbTohKrF80hsZqZyDdSfT3peFx4ZLocBrS56m6NmHR
|
|
UYHMvJ8rQm76T1fryHVidz85g3zRmfBeWg8yqT5oFg4LYgfLsPm1gRjOhs8LfPvI
|
|
OLlRAoGBAIZ5Uv4Z3s8O7WKXXUe/lq6j7vfiVkR1NW/Z/WLKXZpnmvJ7FgxN4e56
|
|
RXT7GwNQHIY8eDjDnsHxzrxd+raOxOZeKcMHj3XyjCX3NHfTscnsBPAGYpY/Wxzh
|
|
T8UYnFu6RzkixElTf2rseEav7rkdKkI3LAeIZy7B0HulKKsmqVQ7
|
|
-----END RSA PRIVATE KEY-----
|
|
`,
|
|
},
|
|
"var.wrong_private_key": ast.Variable{
|
|
Type: ast.TypeString,
|
|
Value: `
|
|
-----BEGIN RSA PRIVATE KEY-----
|
|
MIIEowIBAAKCAQEAlrCgnEVgmNKCq7KPc+zUU5IrxPu1ClMNJS7RTsTPEkbwe5SB
|
|
p+6V6WtCbD/X/lDRRGbOENChh1Phulb7lViqgrdpHydgsrKoS5ah3DfSIxLFLE00
|
|
9Yo4TCYwgw6+s59j16ZAFVinaQ9l6Kmrb2ll136hMrz8QKh+qw+onOLd38WFgm+W
|
|
ZtUqSXf2LANzfzzy4OWFNyFqKaCAolSkPdTS9Nz+svtScvp002DQp8OdP1AgPO+l
|
|
o5N3M38Fftapwg0pCtJ5Zq0NRWIXEonXiTEMA6zy3gEZVOmDxoIFUWnmrqlMJLFy
|
|
5S6LDrHSdqJhCxDK6WRZj43X9j8spktk3eGhMwIDAQABAoIBAAem8ID/BOi9x+Tw
|
|
LFi2rhGQWqimH4tmrEQ3HGnjlKBY+d1MrUjZ1MMFr1nP5CgF8pqGnfA8p/c3Sz8r
|
|
K5tp5T6+EZiDZ2WrrOApxg5ox0MAsQKO6SGO40z6o3wEQ6rbbTaGOrraxaWQIpyu
|
|
AQanU4Sd6ZGqByVBaS1GnklZO+shCHqw73b7g1cpLEmFzcYnKHYHlUUIsstMe8E1
|
|
BaCY0CH7JbWBjcbiTnBVwIRZuu+EjGiQuhTilYL2OWqoMVg1WU0L2IFpR8lkf/2W
|
|
SBx5J6xhwbBGASOpM+qidiN580GdPzGhWYSqKGroHEzBm6xPSmV1tadNA26WFG4p
|
|
pthLiAECgYEA5BsPRpNYJAQLu5B0N7mj9eEp0HABVEgL/MpwiImjaKdAwp78HM64
|
|
IuPvJxs7r+xESiIz4JyjR8zrQjYOCKJsARYkmNlEuAz0SkHabCw1BdEBwUhjUGVB
|
|
efoERK6GxfAoNqmSDwsOvHFOtsmDIlbHmg7G2rUxNVpeou415BSB0B8CgYEAqR4J
|
|
YHKk2Ibr9rU+rBU33TcdTGw0aAkFNAVeqM9j0haWuFXmV3RArgoy09lH+2Ha6z/g
|
|
fTX2xSDAWV7QUlLOlBRIhurPAo2jO2yCrGHPZcWiugstrR2hTTInigaSnCmK3i7F
|
|
6sYmL3S7K01IcVNxSlWvGijtClT92Cl2WUCTfG0CgYAiEjyk4QtQTd5mxLvnOu5X
|
|
oqs5PBGmwiAwQRiv/EcRMbJFn7Oupd3xMDSflbzDmTnWDOfMy/jDl8MoH6TW+1PA
|
|
kcsjnYhbKWwvz0hN0giVdtOZSDO1ZXpzOrn6fEsbM7T9/TQY1SD9WrtUKCNTNL0Z
|
|
sM1ZC6lu+7GZCpW4HKwLJwKBgQCRT0yxQXBg1/UxwuO5ynV4rx2Oh76z0WRWIXMH
|
|
S0MyxdP1SWGkrS/SGtM3cg/GcHtA/V6vV0nUcWK0p6IJyjrTw2XZ/zGluPuTWJYi
|
|
9dvVT26Vunshrz7kbH7KuwEICy3V4IyQQHeY+QzFlR70uMS0IVFWAepCoWqHbIDT
|
|
CYhwNQKBgGPcLXmjpGtkZvggl0aZr9LsvCTckllSCFSI861kivL/rijdNoCHGxZv
|
|
dfDkLTLcz9Gk41rD9Gxn/3sqodnTAc3Z2PxFnzg1Q/u3+x6YAgBwI/g/jE2xutGW
|
|
H7CurtMwALQ/n/6LUKFmjRZjqbKX9SO2QSaC3grd6sY9Tu+bZjLe
|
|
-----END RSA PRIVATE KEY-----
|
|
`,
|
|
},
|
|
},
|
|
Cases: []testFunctionCase{
|
|
// Base-64 encoded cipher decrypts correctly
|
|
{
|
|
`${rsadecrypt(var.cipher_base64, var.private_key)}`,
|
|
"message",
|
|
false,
|
|
},
|
|
// Raw cipher
|
|
{
|
|
`${rsadecrypt(base64decode(var.cipher_base64), var.private_key)}`,
|
|
nil,
|
|
true,
|
|
},
|
|
// Wrong key
|
|
{
|
|
`${rsadecrypt(var.cipher_base64, var.wrong_private_key)}`,
|
|
nil,
|
|
true,
|
|
},
|
|
// Bad key
|
|
{
|
|
`${rsadecrypt(var.cipher_base64, "bad key")}`,
|
|
nil,
|
|
true,
|
|
},
|
|
// Empty key
|
|
{
|
|
`${rsadecrypt(var.cipher_base64, "")}`,
|
|
nil,
|
|
true,
|
|
},
|
|
// Bad cipher
|
|
{
|
|
`${rsadecrypt("bad cipher", var.private_key)}`,
|
|
nil,
|
|
true,
|
|
},
|
|
// Bad base64-encoded cipher
|
|
{
|
|
`${rsadecrypt(base64encode("bad cipher"), var.private_key)}`,
|
|
nil,
|
|
true,
|
|
},
|
|
// Empty cipher
|
|
{
|
|
`${rsadecrypt("", var.private_key)}`,
|
|
nil,
|
|
true,
|
|
},
|
|
// Too many arguments
|
|
{
|
|
`${rsadecrypt("", "", "")}`,
|
|
nil,
|
|
true,
|
|
},
|
|
// One argument
|
|
{
|
|
`${rsadecrypt("")}`,
|
|
nil,
|
|
true,
|
|
},
|
|
// No arguments
|
|
{
|
|
`${rsadecrypt()}`,
|
|
nil,
|
|
true,
|
|
},
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestInterpolateFuncSetProduct(t *testing.T) {
|
|
testFunction(t, testFunctionConfig{
|
|
Cases: []testFunctionCase{
|
|
{
|
|
`${setproduct(list("dev", "qas", "prd"), list("applicationA", "applicationB", "applicationC"))}`,
|
|
[]interface{}{
|
|
[]interface{}{"dev", "applicationA"},
|
|
[]interface{}{"dev", "applicationB"},
|
|
[]interface{}{"dev", "applicationC"},
|
|
[]interface{}{"qas", "applicationA"},
|
|
[]interface{}{"qas", "applicationB"},
|
|
[]interface{}{"qas", "applicationC"},
|
|
[]interface{}{"prd", "applicationA"},
|
|
[]interface{}{"prd", "applicationB"},
|
|
[]interface{}{"prd", "applicationC"}},
|
|
false,
|
|
},
|
|
{
|
|
`${setproduct(list("A", "B"), list("C", "D"), list("E", "F"))}`,
|
|
[]interface{}{
|
|
[]interface{}{"A", "C", "E"},
|
|
[]interface{}{"A", "C", "F"},
|
|
[]interface{}{"A", "D", "E"},
|
|
[]interface{}{"A", "D", "F"},
|
|
[]interface{}{"B", "C", "E"},
|
|
[]interface{}{"B", "C", "F"},
|
|
[]interface{}{"B", "D", "E"},
|
|
[]interface{}{"B", "D", "F"},
|
|
},
|
|
false,
|
|
},
|
|
{
|
|
`${setproduct(list(), list(), list())}`,
|
|
nil,
|
|
true,
|
|
},
|
|
{
|
|
`${setproduct(list("foo"),list("bar"))}`,
|
|
[]interface{}{
|
|
[]interface{}{"foo", "bar"},
|
|
},
|
|
false,
|
|
},
|
|
{
|
|
`${setproduct(list("foo"))}`,
|
|
nil,
|
|
true,
|
|
},
|
|
},
|
|
})
|
|
}
|