lang/funcs: add acc tests for functions (#21112)
* lang/funcs: testing of functions through the lang package API The function-specific unit tests do not cover the HCL conversion that happens when the functions are called in a terraform configuration. For e.g., HCL converts sets to lists before passing it to the function. This means that we could not test passing a set in the function _unit_ tests. This adds a higher-level acceptance test, plus a check that every (pure) function has a test. * website/docs: update function documentation
This commit is contained in:
parent
eed605ac05
commit
394cf7f25e
|
@ -0,0 +1,12 @@
|
|||
version_info {
|
||||
commit_var = "main.GitCommit"
|
||||
version_var = "github.com/hashicorp/terraform/version.Version"
|
||||
prerelease_var = "github.com/hashicorp/terraform/version.Prerelease"
|
||||
}
|
||||
|
||||
version_exec = false
|
||||
|
||||
platform {
|
||||
os = "darwin"
|
||||
arch = "amd64"
|
||||
}
|
|
@ -257,8 +257,10 @@ var CompactFunc = function.New(&function.Spec{
|
|||
},
|
||||
})
|
||||
|
||||
// ContainsFunc contructs a function that determines whether a given list contains
|
||||
// a given single value as one of its elements.
|
||||
// ContainsFunc contructs a function that determines whether a given list
|
||||
// contains a given single value as one of its elements.
|
||||
//
|
||||
// ContainsFunc also works on sets, as HCL automatically converts sets to lists.
|
||||
var ContainsFunc = function.New(&function.Spec{
|
||||
Params: []function.Parameter{
|
||||
{
|
||||
|
|
|
@ -0,0 +1,846 @@
|
|||
package lang
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/hcl2/hcl"
|
||||
"github.com/hashicorp/hcl2/hcl/hclsyntax"
|
||||
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),
|
||||
},
|
||||
},
|
||||
|
||||
"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"),
|
||||
},
|
||||
},
|
||||
|
||||
"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"),
|
||||
},
|
||||
},
|
||||
|
||||
"coalesce": {
|
||||
{
|
||||
`coalesce("first", "second", "third")`,
|
||||
cty.StringVal("first"),
|
||||
},
|
||||
|
||||
{
|
||||
`coalescelist(["first", "second"], ["third", "fourth"])`,
|
||||
cty.ListVal([]cty.Value{
|
||||
cty.StringVal("first"), cty.StringVal("second"),
|
||||
}),
|
||||
},
|
||||
},
|
||||
|
||||
"coalescelist": {
|
||||
{
|
||||
`coalescelist(["a", "b"], ["c", "d"])`,
|
||||
cty.ListVal([]cty.Value{
|
||||
cty.StringVal("a"),
|
||||
cty.StringVal("b"),
|
||||
}),
|
||||
},
|
||||
},
|
||||
|
||||
"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"),
|
||||
}),
|
||||
}),
|
||||
},
|
||||
},
|
||||
|
||||
"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),
|
||||
},
|
||||
},
|
||||
|
||||
"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": {
|
||||
{
|
||||
`flatten([tolist(["a", "b"]), tolist(["c", "d"])])`,
|
||||
cty.ListVal([]cty.Value{
|
||||
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\"}"),
|
||||
},
|
||||
},
|
||||
|
||||
"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": {
|
||||
{
|
||||
`list("hello")`,
|
||||
cty.ListVal([]cty.Value{
|
||||
cty.StringVal("hello"),
|
||||
}),
|
||||
},
|
||||
},
|
||||
|
||||
"log": {
|
||||
{
|
||||
`log(1, 10)`,
|
||||
cty.NumberFloatVal(0),
|
||||
},
|
||||
},
|
||||
|
||||
"lookup": {
|
||||
{
|
||||
`lookup({hello=1, goodbye=42}, "goodbye")`,
|
||||
cty.NumberIntVal(42),
|
||||
},
|
||||
},
|
||||
|
||||
"lower": {
|
||||
{
|
||||
`lower("HELLO")`,
|
||||
cty.StringVal("hello"),
|
||||
},
|
||||
},
|
||||
|
||||
"map": {
|
||||
{
|
||||
`map("hello", "world")`,
|
||||
cty.MapVal(map[string]cty.Value{
|
||||
"hello": cty.StringVal("world"),
|
||||
}),
|
||||
},
|
||||
},
|
||||
|
||||
"matchkeys": {
|
||||
{
|
||||
`matchkeys(["a", "b", "c"], ["ref1", "ref2", "ref3"], ["ref1"])`,
|
||||
cty.ListVal([]cty.Value{
|
||||
cty.StringVal("a"),
|
||||
}),
|
||||
},
|
||||
},
|
||||
|
||||
"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),
|
||||
},
|
||||
},
|
||||
|
||||
"pathexpand": {
|
||||
{
|
||||
`pathexpand("~/test-file")`,
|
||||
cty.StringVal(filepath.Join(homePath, "test-file")),
|
||||
},
|
||||
},
|
||||
|
||||
"pow": {
|
||||
{
|
||||
`pow(1,0)`,
|
||||
cty.NumberFloatVal(1),
|
||||
},
|
||||
},
|
||||
|
||||
"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"),
|
||||
},
|
||||
},
|
||||
|
||||
"sethaselement": {
|
||||
{
|
||||
`sethaselement(["a", "b"], "b")`,
|
||||
cty.BoolVal(true),
|
||||
},
|
||||
},
|
||||
|
||||
"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")}),
|
||||
}),
|
||||
},
|
||||
},
|
||||
|
||||
"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": {
|
||||
{
|
||||
`slice(["a", "b", "c", "d"], 1, 3)`,
|
||||
cty.ListVal([]cty.Value{
|
||||
cty.StringVal("b"), cty.StringVal("c"),
|
||||
}),
|
||||
},
|
||||
},
|
||||
|
||||
"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"),
|
||||
},
|
||||
},
|
||||
|
||||
"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")}),
|
||||
}),
|
||||
},
|
||||
},
|
||||
|
||||
"trimspace": {
|
||||
{
|
||||
`trimspace(" hello ")`,
|
||||
cty.StringVal("hello"),
|
||||
},
|
||||
},
|
||||
|
||||
"upper": {
|
||||
{
|
||||
`upper("hello")`,
|
||||
cty.StringVal("HELLO"),
|
||||
},
|
||||
},
|
||||
|
||||
"urlencode": {
|
||||
{
|
||||
`urlencode("foo:bar@localhost?foo=bar&bar=baz")`,
|
||||
cty.StringVal("foo%3Abar%40localhost%3Ffoo%3Dbar%26bar%3Dbaz"),
|
||||
},
|
||||
},
|
||||
|
||||
"values": {
|
||||
{
|
||||
`values({"hello"="world", "what's"="up"})`,
|
||||
cty.TupleVal([]cty.Value{
|
||||
cty.StringVal("world"),
|
||||
cty.StringVal("up"),
|
||||
}),
|
||||
},
|
||||
},
|
||||
|
||||
"zipmap": {
|
||||
{
|
||||
`zipmap(["hello", "bar"], ["world", "baz"])`,
|
||||
cty.ObjectVal(map[string]cty.Value{
|
||||
"hello": cty.StringVal("world"),
|
||||
"bar": cty.StringVal("baz"),
|
||||
}),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
for f, _ := range scope.Functions() {
|
||||
if _, ok := tests[f]; !ok {
|
||||
t.Errorf("Missing test for function %s\n", f)
|
||||
}
|
||||
}
|
||||
|
||||
for funcName, funcTests := range tests {
|
||||
t.Run(funcName, func(t *testing.T) {
|
||||
for _, test := range funcTests {
|
||||
t.Run(test.src, func(t *testing.T) {
|
||||
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`
|
||||
)
|
|
@ -0,0 +1 @@
|
|||
Hello, ${name}!
|
|
@ -0,0 +1 @@
|
|||
hello!
|
|
@ -19,23 +19,23 @@ that isn't empty.
|
|||
## Examples
|
||||
|
||||
```
|
||||
> coalesce(["a", "b"], ["c", "d"])
|
||||
> coalescelist(["a", "b"], ["c", "d"])
|
||||
[
|
||||
"a",
|
||||
"b",
|
||||
]
|
||||
> coalesce([], ["c", "d"])
|
||||
> coalescelist([], ["c", "d"])
|
||||
[
|
||||
"c",
|
||||
"d",
|
||||
]
|
||||
```
|
||||
|
||||
To perform the `coalesce` operation with a list of lists, use the `...`
|
||||
To perform the `coalescelist` operation with a list of lists, use the `...`
|
||||
symbol to expand the outer list as arguments:
|
||||
|
||||
```
|
||||
> coalesce([[], ["c", "d"]]...)
|
||||
> coalescelist([[], ["c", "d"]]...)
|
||||
[
|
||||
"c",
|
||||
"d",
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
---
|
||||
layout: "functions"
|
||||
page_title: "concat - Functions - Configuration Language"
|
||||
sidebar_current: "docs-funcs-collection-concat"
|
||||
description: |-
|
||||
The concat function combines two or more lists into a single list.
|
||||
---
|
||||
|
||||
# `concat` Function
|
||||
|
||||
-> **Note:** This page is about Terraform 0.12 and later. For Terraform 0.11 and
|
||||
earlier, see
|
||||
[0.11 Configuration Language: Interpolation Syntax](../../configuration-0-11/interpolation.html).
|
||||
|
||||
`concat` takes two or more lists and combines them into a single list.
|
||||
|
||||
## Examples
|
||||
|
||||
```
|
||||
> concat(["a", ""], ["b", "c"])
|
||||
[
|
||||
"a",
|
||||
"",
|
||||
"b",
|
||||
"c",
|
||||
]
|
||||
```
|
|
@ -31,14 +31,14 @@ number of fields, or this function will produce an error.
|
|||
> csvdecode("a,b,c\n1,2,3\n4,5,6")
|
||||
[
|
||||
{
|
||||
"a" = 1
|
||||
"b" = 2
|
||||
"c" = 3
|
||||
"a" = "1"
|
||||
"b" = "2"
|
||||
"c" = "3"
|
||||
},
|
||||
{
|
||||
"a" = 4
|
||||
"b" = 5
|
||||
"c" = 6
|
||||
"a" = "4"
|
||||
"b" = "5"
|
||||
"c" = "6"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
|
|
@ -130,6 +130,10 @@
|
|||
<a href="/docs/configuration/functions/compact.html">compact</a>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<a href="/docs/configuration/functions/concat.html">concat</a>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<a href="/docs/configuration/functions/contains.html">contains</a>
|
||||
</li>
|
||||
|
|
Loading…
Reference in New Issue