2019-04-29 19:11:28 +02:00
|
|
|
package lang
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
2019-07-02 14:30:30 +02:00
|
|
|
"os"
|
2019-04-29 19:11:28 +02:00
|
|
|
"path/filepath"
|
|
|
|
"testing"
|
|
|
|
|
2019-09-10 00:58:44 +02:00
|
|
|
"github.com/hashicorp/hcl/v2"
|
|
|
|
"github.com/hashicorp/hcl/v2/hclsyntax"
|
2021-05-17 20:38:39 +02:00
|
|
|
"github.com/hashicorp/terraform/internal/experiments"
|
2019-04-29 19:11:28 +02:00
|
|
|
homedir "github.com/mitchellh/go-homedir"
|
|
|
|
"github.com/zclconf/go-cty/cty"
|
|
|
|
)
|
|
|
|
|
|
|
|
// TestFunctions tests that functions are callable through the functionality
|
|
|
|
// in the langs package, via HCL.
|
|
|
|
//
|
|
|
|
// These tests are primarily here to assert that the functions are properly
|
|
|
|
// registered in the functions table, rather than to test all of the details
|
|
|
|
// of the functions. Each function should only have one or two tests here,
|
|
|
|
// since the main set of unit tests for a function should live alongside that
|
|
|
|
// function either in the "funcs" subdirectory here or over in the cty
|
|
|
|
// function/stdlib package.
|
|
|
|
//
|
|
|
|
// One exception to that is we can use this test mechanism to assert common
|
|
|
|
// patterns that are used in real-world configurations which rely on behaviors
|
|
|
|
// implemented either in this lang package or in HCL itself, such as automatic
|
|
|
|
// type conversions. The function unit tests don't cover those things because
|
|
|
|
// they call directly into the functions.
|
|
|
|
//
|
|
|
|
// With that said then, this test function should contain at least one simple
|
|
|
|
// test case per function registered in the functions table (just to prove
|
|
|
|
// it really is registered correctly) and possibly a small set of additional
|
|
|
|
// functions showing real-world use-cases that rely on type conversion
|
|
|
|
// behaviors.
|
|
|
|
func TestFunctions(t *testing.T) {
|
|
|
|
// used in `pathexpand()` test
|
|
|
|
homePath, err := homedir.Dir()
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("Error getting home directory: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
tests := map[string][]struct {
|
|
|
|
src string
|
|
|
|
want cty.Value
|
|
|
|
}{
|
|
|
|
// Please maintain this list in alphabetical order by function, with
|
|
|
|
// a blank line between the group of tests for each function.
|
|
|
|
|
|
|
|
"abs": {
|
|
|
|
{
|
|
|
|
`abs(-1)`,
|
|
|
|
cty.NumberIntVal(1),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
2019-07-02 14:30:30 +02:00
|
|
|
"abspath": {
|
|
|
|
{
|
|
|
|
`abspath(".")`,
|
|
|
|
cty.StringVal((func() string {
|
|
|
|
cwd, err := os.Getwd()
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
2021-01-15 08:53:06 +01:00
|
|
|
return filepath.ToSlash(cwd)
|
2019-07-02 14:30:30 +02:00
|
|
|
})()),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
2020-09-22 15:06:42 +02:00
|
|
|
"alltrue": {
|
|
|
|
{
|
2020-10-23 22:52:48 +02:00
|
|
|
`alltrue(["true", true])`,
|
2020-09-22 15:06:42 +02:00
|
|
|
cty.True,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
2020-10-23 22:52:48 +02:00
|
|
|
"anytrue": {
|
|
|
|
{
|
|
|
|
`anytrue([])`,
|
|
|
|
cty.False,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
2019-04-29 19:11:28 +02:00
|
|
|
"base64decode": {
|
|
|
|
{
|
|
|
|
`base64decode("YWJjMTIzIT8kKiYoKSctPUB+")`,
|
|
|
|
cty.StringVal("abc123!?$*&()'-=@~"),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
"base64encode": {
|
|
|
|
{
|
|
|
|
`base64encode("abc123!?$*&()'-=@~")`,
|
|
|
|
cty.StringVal("YWJjMTIzIT8kKiYoKSctPUB+"),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
"base64gzip": {
|
|
|
|
{
|
|
|
|
`base64gzip("test")`,
|
|
|
|
cty.StringVal("H4sIAAAAAAAA/ypJLS4BAAAA//8BAAD//wx+f9gEAAAA"),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
"base64sha256": {
|
|
|
|
{
|
|
|
|
`base64sha256("test")`,
|
|
|
|
cty.StringVal("n4bQgYhMfWWaL+qgxVrQFaO/TxsrC4Is0V1sFbDwCgg="),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
"base64sha512": {
|
|
|
|
{
|
|
|
|
`base64sha512("test")`,
|
|
|
|
cty.StringVal("7iaw3Ur350mqGo7jwQrpkj9hiYB3Lkc/iBml1JQODbJ6wYX4oOHV+E+IvIh/1nsUNzLDBMxfqa2Ob1f1ACio/w=="),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
"basename": {
|
|
|
|
{
|
|
|
|
`basename("testdata/hello.txt")`,
|
|
|
|
cty.StringVal("hello.txt"),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
2020-01-03 23:01:14 +01:00
|
|
|
"can": {
|
|
|
|
{
|
|
|
|
`can(true)`,
|
|
|
|
cty.True,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
// Note: "can" only works with expressions that pass static
|
|
|
|
// validation, because it only gets an opportunity to run in
|
|
|
|
// that case. The following "works" (captures the error) because
|
|
|
|
// Terraform understands it as a reference to an attribute
|
|
|
|
// that does not exist during dynamic evaluation.
|
|
|
|
//
|
|
|
|
// "can" doesn't work with references that could never possibly
|
|
|
|
// be valid and are thus caught during static validation, such
|
|
|
|
// as an expression like "foo" alone which would be understood
|
|
|
|
// as an invalid resource reference.
|
|
|
|
`can({}.baz)`,
|
|
|
|
cty.False,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
2019-04-29 19:11:28 +02:00
|
|
|
"ceil": {
|
|
|
|
{
|
|
|
|
`ceil(1.2)`,
|
|
|
|
cty.NumberIntVal(2),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
"chomp": {
|
|
|
|
{
|
|
|
|
`chomp("goodbye\ncruel\nworld\n")`,
|
|
|
|
cty.StringVal("goodbye\ncruel\nworld"),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
"chunklist": {
|
|
|
|
{
|
|
|
|
`chunklist(["a", "b", "c"], 1)`,
|
|
|
|
cty.ListVal([]cty.Value{
|
|
|
|
cty.ListVal([]cty.Value{
|
|
|
|
cty.StringVal("a"),
|
|
|
|
}),
|
|
|
|
cty.ListVal([]cty.Value{
|
|
|
|
cty.StringVal("b"),
|
|
|
|
}),
|
|
|
|
cty.ListVal([]cty.Value{
|
|
|
|
cty.StringVal("c"),
|
|
|
|
}),
|
|
|
|
}),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
"cidrhost": {
|
|
|
|
{
|
|
|
|
`cidrhost("192.168.1.0/24", 5)`,
|
|
|
|
cty.StringVal("192.168.1.5"),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
"cidrnetmask": {
|
|
|
|
{
|
|
|
|
`cidrnetmask("192.168.1.0/24")`,
|
|
|
|
cty.StringVal("255.255.255.0"),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
"cidrsubnet": {
|
|
|
|
{
|
|
|
|
`cidrsubnet("192.168.2.0/20", 4, 6)`,
|
|
|
|
cty.StringVal("192.168.6.0/24"),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
2019-09-20 04:06:15 +02:00
|
|
|
"cidrsubnets": {
|
|
|
|
{
|
|
|
|
`cidrsubnets("10.0.0.0/8", 8, 8, 16, 8)`,
|
|
|
|
cty.ListVal([]cty.Value{
|
|
|
|
cty.StringVal("10.0.0.0/16"),
|
|
|
|
cty.StringVal("10.1.0.0/16"),
|
|
|
|
cty.StringVal("10.2.0.0/24"),
|
|
|
|
cty.StringVal("10.3.0.0/16"),
|
|
|
|
}),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
2019-04-29 19:11:28 +02:00
|
|
|
"coalesce": {
|
|
|
|
{
|
|
|
|
`coalesce("first", "second", "third")`,
|
|
|
|
cty.StringVal("first"),
|
|
|
|
},
|
|
|
|
|
|
|
|
{
|
|
|
|
`coalescelist(["first", "second"], ["third", "fourth"])`,
|
2019-05-01 23:55:44 +02:00
|
|
|
cty.TupleVal([]cty.Value{
|
2019-04-29 19:11:28 +02:00
|
|
|
cty.StringVal("first"), cty.StringVal("second"),
|
|
|
|
}),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
"coalescelist": {
|
|
|
|
{
|
2020-11-04 22:18:44 +01:00
|
|
|
`coalescelist(tolist(["a", "b"]), tolist(["c", "d"]))`,
|
2019-04-29 19:11:28 +02:00
|
|
|
cty.ListVal([]cty.Value{
|
|
|
|
cty.StringVal("a"),
|
|
|
|
cty.StringVal("b"),
|
|
|
|
}),
|
|
|
|
},
|
2019-05-01 23:55:44 +02:00
|
|
|
{
|
|
|
|
`coalescelist(["a", "b"], ["c", "d"])`,
|
|
|
|
cty.TupleVal([]cty.Value{
|
|
|
|
cty.StringVal("a"),
|
|
|
|
cty.StringVal("b"),
|
|
|
|
}),
|
|
|
|
},
|
2019-04-29 19:11:28 +02:00
|
|
|
},
|
|
|
|
|
|
|
|
"compact": {
|
|
|
|
{
|
|
|
|
`compact(["test", "", "test"])`,
|
|
|
|
cty.ListVal([]cty.Value{
|
|
|
|
cty.StringVal("test"), cty.StringVal("test"),
|
|
|
|
}),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
"concat": {
|
|
|
|
{
|
|
|
|
`concat(["a", ""], ["b", "c"])`,
|
|
|
|
cty.TupleVal([]cty.Value{
|
|
|
|
cty.StringVal("a"),
|
|
|
|
cty.StringVal(""),
|
|
|
|
cty.StringVal("b"),
|
|
|
|
cty.StringVal("c"),
|
|
|
|
}),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
"contains": {
|
|
|
|
{
|
|
|
|
`contains(["a", "b"], "a")`,
|
|
|
|
cty.True,
|
|
|
|
},
|
|
|
|
{ // Should also work with sets, due to automatic conversion
|
|
|
|
`contains(toset(["a", "b"]), "a")`,
|
|
|
|
cty.True,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
"csvdecode": {
|
|
|
|
{
|
|
|
|
`csvdecode("a,b,c\n1,2,3\n4,5,6")`,
|
|
|
|
cty.ListVal([]cty.Value{
|
|
|
|
cty.ObjectVal(map[string]cty.Value{
|
|
|
|
"a": cty.StringVal("1"),
|
|
|
|
"b": cty.StringVal("2"),
|
|
|
|
"c": cty.StringVal("3"),
|
|
|
|
}),
|
|
|
|
cty.ObjectVal(map[string]cty.Value{
|
|
|
|
"a": cty.StringVal("4"),
|
|
|
|
"b": cty.StringVal("5"),
|
|
|
|
"c": cty.StringVal("6"),
|
|
|
|
}),
|
|
|
|
}),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
2020-10-31 01:19:32 +01:00
|
|
|
"defaults": {
|
|
|
|
// This function is pretty specialized and so this is mainly
|
|
|
|
// just a test that it is defined at all. See the function's
|
|
|
|
// own unit tests for more interesting test cases.
|
|
|
|
{
|
|
|
|
`defaults({a: 4}, {a: 5})`,
|
|
|
|
cty.ObjectVal(map[string]cty.Value{
|
|
|
|
"a": cty.NumberIntVal(4),
|
|
|
|
}),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
2019-04-29 19:11:28 +02:00
|
|
|
"dirname": {
|
|
|
|
{
|
|
|
|
`dirname("testdata/hello.txt")`,
|
|
|
|
cty.StringVal("testdata"),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
"distinct": {
|
|
|
|
{
|
|
|
|
`distinct(["a", "b", "a", "b"])`,
|
|
|
|
cty.ListVal([]cty.Value{
|
|
|
|
cty.StringVal("a"), cty.StringVal("b"),
|
|
|
|
}),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
"element": {
|
|
|
|
{
|
|
|
|
`element(["hello"], 0)`,
|
|
|
|
cty.StringVal("hello"),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
"file": {
|
|
|
|
{
|
|
|
|
`file("hello.txt")`,
|
|
|
|
cty.StringVal("hello!"),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
"fileexists": {
|
|
|
|
{
|
|
|
|
`fileexists("hello.txt")`,
|
|
|
|
cty.BoolVal(true),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
2019-08-20 10:50:01 +02:00
|
|
|
"fileset": {
|
|
|
|
{
|
2019-08-28 17:54:04 +02:00
|
|
|
`fileset(".", "*/hello.*")`,
|
2019-08-20 10:50:01 +02:00
|
|
|
cty.SetVal([]cty.Value{
|
2019-08-28 17:54:04 +02:00
|
|
|
cty.StringVal("subdirectory/hello.tmpl"),
|
|
|
|
cty.StringVal("subdirectory/hello.txt"),
|
|
|
|
}),
|
|
|
|
},
|
|
|
|
{
|
|
|
|
`fileset(".", "subdirectory/hello.*")`,
|
|
|
|
cty.SetVal([]cty.Value{
|
|
|
|
cty.StringVal("subdirectory/hello.tmpl"),
|
|
|
|
cty.StringVal("subdirectory/hello.txt"),
|
|
|
|
}),
|
|
|
|
},
|
|
|
|
{
|
|
|
|
`fileset(".", "hello.*")`,
|
|
|
|
cty.SetVal([]cty.Value{
|
|
|
|
cty.StringVal("hello.tmpl"),
|
|
|
|
cty.StringVal("hello.txt"),
|
|
|
|
}),
|
|
|
|
},
|
|
|
|
{
|
|
|
|
`fileset("subdirectory", "hello.*")`,
|
|
|
|
cty.SetVal([]cty.Value{
|
|
|
|
cty.StringVal("hello.tmpl"),
|
|
|
|
cty.StringVal("hello.txt"),
|
2019-08-20 10:50:01 +02:00
|
|
|
}),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
2019-04-29 19:11:28 +02:00
|
|
|
"filebase64": {
|
|
|
|
{
|
|
|
|
`filebase64("hello.txt")`,
|
|
|
|
cty.StringVal("aGVsbG8h"),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
"filebase64sha256": {
|
|
|
|
{
|
|
|
|
`filebase64sha256("hello.txt")`,
|
|
|
|
cty.StringVal("zgYJL7lI2f+sfRo3bkBLJrdXW8wR7gWkYV/vT+w6MIs="),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
"filebase64sha512": {
|
|
|
|
{
|
|
|
|
`filebase64sha512("hello.txt")`,
|
|
|
|
cty.StringVal("xvgdsOn4IGyXHJ5YJuO6gj/7saOpAPgEdlKov3jqmP38dFhVo4U6Y1Z1RY620arxIJ6I6tLRkjgrXEy91oUOAg=="),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
"filemd5": {
|
|
|
|
{
|
|
|
|
`filemd5("hello.txt")`,
|
|
|
|
cty.StringVal("5a8dd3ad0756a93ded72b823b19dd877"),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
"filesha1": {
|
|
|
|
{
|
|
|
|
`filesha1("hello.txt")`,
|
|
|
|
cty.StringVal("8f7d88e901a5ad3a05d8cc0de93313fd76028f8c"),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
"filesha256": {
|
|
|
|
{
|
|
|
|
`filesha256("hello.txt")`,
|
|
|
|
cty.StringVal("ce06092fb948d9ffac7d1a376e404b26b7575bcc11ee05a4615fef4fec3a308b"),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
"filesha512": {
|
|
|
|
{
|
|
|
|
`filesha512("hello.txt")`,
|
|
|
|
cty.StringVal("c6f81db0e9f8206c971c9e5826e3ba823ffbb1a3a900f8047652a8bf78ea98fdfc745855a3853a635675458eb6d1aaf1209e88ead2d192382b5c4cbdd6850e02"),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
"flatten": {
|
|
|
|
{
|
2019-05-01 16:19:40 +02:00
|
|
|
`flatten([["a", "b"], ["c", "d"]])`,
|
|
|
|
cty.TupleVal([]cty.Value{
|
2019-04-29 19:11:28 +02:00
|
|
|
cty.StringVal("a"),
|
|
|
|
cty.StringVal("b"),
|
|
|
|
cty.StringVal("c"),
|
|
|
|
cty.StringVal("d"),
|
|
|
|
}),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
"floor": {
|
|
|
|
{
|
|
|
|
`floor(-1.8)`,
|
|
|
|
cty.NumberFloatVal(-2),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
"format": {
|
|
|
|
{
|
|
|
|
`format("Hello, %s!", "Ander")`,
|
|
|
|
cty.StringVal("Hello, Ander!"),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
"formatlist": {
|
|
|
|
{
|
|
|
|
`formatlist("Hello, %s!", ["Valentina", "Ander", "Olivia", "Sam"])`,
|
|
|
|
cty.ListVal([]cty.Value{
|
|
|
|
cty.StringVal("Hello, Valentina!"),
|
|
|
|
cty.StringVal("Hello, Ander!"),
|
|
|
|
cty.StringVal("Hello, Olivia!"),
|
|
|
|
cty.StringVal("Hello, Sam!"),
|
|
|
|
}),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
"formatdate": {
|
|
|
|
{
|
|
|
|
`formatdate("DD MMM YYYY hh:mm ZZZ", "2018-01-04T23:12:01Z")`,
|
|
|
|
cty.StringVal("04 Jan 2018 23:12 UTC"),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
"indent": {
|
|
|
|
{
|
|
|
|
fmt.Sprintf("indent(4, %#v)", Poem),
|
|
|
|
cty.StringVal("Fleas:\n Adam\n Had'em\n \n E.E. Cummings"),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
"index": {
|
|
|
|
{
|
|
|
|
`index(["a", "b", "c"], "a")`,
|
|
|
|
cty.NumberIntVal(0),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
"join": {
|
|
|
|
{
|
|
|
|
`join(" ", ["Hello", "World"])`,
|
|
|
|
cty.StringVal("Hello World"),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
"jsondecode": {
|
|
|
|
{
|
|
|
|
`jsondecode("{\"hello\": \"world\"}")`,
|
|
|
|
cty.ObjectVal(map[string]cty.Value{
|
|
|
|
"hello": cty.StringVal("world"),
|
|
|
|
}),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
"jsonencode": {
|
|
|
|
{
|
|
|
|
`jsonencode({"hello"="world"})`,
|
|
|
|
cty.StringVal("{\"hello\":\"world\"}"),
|
|
|
|
},
|
2020-09-03 21:16:49 +02:00
|
|
|
// We are intentionally choosing to escape <, >, and & characters
|
|
|
|
// to preserve backwards compatibility with Terraform 0.11
|
|
|
|
{
|
|
|
|
`jsonencode({"hello"="<cats & kittens>"})`,
|
|
|
|
cty.StringVal("{\"hello\":\"\\u003ccats \\u0026 kittens\\u003e\"}"),
|
|
|
|
},
|
2019-04-29 19:11:28 +02:00
|
|
|
},
|
|
|
|
|
|
|
|
"keys": {
|
|
|
|
{
|
|
|
|
`keys({"hello"=1, "goodbye"=42})`,
|
|
|
|
cty.TupleVal([]cty.Value{
|
|
|
|
cty.StringVal("goodbye"),
|
|
|
|
cty.StringVal("hello"),
|
|
|
|
}),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
"length": {
|
|
|
|
{
|
|
|
|
`length(["the", "quick", "brown", "bear"])`,
|
|
|
|
cty.NumberIntVal(4),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
"list": {
|
2020-11-04 22:18:44 +01:00
|
|
|
// There are intentionally no test cases for "list" because
|
|
|
|
// it is a stub that always returns an error.
|
2019-04-29 19:11:28 +02:00
|
|
|
},
|
|
|
|
|
|
|
|
"log": {
|
|
|
|
{
|
|
|
|
`log(1, 10)`,
|
|
|
|
cty.NumberFloatVal(0),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
"lookup": {
|
|
|
|
{
|
|
|
|
`lookup({hello=1, goodbye=42}, "goodbye")`,
|
|
|
|
cty.NumberIntVal(42),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
"lower": {
|
|
|
|
{
|
|
|
|
`lower("HELLO")`,
|
|
|
|
cty.StringVal("hello"),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
"map": {
|
2020-11-04 22:18:44 +01:00
|
|
|
// There are intentionally no test cases for "map" because
|
|
|
|
// it is a stub that always returns an error.
|
2019-04-29 19:11:28 +02:00
|
|
|
},
|
|
|
|
|
|
|
|
"matchkeys": {
|
|
|
|
{
|
|
|
|
`matchkeys(["a", "b", "c"], ["ref1", "ref2", "ref3"], ["ref1"])`,
|
|
|
|
cty.ListVal([]cty.Value{
|
|
|
|
cty.StringVal("a"),
|
|
|
|
}),
|
|
|
|
},
|
2019-06-04 14:36:30 +02:00
|
|
|
{ // mixing types in searchset
|
|
|
|
`matchkeys(["a", "b", "c"], [1, 2, 3], [1, "3"])`,
|
|
|
|
cty.ListVal([]cty.Value{
|
|
|
|
cty.StringVal("a"),
|
|
|
|
cty.StringVal("c"),
|
|
|
|
}),
|
|
|
|
},
|
2019-04-29 19:11:28 +02:00
|
|
|
},
|
|
|
|
|
|
|
|
"max": {
|
|
|
|
{
|
|
|
|
`max(12, 54, 3)`,
|
|
|
|
cty.NumberIntVal(54),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
"md5": {
|
|
|
|
{
|
|
|
|
`md5("tada")`,
|
|
|
|
cty.StringVal("ce47d07243bb6eaf5e1322c81baf9bbf"),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
"merge": {
|
|
|
|
{
|
|
|
|
`merge({"a"="b"}, {"c"="d"})`,
|
|
|
|
cty.ObjectVal(map[string]cty.Value{
|
|
|
|
"a": cty.StringVal("b"),
|
|
|
|
"c": cty.StringVal("d"),
|
|
|
|
}),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
"min": {
|
|
|
|
{
|
|
|
|
`min(12, 54, 3)`,
|
|
|
|
cty.NumberIntVal(3),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
2020-12-19 00:46:30 +01:00
|
|
|
"nonsensitive": {
|
|
|
|
{
|
|
|
|
// Due to how this test is set up we have no way to get
|
|
|
|
// a sensitive value other than to generate one with
|
|
|
|
// another function, so this is a bit odd but does still
|
|
|
|
// meet the goal of verifying that the "nonsensitive"
|
|
|
|
// function is correctly registered.
|
|
|
|
`nonsensitive(sensitive(1))`,
|
|
|
|
cty.NumberIntVal(1),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
lang/funcs: "one" function
In the Terraform language we typically use lists of zero or one values in
some sense interchangably with single values that might be null, because
various Terraform language constructs are designed to work with
collections rather than with nullable values.
In Terraform v0.12 we made the splat operator [*] have a "special power"
of concisely converting from a possibly-null single value into a
zero-or-one list as a way to make that common operation more concise.
In a sense this "one" function is the opposite operation to that special
power: it goes from a zero-or-one collection (list, set, or tuple) to a
possibly-null single value.
This is a concise alternative to the following clunky conditional
expression, with the additional benefit that the following expression is
also not viable for set values, and it also properly handles the case
where there's unexpectedly more than one value:
length(var.foo) != 0 ? var.foo[0] : null
Instead, we can write:
one(var.foo)
As with the splat operator, this is a tricky tradeoff because it could be
argued that it's not something that'd be immediately intuitive to someone
unfamiliar with Terraform. However, I think that's justified given how
often zero-or-one collections arise in typical Terraform configurations.
Unlike the splat operator, it should at least be easier to search for its
name and find its documentation the first time you see it in a
configuration.
My expectation that this will become a common pattern is also my
justification for giving it a short, concise name. Arguably it could be
better named something like "oneornull", but that's a pretty clunky name
and I'm not convinced it really adds any clarity for someone who isn't
already familiar with it.
2021-01-09 01:02:56 +01:00
|
|
|
"one": {
|
|
|
|
{
|
|
|
|
`one([])`,
|
|
|
|
cty.NullVal(cty.DynamicPseudoType),
|
|
|
|
},
|
|
|
|
{
|
|
|
|
`one([true])`,
|
|
|
|
cty.True,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
2019-09-10 05:42:45 +02:00
|
|
|
"parseint": {
|
|
|
|
{
|
|
|
|
`parseint("100", 10)`,
|
|
|
|
cty.NumberIntVal(100),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
2019-04-29 19:11:28 +02:00
|
|
|
"pathexpand": {
|
|
|
|
{
|
|
|
|
`pathexpand("~/test-file")`,
|
|
|
|
cty.StringVal(filepath.Join(homePath, "test-file")),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
"pow": {
|
|
|
|
{
|
|
|
|
`pow(1,0)`,
|
|
|
|
cty.NumberFloatVal(1),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
2019-05-26 22:09:44 +02:00
|
|
|
"range": {
|
|
|
|
{
|
|
|
|
`range(3)`,
|
|
|
|
cty.ListVal([]cty.Value{
|
|
|
|
cty.NumberIntVal(0),
|
|
|
|
cty.NumberIntVal(1),
|
|
|
|
cty.NumberIntVal(2),
|
|
|
|
}),
|
|
|
|
},
|
|
|
|
{
|
|
|
|
`range(1, 4)`,
|
|
|
|
cty.ListVal([]cty.Value{
|
|
|
|
cty.NumberIntVal(1),
|
|
|
|
cty.NumberIntVal(2),
|
|
|
|
cty.NumberIntVal(3),
|
|
|
|
}),
|
|
|
|
},
|
|
|
|
{
|
|
|
|
`range(1, 8, 2)`,
|
|
|
|
cty.ListVal([]cty.Value{
|
|
|
|
cty.NumberIntVal(1),
|
|
|
|
cty.NumberIntVal(3),
|
|
|
|
cty.NumberIntVal(5),
|
|
|
|
cty.NumberIntVal(7),
|
|
|
|
}),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
2019-08-06 17:21:22 +02:00
|
|
|
"regex": {
|
|
|
|
{
|
|
|
|
`regex("(\\d+)([a-z]+)", "aaa111bbb222")`,
|
|
|
|
cty.TupleVal([]cty.Value{cty.StringVal("111"), cty.StringVal("bbb")}),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
"regexall": {
|
|
|
|
{
|
|
|
|
`regexall("(\\d+)([a-z]+)", "...111aaa222bbb...")`,
|
|
|
|
cty.ListVal([]cty.Value{
|
|
|
|
cty.TupleVal([]cty.Value{cty.StringVal("111"), cty.StringVal("aaa")}),
|
|
|
|
cty.TupleVal([]cty.Value{cty.StringVal("222"), cty.StringVal("bbb")}),
|
|
|
|
}),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
2019-04-29 19:11:28 +02:00
|
|
|
"replace": {
|
|
|
|
{
|
|
|
|
`replace("hello", "hel", "bel")`,
|
|
|
|
cty.StringVal("bello"),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
"reverse": {
|
|
|
|
{
|
|
|
|
`reverse(["a", true, 0])`,
|
|
|
|
cty.TupleVal([]cty.Value{cty.Zero, cty.True, cty.StringVal("a")}),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
"rsadecrypt": {
|
|
|
|
{
|
|
|
|
fmt.Sprintf("rsadecrypt(%#v, %#v)", CipherBase64, PrivateKey),
|
|
|
|
cty.StringVal("message"),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
2020-12-19 00:46:30 +01:00
|
|
|
"sensitive": {
|
|
|
|
{
|
|
|
|
`sensitive(1)`,
|
|
|
|
cty.NumberIntVal(1).Mark("sensitive"),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
2019-04-29 19:11:28 +02:00
|
|
|
"setintersection": {
|
|
|
|
{
|
|
|
|
`setintersection(["a", "b"], ["b", "c"], ["b", "d"])`,
|
|
|
|
cty.SetVal([]cty.Value{
|
|
|
|
cty.StringVal("b"),
|
|
|
|
}),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
"setproduct": {
|
|
|
|
{
|
|
|
|
`setproduct(["development", "staging", "production"], ["app1", "app2"])`,
|
|
|
|
cty.ListVal([]cty.Value{
|
|
|
|
cty.TupleVal([]cty.Value{cty.StringVal("development"), cty.StringVal("app1")}),
|
|
|
|
cty.TupleVal([]cty.Value{cty.StringVal("development"), cty.StringVal("app2")}),
|
|
|
|
cty.TupleVal([]cty.Value{cty.StringVal("staging"), cty.StringVal("app1")}),
|
|
|
|
cty.TupleVal([]cty.Value{cty.StringVal("staging"), cty.StringVal("app2")}),
|
|
|
|
cty.TupleVal([]cty.Value{cty.StringVal("production"), cty.StringVal("app1")}),
|
|
|
|
cty.TupleVal([]cty.Value{cty.StringVal("production"), cty.StringVal("app2")}),
|
|
|
|
}),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
2020-02-06 18:49:11 +01:00
|
|
|
"setsubtract": {
|
|
|
|
{
|
|
|
|
`setsubtract(["a", "b", "c"], ["a", "c"])`,
|
|
|
|
cty.SetVal([]cty.Value{
|
|
|
|
cty.StringVal("b"),
|
|
|
|
}),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
2019-04-29 19:11:28 +02:00
|
|
|
"setunion": {
|
|
|
|
{
|
|
|
|
`setunion(["a", "b"], ["b", "c"], ["d"])`,
|
|
|
|
cty.SetVal([]cty.Value{
|
|
|
|
cty.StringVal("d"),
|
|
|
|
cty.StringVal("b"),
|
|
|
|
cty.StringVal("a"),
|
|
|
|
cty.StringVal("c"),
|
|
|
|
}),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
"sha1": {
|
|
|
|
{
|
|
|
|
`sha1("test")`,
|
|
|
|
cty.StringVal("a94a8fe5ccb19ba61c4c0873d391e987982fbbd3"),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
"sha256": {
|
|
|
|
{
|
|
|
|
`sha256("test")`,
|
|
|
|
cty.StringVal("9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08"),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
"sha512": {
|
|
|
|
{
|
|
|
|
`sha512("test")`,
|
|
|
|
cty.StringVal("ee26b0dd4af7e749aa1a8ee3c10ae9923f618980772e473f8819a5d4940e0db27ac185f8a0e1d5f84f88bc887fd67b143732c304cc5fa9ad8e6f57f50028a8ff"),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
"signum": {
|
|
|
|
{
|
|
|
|
`signum(12)`,
|
|
|
|
cty.NumberFloatVal(1),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
"slice": {
|
|
|
|
{
|
2019-05-01 22:48:01 +02:00
|
|
|
// force a list type here for testing
|
2020-11-04 22:18:44 +01:00
|
|
|
`slice(tolist(["a", "b", "c", "d"]), 1, 3)`,
|
2019-04-29 19:11:28 +02:00
|
|
|
cty.ListVal([]cty.Value{
|
|
|
|
cty.StringVal("b"), cty.StringVal("c"),
|
|
|
|
}),
|
|
|
|
},
|
2019-05-01 22:48:01 +02:00
|
|
|
{
|
|
|
|
`slice(["a", "b", 3, 4], 1, 3)`,
|
|
|
|
cty.TupleVal([]cty.Value{
|
|
|
|
cty.StringVal("b"), cty.NumberIntVal(3),
|
|
|
|
}),
|
|
|
|
},
|
2019-04-29 19:11:28 +02:00
|
|
|
},
|
|
|
|
|
|
|
|
"sort": {
|
|
|
|
{
|
|
|
|
`sort(["banana", "apple"])`,
|
|
|
|
cty.ListVal([]cty.Value{
|
|
|
|
cty.StringVal("apple"),
|
|
|
|
cty.StringVal("banana"),
|
|
|
|
}),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
"split": {
|
|
|
|
{
|
|
|
|
`split(" ", "Hello World")`,
|
|
|
|
cty.ListVal([]cty.Value{
|
|
|
|
cty.StringVal("Hello"),
|
|
|
|
cty.StringVal("World"),
|
|
|
|
}),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
"strrev": {
|
|
|
|
{
|
|
|
|
`strrev("hello world")`,
|
|
|
|
cty.StringVal("dlrow olleh"),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
"substr": {
|
|
|
|
{
|
|
|
|
`substr("hello world", 1, 4)`,
|
|
|
|
cty.StringVal("ello"),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
2020-04-15 20:27:06 +02:00
|
|
|
"sum": {
|
|
|
|
{
|
|
|
|
`sum([2340.5,10,3])`,
|
|
|
|
cty.NumberFloatVal(2353.5),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
2020-10-21 19:56:56 +02:00
|
|
|
"textdecodebase64": {
|
|
|
|
{
|
|
|
|
`textdecodebase64("dABlAHMAdAA=", "UTF-16LE")`,
|
|
|
|
cty.StringVal("test"),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
"textencodebase64": {
|
|
|
|
{
|
|
|
|
`textencodebase64("test", "UTF-16LE")`,
|
|
|
|
cty.StringVal("dABlAHMAdAA="),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
2019-04-29 19:11:28 +02:00
|
|
|
"templatefile": {
|
|
|
|
{
|
|
|
|
`templatefile("hello.tmpl", {name = "Jodie"})`,
|
|
|
|
cty.StringVal("Hello, Jodie!"),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
"timeadd": {
|
|
|
|
{
|
|
|
|
`timeadd("2017-11-22T00:00:00Z", "1s")`,
|
|
|
|
cty.StringVal("2017-11-22T00:00:01Z"),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
"title": {
|
|
|
|
{
|
|
|
|
`title("hello")`,
|
|
|
|
cty.StringVal("Hello"),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
"tobool": {
|
|
|
|
{
|
|
|
|
`tobool("false")`,
|
|
|
|
cty.False,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
"tolist": {
|
|
|
|
{
|
|
|
|
`tolist(["a", "b", "c"])`,
|
|
|
|
cty.ListVal([]cty.Value{
|
|
|
|
cty.StringVal("a"), cty.StringVal("b"), cty.StringVal("c"),
|
|
|
|
}),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
"tomap": {
|
|
|
|
{
|
|
|
|
`tomap({"a" = 1, "b" = 2})`,
|
|
|
|
cty.MapVal(map[string]cty.Value{
|
|
|
|
"a": cty.NumberIntVal(1),
|
|
|
|
"b": cty.NumberIntVal(2),
|
|
|
|
}),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
"tonumber": {
|
|
|
|
{
|
|
|
|
`tonumber("42")`,
|
|
|
|
cty.NumberIntVal(42),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
"toset": {
|
|
|
|
{
|
|
|
|
`toset(["a", "b", "c"])`,
|
|
|
|
cty.SetVal([]cty.Value{
|
|
|
|
cty.StringVal("a"), cty.StringVal("b"), cty.StringVal("c"),
|
|
|
|
}),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
"tostring": {
|
|
|
|
{
|
|
|
|
`tostring("a")`,
|
|
|
|
cty.StringVal("a"),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
"transpose": {
|
|
|
|
{
|
|
|
|
`transpose({"a" = ["1", "2"], "b" = ["2", "3"]})`,
|
|
|
|
cty.MapVal(map[string]cty.Value{
|
|
|
|
"1": cty.ListVal([]cty.Value{cty.StringVal("a")}),
|
|
|
|
"2": cty.ListVal([]cty.Value{cty.StringVal("a"), cty.StringVal("b")}),
|
|
|
|
"3": cty.ListVal([]cty.Value{cty.StringVal("b")}),
|
|
|
|
}),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
2019-11-19 11:10:33 +01:00
|
|
|
"trim": {
|
|
|
|
{
|
|
|
|
`trim("?!hello?!", "!?")`,
|
|
|
|
cty.StringVal("hello"),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
"trimprefix": {
|
|
|
|
{
|
|
|
|
`trimprefix("helloworld", "hello")`,
|
|
|
|
cty.StringVal("world"),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
2019-04-29 19:11:28 +02:00
|
|
|
"trimspace": {
|
|
|
|
{
|
|
|
|
`trimspace(" hello ")`,
|
|
|
|
cty.StringVal("hello"),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
2019-11-19 11:10:33 +01:00
|
|
|
"trimsuffix": {
|
|
|
|
{
|
|
|
|
`trimsuffix("helloworld", "world")`,
|
|
|
|
cty.StringVal("hello"),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
2020-01-03 23:01:14 +01:00
|
|
|
"try": {
|
|
|
|
{
|
|
|
|
// Note: "try" only works with expressions that pass static
|
|
|
|
// validation, because it only gets an opportunity to run in
|
|
|
|
// that case. The following "works" (captures the error) because
|
|
|
|
// Terraform understands it as a reference to an attribute
|
|
|
|
// that does not exist during dynamic evaluation.
|
|
|
|
//
|
|
|
|
// "try" doesn't work with references that could never possibly
|
|
|
|
// be valid and are thus caught during static validation, such
|
|
|
|
// as an expression like "foo" alone which would be understood
|
|
|
|
// as an invalid resource reference. That's okay because this
|
|
|
|
// function exists primarily to ease access to dynamically-typed
|
|
|
|
// structures that Terraform can't statically validate by
|
|
|
|
// definition.
|
|
|
|
`try({}.baz, "fallback")`,
|
|
|
|
cty.StringVal("fallback"),
|
|
|
|
},
|
|
|
|
{
|
|
|
|
`try("fallback")`,
|
|
|
|
cty.StringVal("fallback"),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
2019-04-29 19:11:28 +02:00
|
|
|
"upper": {
|
|
|
|
{
|
|
|
|
`upper("hello")`,
|
|
|
|
cty.StringVal("HELLO"),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
"urlencode": {
|
|
|
|
{
|
|
|
|
`urlencode("foo:bar@localhost?foo=bar&bar=baz")`,
|
|
|
|
cty.StringVal("foo%3Abar%40localhost%3Ffoo%3Dbar%26bar%3Dbaz"),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
2019-05-08 11:13:30 +02:00
|
|
|
"uuidv5": {
|
|
|
|
{
|
|
|
|
`uuidv5("dns", "tada")`,
|
|
|
|
cty.StringVal("faa898db-9b9d-5b75-86a9-149e7bb8e3b8"),
|
|
|
|
},
|
|
|
|
{
|
|
|
|
`uuidv5("url", "tada")`,
|
|
|
|
cty.StringVal("2c1ff6b4-211f-577e-94de-d978b0caa16e"),
|
|
|
|
},
|
|
|
|
{
|
|
|
|
`uuidv5("oid", "tada")`,
|
|
|
|
cty.StringVal("61eeea26-5176-5288-87fc-232d6ed30d2f"),
|
|
|
|
},
|
|
|
|
{
|
|
|
|
`uuidv5("x500", "tada")`,
|
|
|
|
cty.StringVal("7e12415e-f7c9-57c3-9e43-52dc9950d264"),
|
|
|
|
},
|
|
|
|
{
|
|
|
|
`uuidv5("6ba7b810-9dad-11d1-80b4-00c04fd430c8", "tada")`,
|
|
|
|
cty.StringVal("faa898db-9b9d-5b75-86a9-149e7bb8e3b8"),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
2019-04-29 19:11:28 +02:00
|
|
|
"values": {
|
|
|
|
{
|
|
|
|
`values({"hello"="world", "what's"="up"})`,
|
|
|
|
cty.TupleVal([]cty.Value{
|
|
|
|
cty.StringVal("world"),
|
|
|
|
cty.StringVal("up"),
|
|
|
|
}),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
2019-05-26 19:25:18 +02:00
|
|
|
"yamldecode": {
|
|
|
|
{
|
|
|
|
`yamldecode("true")`,
|
|
|
|
cty.True,
|
|
|
|
},
|
2020-06-17 19:05:14 +02:00
|
|
|
{
|
|
|
|
`yamldecode("key: 0ba")`,
|
|
|
|
cty.ObjectVal(map[string]cty.Value{
|
|
|
|
"key": cty.StringVal("0ba"),
|
|
|
|
}),
|
|
|
|
},
|
2019-05-26 19:25:18 +02:00
|
|
|
},
|
|
|
|
|
|
|
|
"yamlencode": {
|
|
|
|
{
|
|
|
|
`yamlencode(["foo", "bar", true])`,
|
|
|
|
cty.StringVal("- \"foo\"\n- \"bar\"\n- true\n"),
|
|
|
|
},
|
|
|
|
{
|
|
|
|
`yamlencode({a = "b", c = "d"})`,
|
|
|
|
cty.StringVal("\"a\": \"b\"\n\"c\": \"d\"\n"),
|
|
|
|
},
|
|
|
|
{
|
|
|
|
`yamlencode(true)`,
|
|
|
|
// the ... here is an "end of document" marker, produced for implied primitive types only
|
|
|
|
cty.StringVal("true\n...\n"),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
2019-04-29 19:11:28 +02:00
|
|
|
"zipmap": {
|
|
|
|
{
|
|
|
|
`zipmap(["hello", "bar"], ["world", "baz"])`,
|
|
|
|
cty.ObjectVal(map[string]cty.Value{
|
|
|
|
"hello": cty.StringVal("world"),
|
|
|
|
"bar": cty.StringVal("baz"),
|
|
|
|
}),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
2020-10-31 01:19:32 +01:00
|
|
|
experimentalFuncs := map[string]experiments.Experiment{}
|
|
|
|
experimentalFuncs["defaults"] = experiments.ModuleVariableOptionalAttrs
|
2019-04-29 19:11:28 +02:00
|
|
|
|
2020-10-31 01:19:32 +01:00
|
|
|
t.Run("all functions are tested", func(t *testing.T) {
|
|
|
|
data := &dataForTests{} // no variables available; we only need literals here
|
|
|
|
scope := &Scope{
|
|
|
|
Data: data,
|
|
|
|
BaseDir: "./testdata/functions-test", // for the functions that read from the filesystem
|
|
|
|
}
|
2019-04-29 19:11:28 +02:00
|
|
|
|
2020-10-31 01:19:32 +01:00
|
|
|
// Check that there is at least one test case for each function, omitting
|
|
|
|
// those functions that do not return consistent values
|
|
|
|
allFunctions := scope.Functions()
|
|
|
|
|
|
|
|
// TODO: we can test the impure functions partially by configuring the scope
|
|
|
|
// with PureOnly: true and then verify that they return unknown values of a
|
|
|
|
// suitable type.
|
|
|
|
for _, impureFunc := range impureFunctions {
|
|
|
|
delete(allFunctions, impureFunc)
|
2019-04-29 19:11:28 +02:00
|
|
|
}
|
2020-10-31 01:19:32 +01:00
|
|
|
for f := range scope.Functions() {
|
|
|
|
if _, ok := tests[f]; !ok {
|
|
|
|
t.Errorf("Missing test for function %s\n", f)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
})
|
2019-04-29 19:11:28 +02:00
|
|
|
|
|
|
|
for funcName, funcTests := range tests {
|
|
|
|
t.Run(funcName, func(t *testing.T) {
|
2020-10-31 01:19:32 +01:00
|
|
|
|
|
|
|
// prepareScope starts as a no-op, but if a function is marked as
|
|
|
|
// experimental in our experimentalFuncs table above then we'll
|
|
|
|
// reassign this to be a function that activates the appropriate
|
|
|
|
// experiment.
|
|
|
|
prepareScope := func(t *testing.T, scope *Scope) {}
|
|
|
|
|
|
|
|
if experiment, isExperimental := experimentalFuncs[funcName]; isExperimental {
|
|
|
|
// First, we'll run all of the tests without the experiment
|
|
|
|
// enabled to see that they do actually fail in that case.
|
|
|
|
for _, test := range funcTests {
|
|
|
|
testName := fmt.Sprintf("experimental(%s)", test.src)
|
|
|
|
t.Run(testName, func(t *testing.T) {
|
|
|
|
data := &dataForTests{} // no variables available; we only need literals here
|
|
|
|
scope := &Scope{
|
|
|
|
Data: data,
|
|
|
|
BaseDir: "./testdata/functions-test", // for the functions that read from the filesystem
|
|
|
|
}
|
|
|
|
|
|
|
|
expr, parseDiags := hclsyntax.ParseExpression([]byte(test.src), "test.hcl", hcl.Pos{Line: 1, Column: 1})
|
|
|
|
if parseDiags.HasErrors() {
|
|
|
|
for _, diag := range parseDiags {
|
|
|
|
t.Error(diag.Error())
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
_, diags := scope.EvalExpr(expr, cty.DynamicPseudoType)
|
|
|
|
if !diags.HasErrors() {
|
|
|
|
t.Errorf("experimental function %q succeeded without its experiment %s enabled\nexpr: %s", funcName, experiment.Keyword(), test.src)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
// Now make the experiment active in the scope so that the
|
|
|
|
// function will actually work when we test it below.
|
|
|
|
prepareScope = func(t *testing.T, scope *Scope) {
|
|
|
|
t.Helper()
|
|
|
|
t.Logf("activating experiment %s to test %q", experiment.Keyword(), funcName)
|
|
|
|
experimentsSet := experiments.NewSet()
|
|
|
|
experimentsSet.Add(experiment)
|
|
|
|
scope.SetActiveExperiments(experimentsSet)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-04-29 19:11:28 +02:00
|
|
|
for _, test := range funcTests {
|
|
|
|
t.Run(test.src, func(t *testing.T) {
|
2020-10-31 01:19:32 +01:00
|
|
|
data := &dataForTests{} // no variables available; we only need literals here
|
|
|
|
scope := &Scope{
|
|
|
|
Data: data,
|
|
|
|
BaseDir: "./testdata/functions-test", // for the functions that read from the filesystem
|
|
|
|
}
|
|
|
|
prepareScope(t, scope)
|
|
|
|
|
2019-04-29 19:11:28 +02:00
|
|
|
expr, parseDiags := hclsyntax.ParseExpression([]byte(test.src), "test.hcl", hcl.Pos{Line: 1, Column: 1})
|
|
|
|
if parseDiags.HasErrors() {
|
|
|
|
for _, diag := range parseDiags {
|
|
|
|
t.Error(diag.Error())
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
got, diags := scope.EvalExpr(expr, cty.DynamicPseudoType)
|
|
|
|
if diags.HasErrors() {
|
|
|
|
for _, diag := range diags {
|
|
|
|
t.Errorf("%s: %s", diag.Description().Summary, diag.Description().Detail)
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if !test.want.RawEquals(got) {
|
|
|
|
t.Errorf("wrong result\nexpr: %s\ngot: %#v\nwant: %#v", test.src, got, test.want)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
const (
|
|
|
|
CipherBase64 = "eczGaDhXDbOFRZGhjx2etVzWbRqWDlmq0bvNt284JHVbwCgObiuyX9uV0LSAMY707IEgMkExJqXmsB4OWKxvB7epRB9G/3+F+pcrQpODlDuL9oDUAsa65zEpYF0Wbn7Oh7nrMQncyUPpyr9WUlALl0gRWytOA23S+y5joa4M34KFpawFgoqTu/2EEH4Xl1zo+0fy73fEto+nfkUY+meuyGZ1nUx/+DljP7ZqxHBFSlLODmtuTMdswUbHbXbWneW51D7Jm7xB8nSdiA2JQNK5+Sg5x8aNfgvFTt/m2w2+qpsyFa5Wjeu6fZmXSl840CA07aXbk9vN4I81WmJyblD/ZA=="
|
|
|
|
PrivateKey = `
|
|
|
|
-----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-----
|
|
|
|
`
|
|
|
|
Poem = `Fleas:
|
|
|
|
Adam
|
|
|
|
Had'em
|
|
|
|
|
|
|
|
E.E. Cummings`
|
|
|
|
)
|