From ce68b4d27cced0611e253b46a45919cc13d74a96 Mon Sep 17 00:00:00 2001 From: Martin Atkins Date: Mon, 28 Oct 2019 16:26:40 -0700 Subject: [PATCH] config: Remove legacy interpolation function implementations These are often confusing for new contributors, since this looks suspiciously like the right place to add new functions or change the behavior of existing ones. To reduce that confusion, here we remove them entirely from this package (which is now dead code in Terraform 0.12 anyway) and include in the documentation comments a pointer to the current function implementations. --- config/config_test.go | 50 - config/hcl2_shim_util.go | 134 -- config/hcl2_shim_util_test.go | 176 -- config/interpolate_funcs.go | 1717 +---------------- config/interpolate_funcs_test.go | 2995 ------------------------------ config/raw_config.go | 43 +- 6 files changed, 11 insertions(+), 5104 deletions(-) delete mode 100644 config/hcl2_shim_util.go delete mode 100644 config/hcl2_shim_util_test.go delete mode 100644 config/interpolate_funcs_test.go diff --git a/config/config_test.go b/config/config_test.go index 93ac8ece6..926c1d9b4 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -282,13 +282,6 @@ func TestConfigValidate_countInt(t *testing.T) { } } -func TestConfigValidate_countInt_HCL2(t *testing.T) { - c := testConfigHCL2(t, "validate-count-int") - if err := c.Validate(); err != nil { - t.Fatalf("unexpected error: %s", err) - } -} - func TestConfigValidate_countBadContext(t *testing.T) { c := testConfig(t, "validate-count-bad-context") @@ -320,25 +313,6 @@ func TestConfigValidate_countNotInt(t *testing.T) { } } -func TestConfigValidate_countNotInt_HCL2(t *testing.T) { - c := testConfigHCL2(t, "validate-count-not-int-const") - if err := c.Validate(); err == nil { - t.Fatal("should not be valid") - } -} - -func TestConfigValidate_countNotIntUnknown_HCL2(t *testing.T) { - c := testConfigHCL2(t, "validate-count-not-int") - // In HCL2 this is not an error because the unknown variable interpolates - // to produce an unknown string, which we assume (incorrectly, it turns out) - // will become a string containing only digits. This is okay because - // the config validation is only a "best effort" and we'll get a definitive - // result during the validation graph walk. - if err := c.Validate(); err != nil { - t.Fatalf("unexpected error: %s", err) - } -} - func TestConfigValidate_countUserVar(t *testing.T) { c := testConfig(t, "validate-count-user-var") if err := c.Validate(); err != nil { @@ -346,13 +320,6 @@ func TestConfigValidate_countUserVar(t *testing.T) { } } -func TestConfigValidate_countUserVar_HCL2(t *testing.T) { - c := testConfigHCL2(t, "validate-count-user-var") - if err := c.Validate(); err != nil { - t.Fatalf("err: %s", err) - } -} - func TestConfigValidate_countLocalValue(t *testing.T) { c := testConfig(t, "validate-local-value-count") if err := c.Validate(); err != nil { @@ -740,23 +707,6 @@ func testConfig(t *testing.T, name string) *Config { return c } -// testConfigHCL loads a config, forcing it to be processed with the HCL2 -// loader even if it doesn't explicitly opt in to the HCL2 experiment. -func testConfigHCL2(t *testing.T, name string) *Config { - t.Helper() - cer, _, err := globalHCL2Loader.loadFile(filepath.Join(fixtureDir, name, "main.tf")) - if err != nil { - t.Fatalf("failed to load %s: %s", name, err) - } - - cfg, err := cer.Config() - if err != nil { - t.Fatalf("failed to decode %s: %s", name, err) - } - - return cfg -} - func TestConfigDataCount(t *testing.T) { c := testConfig(t, "data-count") actual, err := c.Resources[0].Count() diff --git a/config/hcl2_shim_util.go b/config/hcl2_shim_util.go deleted file mode 100644 index b6550749a..000000000 --- a/config/hcl2_shim_util.go +++ /dev/null @@ -1,134 +0,0 @@ -package config - -import ( - "fmt" - - "github.com/zclconf/go-cty/cty/function/stdlib" - - "github.com/hashicorp/hil/ast" - "github.com/hashicorp/terraform/configs/hcl2shim" - - hcl2 "github.com/hashicorp/hcl/v2" - "github.com/zclconf/go-cty/cty" - "github.com/zclconf/go-cty/cty/convert" - "github.com/zclconf/go-cty/cty/function" -) - -// --------------------------------------------------------------------------- -// This file contains some helper functions that are used to shim between -// HCL2 concepts and HCL/HIL concepts, to help us mostly preserve the existing -// public API that was built around HCL/HIL-oriented approaches. -// --------------------------------------------------------------------------- - -func hcl2InterpolationFuncs() map[string]function.Function { - hcl2Funcs := map[string]function.Function{} - - for name, hilFunc := range Funcs() { - hcl2Funcs[name] = hcl2InterpolationFuncShim(hilFunc) - } - - // Some functions in the old world are dealt with inside langEvalConfig - // due to their legacy reliance on direct access to the symbol table. - // Since 0.7 they don't actually need it anymore and just ignore it, - // so we're cheating a bit here and exploiting that detail by passing nil. - hcl2Funcs["lookup"] = hcl2InterpolationFuncShim(interpolationFuncLookup(nil)) - hcl2Funcs["keys"] = hcl2InterpolationFuncShim(interpolationFuncKeys(nil)) - hcl2Funcs["values"] = hcl2InterpolationFuncShim(interpolationFuncValues(nil)) - - // As a bonus, we'll provide the JSON-handling functions from the cty - // function library since its "jsonencode" is more complete (doesn't force - // weird type conversions) and HIL's type system can't represent - // "jsondecode" at all. The result of jsondecode will eventually be forced - // to conform to the HIL type system on exit into the rest of Terraform due - // to our shimming right now, but it should be usable for decoding _within_ - // an expression. - hcl2Funcs["jsonencode"] = stdlib.JSONEncodeFunc - hcl2Funcs["jsondecode"] = stdlib.JSONDecodeFunc - - return hcl2Funcs -} - -func hcl2InterpolationFuncShim(hilFunc ast.Function) function.Function { - spec := &function.Spec{} - - for i, hilArgType := range hilFunc.ArgTypes { - spec.Params = append(spec.Params, function.Parameter{ - Type: hcl2shim.HCL2TypeForHILType(hilArgType), - Name: fmt.Sprintf("arg%d", i+1), // HIL args don't have names, so we'll fudge it - }) - } - - if hilFunc.Variadic { - spec.VarParam = &function.Parameter{ - Type: hcl2shim.HCL2TypeForHILType(hilFunc.VariadicType), - Name: "varargs", // HIL args don't have names, so we'll fudge it - } - } - - spec.Type = func(args []cty.Value) (cty.Type, error) { - return hcl2shim.HCL2TypeForHILType(hilFunc.ReturnType), nil - } - spec.Impl = func(args []cty.Value, retType cty.Type) (cty.Value, error) { - hilArgs := make([]interface{}, len(args)) - for i, arg := range args { - hilV := hcl2shim.HILVariableFromHCL2Value(arg) - - // Although the cty function system does automatic type conversions - // to match the argument types, cty doesn't distinguish int and - // float and so we may need to adjust here to ensure that the - // wrapped function gets exactly the Go type it was expecting. - var wantType ast.Type - if i < len(hilFunc.ArgTypes) { - wantType = hilFunc.ArgTypes[i] - } else { - wantType = hilFunc.VariadicType - } - switch { - case hilV.Type == ast.TypeInt && wantType == ast.TypeFloat: - hilV.Type = wantType - hilV.Value = float64(hilV.Value.(int)) - case hilV.Type == ast.TypeFloat && wantType == ast.TypeInt: - hilV.Type = wantType - hilV.Value = int(hilV.Value.(float64)) - } - - // HIL functions actually expect to have the outermost variable - // "peeled" but any nested values (in lists or maps) will - // still have their ast.Variable wrapping. - hilArgs[i] = hilV.Value - } - - hilResult, err := hilFunc.Callback(hilArgs) - if err != nil { - return cty.DynamicVal, err - } - - // Just as on the way in, we get back a partially-peeled ast.Variable - // which we need to re-wrap in order to convert it back into what - // we're calling a "config value". - rv := hcl2shim.HCL2ValueFromHILVariable(ast.Variable{ - Type: hilFunc.ReturnType, - Value: hilResult, - }) - - return convert.Convert(rv, retType) // if result is unknown we'll force the correct type here - } - return function.New(spec) -} - -func hcl2EvalWithUnknownVars(expr hcl2.Expression) (cty.Value, hcl2.Diagnostics) { - trs := expr.Variables() - vars := map[string]cty.Value{} - val := cty.DynamicVal - - for _, tr := range trs { - name := tr.RootName() - vars[name] = val - } - - ctx := &hcl2.EvalContext{ - Variables: vars, - Functions: hcl2InterpolationFuncs(), - } - return expr.Value(ctx) -} diff --git a/config/hcl2_shim_util_test.go b/config/hcl2_shim_util_test.go deleted file mode 100644 index 82052f33a..000000000 --- a/config/hcl2_shim_util_test.go +++ /dev/null @@ -1,176 +0,0 @@ -package config - -import ( - "testing" - - hcl2 "github.com/hashicorp/hcl/v2" - hcl2syntax "github.com/hashicorp/hcl/v2/hclsyntax" - "github.com/zclconf/go-cty/cty" -) - -func TestHCL2InterpolationFuncs(t *testing.T) { - // This is not a comprehensive test of all the functions (they are tested - // in interpolation_funcs_test.go already) but rather just calling a - // representative set via the HCL2 API to verify that the HCL2-to-HIL - // function shim is working as expected. - tests := []struct { - Expr string - Want cty.Value - Err bool - }{ - { - `upper("hello")`, - cty.StringVal("HELLO"), - false, - }, - { - `abs(-2)`, - cty.NumberIntVal(2), - false, - }, - { - `abs(-2.5)`, - cty.NumberFloatVal(2.5), - false, - }, - { - `cidrsubnet("")`, - cty.DynamicVal, - true, // not enough arguments - }, - { - `cidrsubnet("10.1.0.0/16", 8, 2)`, - cty.StringVal("10.1.2.0/24"), - false, - }, - { - `concat([])`, - // Since HIL doesn't maintain element type information for list - // types, HCL2 can't either without elements to sniff. - cty.ListValEmpty(cty.DynamicPseudoType), - false, - }, - { - `concat([], [])`, - cty.ListValEmpty(cty.DynamicPseudoType), - false, - }, - { - `concat(["a"], ["b", "c"])`, - cty.ListVal([]cty.Value{ - cty.StringVal("a"), - cty.StringVal("b"), - cty.StringVal("c"), - }), - false, - }, - { - `list()`, - cty.ListValEmpty(cty.DynamicPseudoType), - false, - }, - { - `list("a", "b", "c")`, - cty.ListVal([]cty.Value{ - cty.StringVal("a"), - cty.StringVal("b"), - cty.StringVal("c"), - }), - false, - }, - { - `list(list("a"), list("b"), list("c"))`, - // The types emerge here in a bit of a strange tangle because of - // the guesswork we do when trying to recover lost information from - // HIL, but the rest of the language doesn't really care whether - // we use lists or tuples here as long as we are consistent with - // the type system invariants. - cty.ListVal([]cty.Value{ - cty.TupleVal([]cty.Value{cty.StringVal("a")}), - cty.TupleVal([]cty.Value{cty.StringVal("b")}), - cty.TupleVal([]cty.Value{cty.StringVal("c")}), - }), - false, - }, - { - `list(list("a"), "b")`, - cty.DynamicVal, - true, // inconsistent types - }, - { - `length([])`, - cty.NumberIntVal(0), - false, - }, - { - `length([2])`, - cty.NumberIntVal(1), - false, - }, - { - `jsonencode(2)`, - cty.StringVal(`2`), - false, - }, - { - `jsonencode(true)`, - cty.StringVal(`true`), - false, - }, - { - `jsonencode("foo")`, - cty.StringVal(`"foo"`), - false, - }, - { - `jsonencode({})`, - cty.StringVal(`{}`), - false, - }, - { - `jsonencode([1])`, - cty.StringVal(`[1]`), - false, - }, - { - `jsondecode("{}")`, - cty.EmptyObjectVal, - false, - }, - { - `jsondecode("[5, true]")[0]`, - cty.NumberIntVal(5), - false, - }, - } - - for _, test := range tests { - t.Run(test.Expr, func(t *testing.T) { - expr, diags := hcl2syntax.ParseExpression([]byte(test.Expr), "", hcl2.Pos{Line: 1, Column: 1}) - if len(diags) != 0 { - for _, diag := range diags { - t.Logf("- %s", diag) - } - t.Fatalf("unexpected diagnostics while parsing expression") - } - - got, diags := expr.Value(&hcl2.EvalContext{ - Functions: hcl2InterpolationFuncs(), - }) - gotErr := diags.HasErrors() - if gotErr != test.Err { - if test.Err { - t.Errorf("expected errors but got none") - } else { - t.Errorf("unexpected errors") - for _, diag := range diags { - t.Logf("- %s", diag) - } - } - } - if !got.RawEquals(test.Want) { - t.Errorf("wrong result\nexpr: %s\ngot: %#v\nwant: %#v", test.Expr, got, test.Want) - } - }) - } -} diff --git a/config/interpolate_funcs.go b/config/interpolate_funcs.go index 6a2050c91..1f3f67b90 100644 --- a/config/interpolate_funcs.go +++ b/config/interpolate_funcs.go @@ -1,36 +1,9 @@ package config import ( - "bytes" - "compress/gzip" - "crypto/md5" - "crypto/rsa" - "crypto/sha1" - "crypto/sha256" - "crypto/sha512" - "crypto/x509" - "encoding/base64" - "encoding/hex" - "encoding/json" - "encoding/pem" "fmt" - "io/ioutil" - "math" - "net" - "net/url" - "path/filepath" - "regexp" - "sort" - "strconv" - "strings" - "time" - "github.com/apparentlymart/go-cidr/cidr" - "github.com/hashicorp/go-uuid" - "github.com/hashicorp/hil" "github.com/hashicorp/hil/ast" - "github.com/mitchellh/go-homedir" - "golang.org/x/crypto/bcrypt" ) // stringSliceToVariableValue converts a string slice into the value @@ -72,1690 +45,10 @@ func listVariableValueToStringSlice(values []ast.Variable) ([]string, error) { return output, nil } -// Funcs is the mapping of built-in functions for configuration. +// Funcs used to return a mapping of built-in functions for configuration. +// +// However, these function implementations are no longer used. To find the +// current function implementations, refer to ../lang/functions.go instead. func Funcs() map[string]ast.Function { - return map[string]ast.Function{ - "abs": interpolationFuncAbs(), - "basename": interpolationFuncBasename(), - "base64decode": interpolationFuncBase64Decode(), - "base64encode": interpolationFuncBase64Encode(), - "base64gzip": interpolationFuncBase64Gzip(), - "base64sha256": interpolationFuncBase64Sha256(), - "base64sha512": interpolationFuncBase64Sha512(), - "bcrypt": interpolationFuncBcrypt(), - "ceil": interpolationFuncCeil(), - "chomp": interpolationFuncChomp(), - "cidrhost": interpolationFuncCidrHost(), - "cidrnetmask": interpolationFuncCidrNetmask(), - "cidrsubnet": interpolationFuncCidrSubnet(), - "coalesce": interpolationFuncCoalesce(), - "coalescelist": interpolationFuncCoalesceList(), - "compact": interpolationFuncCompact(), - "concat": interpolationFuncConcat(), - "contains": interpolationFuncContains(), - "dirname": interpolationFuncDirname(), - "distinct": interpolationFuncDistinct(), - "element": interpolationFuncElement(), - "chunklist": interpolationFuncChunklist(), - "file": interpolationFuncFile(), - "matchkeys": interpolationFuncMatchKeys(), - "flatten": interpolationFuncFlatten(), - "floor": interpolationFuncFloor(), - "format": interpolationFuncFormat(), - "formatlist": interpolationFuncFormatList(), - "indent": interpolationFuncIndent(), - "index": interpolationFuncIndex(), - "join": interpolationFuncJoin(), - "jsonencode": interpolationFuncJSONEncode(), - "length": interpolationFuncLength(), - "list": interpolationFuncList(), - "log": interpolationFuncLog(), - "lower": interpolationFuncLower(), - "map": interpolationFuncMap(), - "max": interpolationFuncMax(), - "md5": interpolationFuncMd5(), - "merge": interpolationFuncMerge(), - "min": interpolationFuncMin(), - "pathexpand": interpolationFuncPathExpand(), - "pow": interpolationFuncPow(), - "uuid": interpolationFuncUUID(), - "replace": interpolationFuncReplace(), - "reverse": interpolationFuncReverse(), - "rsadecrypt": interpolationFuncRsaDecrypt(), - "sha1": interpolationFuncSha1(), - "sha256": interpolationFuncSha256(), - "sha512": interpolationFuncSha512(), - "signum": interpolationFuncSignum(), - "slice": interpolationFuncSlice(), - "sort": interpolationFuncSort(), - "split": interpolationFuncSplit(), - "substr": interpolationFuncSubstr(), - "timestamp": interpolationFuncTimestamp(), - "timeadd": interpolationFuncTimeAdd(), - "title": interpolationFuncTitle(), - "transpose": interpolationFuncTranspose(), - "trimspace": interpolationFuncTrimSpace(), - "upper": interpolationFuncUpper(), - "urlencode": interpolationFuncURLEncode(), - "zipmap": interpolationFuncZipMap(), - } -} - -// interpolationFuncList creates a list from the parameters passed -// to it. -func interpolationFuncList() ast.Function { - return ast.Function{ - ArgTypes: []ast.Type{}, - ReturnType: ast.TypeList, - Variadic: true, - VariadicType: ast.TypeAny, - Callback: func(args []interface{}) (interface{}, error) { - var outputList []ast.Variable - - for i, val := range args { - switch v := val.(type) { - case string: - outputList = append(outputList, ast.Variable{Type: ast.TypeString, Value: v}) - case []ast.Variable: - outputList = append(outputList, ast.Variable{Type: ast.TypeList, Value: v}) - case map[string]ast.Variable: - outputList = append(outputList, ast.Variable{Type: ast.TypeMap, Value: v}) - default: - return nil, fmt.Errorf("unexpected type %T for argument %d in list", v, i) - } - } - - // we don't support heterogeneous types, so make sure all types match the first - if len(outputList) > 0 { - firstType := outputList[0].Type - for i, v := range outputList[1:] { - if v.Type != firstType { - return nil, fmt.Errorf("unexpected type %s for argument %d in list", v.Type, i+1) - } - } - } - - return outputList, nil - }, - } -} - -// interpolationFuncMap creates a map from the parameters passed -// to it. -func interpolationFuncMap() ast.Function { - return ast.Function{ - ArgTypes: []ast.Type{}, - ReturnType: ast.TypeMap, - Variadic: true, - VariadicType: ast.TypeAny, - Callback: func(args []interface{}) (interface{}, error) { - outputMap := make(map[string]ast.Variable) - - if len(args)%2 != 0 { - return nil, fmt.Errorf("requires an even number of arguments, got %d", len(args)) - } - - var firstType *ast.Type - for i := 0; i < len(args); i += 2 { - key, ok := args[i].(string) - if !ok { - return nil, fmt.Errorf("argument %d represents a key, so it must be a string", i+1) - } - val := args[i+1] - variable, err := hil.InterfaceToVariable(val) - if err != nil { - return nil, err - } - // Enforce map type homogeneity - if firstType == nil { - firstType = &variable.Type - } else if variable.Type != *firstType { - return nil, fmt.Errorf("all map values must have the same type, got %s then %s", firstType.Printable(), variable.Type.Printable()) - } - // Check for duplicate keys - if _, ok := outputMap[key]; ok { - return nil, fmt.Errorf("argument %d is a duplicate key: %q", i+1, key) - } - outputMap[key] = variable - } - - return outputMap, nil - }, - } -} - -// interpolationFuncCompact strips a list of multi-variable values -// (e.g. as returned by "split") of any empty strings. -func interpolationFuncCompact() ast.Function { - return ast.Function{ - ArgTypes: []ast.Type{ast.TypeList}, - ReturnType: ast.TypeList, - Variadic: false, - Callback: func(args []interface{}) (interface{}, error) { - inputList := args[0].([]ast.Variable) - - var outputList []string - for _, val := range inputList { - strVal, ok := val.Value.(string) - if !ok { - return nil, fmt.Errorf( - "compact() may only be used with flat lists, this list contains elements of %s", - val.Type.Printable()) - } - if strVal == "" { - continue - } - - outputList = append(outputList, strVal) - } - return stringSliceToVariableValue(outputList), nil - }, - } -} - -// interpolationFuncCidrHost implements the "cidrhost" function that -// fills in the host part of a CIDR range address to create a single -// host address -func interpolationFuncCidrHost() ast.Function { - return ast.Function{ - ArgTypes: []ast.Type{ - ast.TypeString, // starting CIDR mask - ast.TypeInt, // host number to insert - }, - ReturnType: ast.TypeString, - Variadic: false, - Callback: func(args []interface{}) (interface{}, error) { - hostNum := args[1].(int) - _, network, err := net.ParseCIDR(args[0].(string)) - if err != nil { - return nil, fmt.Errorf("invalid CIDR expression: %s", err) - } - - ip, err := cidr.Host(network, hostNum) - if err != nil { - return nil, err - } - - return ip.String(), nil - }, - } -} - -// interpolationFuncCidrNetmask implements the "cidrnetmask" function -// that returns the subnet mask in IP address notation. -func interpolationFuncCidrNetmask() ast.Function { - return ast.Function{ - ArgTypes: []ast.Type{ - ast.TypeString, // CIDR mask - }, - ReturnType: ast.TypeString, - Variadic: false, - Callback: func(args []interface{}) (interface{}, error) { - _, network, err := net.ParseCIDR(args[0].(string)) - if err != nil { - return nil, fmt.Errorf("invalid CIDR expression: %s", err) - } - - return net.IP(network.Mask).String(), nil - }, - } -} - -// interpolationFuncCidrSubnet implements the "cidrsubnet" function that -// adds an additional subnet of the given length onto an existing -// IP block expressed in CIDR notation. -func interpolationFuncCidrSubnet() ast.Function { - return ast.Function{ - ArgTypes: []ast.Type{ - ast.TypeString, // starting CIDR mask - ast.TypeInt, // number of bits to extend the prefix - ast.TypeInt, // network number to append to the prefix - }, - ReturnType: ast.TypeString, - Variadic: false, - Callback: func(args []interface{}) (interface{}, error) { - extraBits := args[1].(int) - subnetNum := args[2].(int) - _, network, err := net.ParseCIDR(args[0].(string)) - if err != nil { - return nil, fmt.Errorf("invalid CIDR expression: %s", err) - } - - // For portability with 32-bit systems where the subnet number - // will be a 32-bit int, we only allow extension of 32 bits in - // one call even if we're running on a 64-bit machine. - // (Of course, this is significant only for IPv6.) - if extraBits > 32 { - return nil, fmt.Errorf("may not extend prefix by more than 32 bits") - } - - newNetwork, err := cidr.Subnet(network, extraBits, subnetNum) - if err != nil { - return nil, err - } - - return newNetwork.String(), nil - }, - } -} - -// interpolationFuncCoalesce implements the "coalesce" function that -// returns the first non null / empty string from the provided input -func interpolationFuncCoalesce() ast.Function { - return ast.Function{ - ArgTypes: []ast.Type{ast.TypeString}, - ReturnType: ast.TypeString, - Variadic: true, - VariadicType: ast.TypeString, - Callback: func(args []interface{}) (interface{}, error) { - if len(args) < 2 { - return nil, fmt.Errorf("must provide at least two arguments") - } - for _, arg := range args { - argument := arg.(string) - - if argument != "" { - return argument, nil - } - } - return "", nil - }, - } -} - -// interpolationFuncCoalesceList implements the "coalescelist" function that -// returns the first non empty list from the provided input -func interpolationFuncCoalesceList() ast.Function { - return ast.Function{ - ArgTypes: []ast.Type{ast.TypeList}, - ReturnType: ast.TypeList, - Variadic: true, - VariadicType: ast.TypeList, - Callback: func(args []interface{}) (interface{}, error) { - if len(args) < 2 { - return nil, fmt.Errorf("must provide at least two arguments") - } - for _, arg := range args { - argument := arg.([]ast.Variable) - - if len(argument) > 0 { - return argument, nil - } - } - return make([]ast.Variable, 0), nil - }, - } -} - -// interpolationFuncContains returns true if an element is in the list -// and return false otherwise -func interpolationFuncContains() ast.Function { - return ast.Function{ - ArgTypes: []ast.Type{ast.TypeList, ast.TypeString}, - ReturnType: ast.TypeBool, - Callback: func(args []interface{}) (interface{}, error) { - _, err := interpolationFuncIndex().Callback(args) - if err != nil { - return false, nil - } - return true, nil - }, - } -} - -// interpolationFuncConcat implements the "concat" function that concatenates -// multiple lists. -func interpolationFuncConcat() ast.Function { - return ast.Function{ - ArgTypes: []ast.Type{ast.TypeList}, - ReturnType: ast.TypeList, - Variadic: true, - VariadicType: ast.TypeList, - Callback: func(args []interface{}) (interface{}, error) { - var outputList []ast.Variable - - for _, arg := range args { - for _, v := range arg.([]ast.Variable) { - switch v.Type { - case ast.TypeString: - outputList = append(outputList, v) - case ast.TypeList: - outputList = append(outputList, v) - case ast.TypeMap: - outputList = append(outputList, v) - default: - return nil, fmt.Errorf("concat() does not support lists of %s", v.Type.Printable()) - } - } - } - - // we don't support heterogeneous types, so make sure all types match the first - if len(outputList) > 0 { - firstType := outputList[0].Type - for _, v := range outputList[1:] { - if v.Type != firstType { - return nil, fmt.Errorf("unexpected %s in list of %s", v.Type.Printable(), firstType.Printable()) - } - } - } - - return outputList, nil - }, - } -} - -// interpolationFuncPow returns base x exponential of y. -func interpolationFuncPow() ast.Function { - return ast.Function{ - ArgTypes: []ast.Type{ast.TypeFloat, ast.TypeFloat}, - ReturnType: ast.TypeFloat, - Callback: func(args []interface{}) (interface{}, error) { - return math.Pow(args[0].(float64), args[1].(float64)), nil - }, - } -} - -// interpolationFuncFile implements the "file" function that allows -// loading contents from a file. -func interpolationFuncFile() ast.Function { - return ast.Function{ - ArgTypes: []ast.Type{ast.TypeString}, - ReturnType: ast.TypeString, - Callback: func(args []interface{}) (interface{}, error) { - path, err := homedir.Expand(args[0].(string)) - if err != nil { - return "", err - } - data, err := ioutil.ReadFile(path) - if err != nil { - return "", err - } - - return string(data), nil - }, - } -} - -// interpolationFuncFormat implements the "format" function that does -// string formatting. -func interpolationFuncFormat() ast.Function { - return ast.Function{ - ArgTypes: []ast.Type{ast.TypeString}, - Variadic: true, - VariadicType: ast.TypeAny, - ReturnType: ast.TypeString, - Callback: func(args []interface{}) (interface{}, error) { - format := args[0].(string) - return fmt.Sprintf(format, args[1:]...), nil - }, - } -} - -// interpolationFuncMax returns the maximum of the numeric arguments -func interpolationFuncMax() ast.Function { - return ast.Function{ - ArgTypes: []ast.Type{ast.TypeFloat}, - ReturnType: ast.TypeFloat, - Variadic: true, - VariadicType: ast.TypeFloat, - Callback: func(args []interface{}) (interface{}, error) { - max := args[0].(float64) - - for i := 1; i < len(args); i++ { - max = math.Max(max, args[i].(float64)) - } - - return max, nil - }, - } -} - -// interpolationFuncMin returns the minimum of the numeric arguments -func interpolationFuncMin() ast.Function { - return ast.Function{ - ArgTypes: []ast.Type{ast.TypeFloat}, - ReturnType: ast.TypeFloat, - Variadic: true, - VariadicType: ast.TypeFloat, - Callback: func(args []interface{}) (interface{}, error) { - min := args[0].(float64) - - for i := 1; i < len(args); i++ { - min = math.Min(min, args[i].(float64)) - } - - return min, nil - }, - } -} - -// interpolationFuncPathExpand will expand any `~`'s found with the full file path -func interpolationFuncPathExpand() ast.Function { - return ast.Function{ - ArgTypes: []ast.Type{ast.TypeString}, - ReturnType: ast.TypeString, - Callback: func(args []interface{}) (interface{}, error) { - return homedir.Expand(args[0].(string)) - }, - } -} - -// interpolationFuncCeil returns the the least integer value greater than or equal to the argument -func interpolationFuncCeil() ast.Function { - return ast.Function{ - ArgTypes: []ast.Type{ast.TypeFloat}, - ReturnType: ast.TypeInt, - Callback: func(args []interface{}) (interface{}, error) { - return int(math.Ceil(args[0].(float64))), nil - }, - } -} - -// interpolationFuncLog returns the logarithnm. -func interpolationFuncLog() ast.Function { - return ast.Function{ - ArgTypes: []ast.Type{ast.TypeFloat, ast.TypeFloat}, - ReturnType: ast.TypeFloat, - Callback: func(args []interface{}) (interface{}, error) { - return math.Log(args[0].(float64)) / math.Log(args[1].(float64)), nil - }, - } -} - -// interpolationFuncChomp removes trailing newlines from the given string -func interpolationFuncChomp() ast.Function { - newlines := regexp.MustCompile(`(?:\r\n?|\n)*\z`) - return ast.Function{ - ArgTypes: []ast.Type{ast.TypeString}, - ReturnType: ast.TypeString, - Callback: func(args []interface{}) (interface{}, error) { - return newlines.ReplaceAllString(args[0].(string), ""), nil - }, - } -} - -// interpolationFuncFloorreturns returns the greatest integer value less than or equal to the argument -func interpolationFuncFloor() ast.Function { - return ast.Function{ - ArgTypes: []ast.Type{ast.TypeFloat}, - ReturnType: ast.TypeInt, - Callback: func(args []interface{}) (interface{}, error) { - return int(math.Floor(args[0].(float64))), nil - }, - } -} - -func interpolationFuncZipMap() ast.Function { - return ast.Function{ - ArgTypes: []ast.Type{ - ast.TypeList, // Keys - ast.TypeList, // Values - }, - ReturnType: ast.TypeMap, - Callback: func(args []interface{}) (interface{}, error) { - keys := args[0].([]ast.Variable) - values := args[1].([]ast.Variable) - - if len(keys) != len(values) { - return nil, fmt.Errorf("count of keys (%d) does not match count of values (%d)", - len(keys), len(values)) - } - - for i, val := range keys { - if val.Type != ast.TypeString { - return nil, fmt.Errorf("keys must be strings. value at position %d is %s", - i, val.Type.Printable()) - } - } - - result := map[string]ast.Variable{} - for i := 0; i < len(keys); i++ { - result[keys[i].Value.(string)] = values[i] - } - - return result, nil - }, - } -} - -// interpolationFuncFormatList implements the "formatlist" function that does -// string formatting on lists. -func interpolationFuncFormatList() ast.Function { - return ast.Function{ - ArgTypes: []ast.Type{ast.TypeAny}, - Variadic: true, - VariadicType: ast.TypeAny, - ReturnType: ast.TypeList, - Callback: func(args []interface{}) (interface{}, error) { - // Make a copy of the variadic part of args - // to avoid modifying the original. - varargs := make([]interface{}, len(args)-1) - copy(varargs, args[1:]) - - // Verify we have some arguments - if len(varargs) == 0 { - return nil, fmt.Errorf("no arguments to formatlist") - } - - // Convert arguments that are lists into slices. - // Confirm along the way that all lists have the same length (n). - var n int - listSeen := false - for i := 1; i < len(args); i++ { - s, ok := args[i].([]ast.Variable) - if !ok { - continue - } - - // Mark that we've seen at least one list - listSeen = true - - // Convert the ast.Variable to a slice of strings - parts, err := listVariableValueToStringSlice(s) - if err != nil { - return nil, err - } - - // otherwise the list is sent down to be indexed - varargs[i-1] = parts - - // Check length - if n == 0 { - // first list we've seen - n = len(parts) - continue - } - if n != len(parts) { - return nil, fmt.Errorf("format: mismatched list lengths: %d != %d", n, len(parts)) - } - } - - // If we didn't see a list this is an error because we - // can't determine the return value length. - if !listSeen { - return nil, fmt.Errorf( - "formatlist requires at least one list argument") - } - - // Do the formatting. - format := args[0].(string) - - // Generate a list of formatted strings. - list := make([]string, n) - fmtargs := make([]interface{}, len(varargs)) - for i := 0; i < n; i++ { - for j, arg := range varargs { - switch arg := arg.(type) { - default: - fmtargs[j] = arg - case []string: - fmtargs[j] = arg[i] - } - } - list[i] = fmt.Sprintf(format, fmtargs...) - } - return stringSliceToVariableValue(list), nil - }, - } -} - -// interpolationFuncIndent indents a multi-line string with the -// specified number of spaces -func interpolationFuncIndent() ast.Function { - return ast.Function{ - ArgTypes: []ast.Type{ast.TypeInt, ast.TypeString}, - ReturnType: ast.TypeString, - Callback: func(args []interface{}) (interface{}, error) { - spaces := args[0].(int) - data := args[1].(string) - pad := strings.Repeat(" ", spaces) - return strings.Replace(data, "\n", "\n"+pad, -1), nil - }, - } -} - -// interpolationFuncIndex implements the "index" function that allows one to -// find the index of a specific element in a list -func interpolationFuncIndex() ast.Function { - return ast.Function{ - ArgTypes: []ast.Type{ast.TypeList, ast.TypeString}, - ReturnType: ast.TypeInt, - Callback: func(args []interface{}) (interface{}, error) { - haystack := args[0].([]ast.Variable) - needle := args[1].(string) - for index, element := range haystack { - if needle == element.Value { - return index, nil - } - } - return nil, fmt.Errorf("Could not find '%s' in '%s'", needle, haystack) - }, - } -} - -// interpolationFuncBasename implements the "dirname" function. -func interpolationFuncDirname() ast.Function { - return ast.Function{ - ArgTypes: []ast.Type{ast.TypeString}, - ReturnType: ast.TypeString, - Callback: func(args []interface{}) (interface{}, error) { - return filepath.Dir(args[0].(string)), nil - }, - } -} - -// interpolationFuncDistinct implements the "distinct" function that -// removes duplicate elements from a list. -func interpolationFuncDistinct() ast.Function { - return ast.Function{ - ArgTypes: []ast.Type{ast.TypeList}, - ReturnType: ast.TypeList, - Variadic: true, - VariadicType: ast.TypeList, - Callback: func(args []interface{}) (interface{}, error) { - var list []string - - if len(args) != 1 { - return nil, fmt.Errorf("accepts only one argument.") - } - - if argument, ok := args[0].([]ast.Variable); ok { - for _, element := range argument { - if element.Type != ast.TypeString { - return nil, fmt.Errorf( - "only works for flat lists, this list contains elements of %s", - element.Type.Printable()) - } - list = appendIfMissing(list, element.Value.(string)) - } - } - - return stringSliceToVariableValue(list), nil - }, - } -} - -// helper function to add an element to a list, if it does not already exsit -func appendIfMissing(slice []string, element string) []string { - for _, ele := range slice { - if ele == element { - return slice - } - } - return append(slice, element) -} - -// for two lists `keys` and `values` of equal length, returns all elements -// from `values` where the corresponding element from `keys` is in `searchset`. -func interpolationFuncMatchKeys() ast.Function { - return ast.Function{ - ArgTypes: []ast.Type{ast.TypeList, ast.TypeList, ast.TypeList}, - ReturnType: ast.TypeList, - Callback: func(args []interface{}) (interface{}, error) { - output := make([]ast.Variable, 0) - - values, _ := args[0].([]ast.Variable) - keys, _ := args[1].([]ast.Variable) - searchset, _ := args[2].([]ast.Variable) - - if len(keys) != len(values) { - return nil, fmt.Errorf("length of keys and values should be equal") - } - - for i, key := range keys { - for _, search := range searchset { - if res, err := compareSimpleVariables(key, search); err != nil { - return nil, err - } else if res == true { - output = append(output, values[i]) - break - } - } - } - // if searchset is empty, then output is an empty list as well. - // if we haven't matched any key, then output is an empty list. - return output, nil - }, - } -} - -// compare two variables of the same type, i.e. non complex one, such as TypeList or TypeMap -func compareSimpleVariables(a, b ast.Variable) (bool, error) { - if a.Type != b.Type { - return false, fmt.Errorf( - "won't compare items of different types %s and %s", - a.Type.Printable(), b.Type.Printable()) - } - switch a.Type { - case ast.TypeString: - return a.Value.(string) == b.Value.(string), nil - default: - return false, fmt.Errorf( - "can't compare items of type %s", - a.Type.Printable()) - } -} - -// interpolationFuncJoin implements the "join" function that allows -// multi-variable values to be joined by some character. -func interpolationFuncJoin() ast.Function { - return ast.Function{ - ArgTypes: []ast.Type{ast.TypeString}, - Variadic: true, - VariadicType: ast.TypeList, - ReturnType: ast.TypeString, - Callback: func(args []interface{}) (interface{}, error) { - var list []string - - if len(args) < 2 { - return nil, fmt.Errorf("not enough arguments to join()") - } - - for _, arg := range args[1:] { - for _, part := range arg.([]ast.Variable) { - if part.Type != ast.TypeString { - return nil, fmt.Errorf( - "only works on flat lists, this list contains elements of %s", - part.Type.Printable()) - } - list = append(list, part.Value.(string)) - } - } - - return strings.Join(list, args[0].(string)), nil - }, - } -} - -// interpolationFuncJSONEncode implements the "jsonencode" function that encodes -// a string, list, or map as its JSON representation. -func interpolationFuncJSONEncode() ast.Function { - return ast.Function{ - ArgTypes: []ast.Type{ast.TypeAny}, - ReturnType: ast.TypeString, - Callback: func(args []interface{}) (interface{}, error) { - var toEncode interface{} - - switch typedArg := args[0].(type) { - case string: - toEncode = typedArg - - case []ast.Variable: - strings := make([]string, len(typedArg)) - - for i, v := range typedArg { - if v.Type != ast.TypeString { - variable, _ := hil.InterfaceToVariable(typedArg) - toEncode, _ = hil.VariableToInterface(variable) - - jEnc, err := json.Marshal(toEncode) - if err != nil { - return "", fmt.Errorf("failed to encode JSON data '%s'", toEncode) - } - return string(jEnc), nil - - } - strings[i] = v.Value.(string) - } - toEncode = strings - - case map[string]ast.Variable: - stringMap := make(map[string]string) - for k, v := range typedArg { - if v.Type != ast.TypeString { - variable, _ := hil.InterfaceToVariable(typedArg) - toEncode, _ = hil.VariableToInterface(variable) - - jEnc, err := json.Marshal(toEncode) - if err != nil { - return "", fmt.Errorf("failed to encode JSON data '%s'", toEncode) - } - return string(jEnc), nil - } - stringMap[k] = v.Value.(string) - } - toEncode = stringMap - - default: - return "", fmt.Errorf("unknown type for JSON encoding: %T", args[0]) - } - - jEnc, err := json.Marshal(toEncode) - if err != nil { - return "", fmt.Errorf("failed to encode JSON data '%s'", toEncode) - } - return string(jEnc), nil - }, - } -} - -// interpolationFuncReplace implements the "replace" function that does -// string replacement. -func interpolationFuncReplace() ast.Function { - return ast.Function{ - ArgTypes: []ast.Type{ast.TypeString, ast.TypeString, ast.TypeString}, - ReturnType: ast.TypeString, - Callback: func(args []interface{}) (interface{}, error) { - s := args[0].(string) - search := args[1].(string) - replace := args[2].(string) - - // We search/replace using a regexp if the string is surrounded - // in forward slashes. - if len(search) > 1 && search[0] == '/' && search[len(search)-1] == '/' { - re, err := regexp.Compile(search[1 : len(search)-1]) - if err != nil { - return nil, err - } - - return re.ReplaceAllString(s, replace), nil - } - - return strings.Replace(s, search, replace, -1), nil - }, - } -} - -// interpolationFuncReverse implements the "reverse" function that does list reversal -func interpolationFuncReverse() ast.Function { - return ast.Function{ - ArgTypes: []ast.Type{ast.TypeList}, - ReturnType: ast.TypeList, - Variadic: false, - Callback: func(args []interface{}) (interface{}, error) { - inputList := args[0].([]ast.Variable) - - reversedList := make([]ast.Variable, len(inputList)) - for idx := range inputList { - reversedList[len(inputList)-1-idx] = inputList[idx] - } - - return reversedList, nil - }, - } -} - -func interpolationFuncLength() ast.Function { - return ast.Function{ - ArgTypes: []ast.Type{ast.TypeAny}, - ReturnType: ast.TypeInt, - Variadic: false, - Callback: func(args []interface{}) (interface{}, error) { - subject := args[0] - - switch typedSubject := subject.(type) { - case string: - return len(typedSubject), nil - case []ast.Variable: - return len(typedSubject), nil - case map[string]ast.Variable: - return len(typedSubject), nil - } - - return 0, fmt.Errorf("arguments to length() must be a string, list, or map") - }, - } -} - -func interpolationFuncSignum() ast.Function { - return ast.Function{ - ArgTypes: []ast.Type{ast.TypeInt}, - ReturnType: ast.TypeInt, - Variadic: false, - Callback: func(args []interface{}) (interface{}, error) { - num := args[0].(int) - switch { - case num < 0: - return -1, nil - case num > 0: - return +1, nil - default: - return 0, nil - } - }, - } -} - -// interpolationFuncSlice returns a portion of the input list between from, inclusive and to, exclusive. -func interpolationFuncSlice() ast.Function { - return ast.Function{ - ArgTypes: []ast.Type{ - ast.TypeList, // inputList - ast.TypeInt, // from - ast.TypeInt, // to - }, - ReturnType: ast.TypeList, - Variadic: false, - Callback: func(args []interface{}) (interface{}, error) { - inputList := args[0].([]ast.Variable) - from := args[1].(int) - to := args[2].(int) - - if from < 0 { - return nil, fmt.Errorf("from index must be >= 0") - } - if to > len(inputList) { - return nil, fmt.Errorf("to index must be <= length of the input list") - } - if from > to { - return nil, fmt.Errorf("from index must be <= to index") - } - - var outputList []ast.Variable - for i, val := range inputList { - if i >= from && i < to { - outputList = append(outputList, val) - } - } - return outputList, nil - }, - } -} - -// interpolationFuncSort sorts a list of a strings lexographically -func interpolationFuncSort() ast.Function { - return ast.Function{ - ArgTypes: []ast.Type{ast.TypeList}, - ReturnType: ast.TypeList, - Variadic: false, - Callback: func(args []interface{}) (interface{}, error) { - inputList := args[0].([]ast.Variable) - - // Ensure that all the list members are strings and - // create a string slice from them - members := make([]string, len(inputList)) - for i, val := range inputList { - if val.Type != ast.TypeString { - return nil, fmt.Errorf( - "sort() may only be used with lists of strings - %s at index %d", - val.Type.String(), i) - } - - members[i] = val.Value.(string) - } - - sort.Strings(members) - return stringSliceToVariableValue(members), nil - }, - } -} - -// interpolationFuncSplit implements the "split" function that allows -// strings to split into multi-variable values -func interpolationFuncSplit() ast.Function { - return ast.Function{ - ArgTypes: []ast.Type{ast.TypeString, ast.TypeString}, - ReturnType: ast.TypeList, - Callback: func(args []interface{}) (interface{}, error) { - sep := args[0].(string) - s := args[1].(string) - elements := strings.Split(s, sep) - return stringSliceToVariableValue(elements), nil - }, - } -} - -// interpolationFuncLookup implements the "lookup" function that allows -// dynamic lookups of map types within a Terraform configuration. -func interpolationFuncLookup(vs map[string]ast.Variable) ast.Function { - return ast.Function{ - ArgTypes: []ast.Type{ast.TypeMap, ast.TypeString}, - ReturnType: ast.TypeString, - Variadic: true, - VariadicType: ast.TypeString, - Callback: func(args []interface{}) (interface{}, error) { - defaultValue := "" - defaultValueSet := false - if len(args) > 2 { - defaultValue = args[2].(string) - defaultValueSet = true - } - if len(args) > 3 { - return "", fmt.Errorf("lookup() takes no more than three arguments") - } - index := args[1].(string) - mapVar := args[0].(map[string]ast.Variable) - - v, ok := mapVar[index] - if !ok { - if defaultValueSet { - return defaultValue, nil - } else { - return "", fmt.Errorf( - "lookup failed to find '%s'", - args[1].(string)) - } - } - if v.Type != ast.TypeString { - return nil, fmt.Errorf( - "lookup() may only be used with flat maps, this map contains elements of %s", - v.Type.Printable()) - } - - return v.Value.(string), nil - }, - } -} - -// interpolationFuncElement implements the "element" function that allows -// a specific index to be looked up in a multi-variable value. Note that this will -// wrap if the index is larger than the number of elements in the multi-variable value. -func interpolationFuncElement() ast.Function { - return ast.Function{ - ArgTypes: []ast.Type{ast.TypeList, ast.TypeString}, - ReturnType: ast.TypeString, - Callback: func(args []interface{}) (interface{}, error) { - list := args[0].([]ast.Variable) - if len(list) == 0 { - return nil, fmt.Errorf("element() may not be used with an empty list") - } - - index, err := strconv.Atoi(args[1].(string)) - if err != nil || index < 0 { - return "", fmt.Errorf( - "invalid number for index, got %s", args[1]) - } - - resolvedIndex := index % len(list) - - v := list[resolvedIndex] - if v.Type != ast.TypeString { - return nil, fmt.Errorf( - "element() may only be used with flat lists, this list contains elements of %s", - v.Type.Printable()) - } - return v.Value, nil - }, - } -} - -// returns the `list` items chunked by `size`. -func interpolationFuncChunklist() ast.Function { - return ast.Function{ - ArgTypes: []ast.Type{ - ast.TypeList, // inputList - ast.TypeInt, // size - }, - ReturnType: ast.TypeList, - Callback: func(args []interface{}) (interface{}, error) { - output := make([]ast.Variable, 0) - - values, _ := args[0].([]ast.Variable) - size, _ := args[1].(int) - - // errors if size is negative - if size < 0 { - return nil, fmt.Errorf("The size argument must be positive") - } - - // if size is 0, returns a list made of the initial list - if size == 0 { - output = append(output, ast.Variable{ - Type: ast.TypeList, - Value: values, - }) - return output, nil - } - - variables := make([]ast.Variable, 0) - chunk := ast.Variable{ - Type: ast.TypeList, - Value: variables, - } - l := len(values) - for i, v := range values { - variables = append(variables, v) - - // Chunk when index isn't 0, or when reaching the values's length - if (i+1)%size == 0 || (i+1) == l { - chunk.Value = variables - output = append(output, chunk) - variables = make([]ast.Variable, 0) - } - } - - return output, nil - }, - } -} - -// interpolationFuncKeys implements the "keys" function that yields a list of -// keys of map types within a Terraform configuration. -func interpolationFuncKeys(vs map[string]ast.Variable) ast.Function { - return ast.Function{ - ArgTypes: []ast.Type{ast.TypeMap}, - ReturnType: ast.TypeList, - Callback: func(args []interface{}) (interface{}, error) { - mapVar := args[0].(map[string]ast.Variable) - keys := make([]string, 0) - - for k, _ := range mapVar { - keys = append(keys, k) - } - - sort.Strings(keys) - - // Keys are guaranteed to be strings - return stringSliceToVariableValue(keys), nil - }, - } -} - -// interpolationFuncValues implements the "values" function that yields a list of -// keys of map types within a Terraform configuration. -func interpolationFuncValues(vs map[string]ast.Variable) ast.Function { - return ast.Function{ - ArgTypes: []ast.Type{ast.TypeMap}, - ReturnType: ast.TypeList, - Callback: func(args []interface{}) (interface{}, error) { - mapVar := args[0].(map[string]ast.Variable) - keys := make([]string, 0) - - for k, _ := range mapVar { - keys = append(keys, k) - } - - sort.Strings(keys) - - values := make([]string, len(keys)) - for index, key := range keys { - if value, ok := mapVar[key].Value.(string); ok { - values[index] = value - } else { - return "", fmt.Errorf("values(): %q has element with bad type %s", - key, mapVar[key].Type) - } - } - - variable, err := hil.InterfaceToVariable(values) - if err != nil { - return nil, err - } - - return variable.Value, nil - }, - } -} - -// interpolationFuncBasename implements the "basename" function. -func interpolationFuncBasename() ast.Function { - return ast.Function{ - ArgTypes: []ast.Type{ast.TypeString}, - ReturnType: ast.TypeString, - Callback: func(args []interface{}) (interface{}, error) { - return filepath.Base(args[0].(string)), nil - }, - } -} - -// interpolationFuncBase64Encode implements the "base64encode" function that -// allows Base64 encoding. -func interpolationFuncBase64Encode() ast.Function { - return ast.Function{ - ArgTypes: []ast.Type{ast.TypeString}, - ReturnType: ast.TypeString, - Callback: func(args []interface{}) (interface{}, error) { - s := args[0].(string) - return base64.StdEncoding.EncodeToString([]byte(s)), nil - }, - } -} - -// interpolationFuncBase64Decode implements the "base64decode" function that -// allows Base64 decoding. -func interpolationFuncBase64Decode() ast.Function { - return ast.Function{ - ArgTypes: []ast.Type{ast.TypeString}, - ReturnType: ast.TypeString, - Callback: func(args []interface{}) (interface{}, error) { - s := args[0].(string) - sDec, err := base64.StdEncoding.DecodeString(s) - if err != nil { - return "", fmt.Errorf("failed to decode base64 data '%s'", s) - } - return string(sDec), nil - }, - } -} - -// interpolationFuncBase64Gzip implements the "gzip" function that allows gzip -// compression encoding the result using base64 -func interpolationFuncBase64Gzip() ast.Function { - return ast.Function{ - ArgTypes: []ast.Type{ast.TypeString}, - ReturnType: ast.TypeString, - Callback: func(args []interface{}) (interface{}, error) { - s := args[0].(string) - - var b bytes.Buffer - gz := gzip.NewWriter(&b) - if _, err := gz.Write([]byte(s)); err != nil { - return "", fmt.Errorf("failed to write gzip raw data: '%s'", s) - } - if err := gz.Flush(); err != nil { - return "", fmt.Errorf("failed to flush gzip writer: '%s'", s) - } - if err := gz.Close(); err != nil { - return "", fmt.Errorf("failed to close gzip writer: '%s'", s) - } - - return base64.StdEncoding.EncodeToString(b.Bytes()), nil - }, - } -} - -// interpolationFuncLower implements the "lower" function that does -// string lower casing. -func interpolationFuncLower() ast.Function { - return ast.Function{ - ArgTypes: []ast.Type{ast.TypeString}, - ReturnType: ast.TypeString, - Callback: func(args []interface{}) (interface{}, error) { - toLower := args[0].(string) - return strings.ToLower(toLower), nil - }, - } -} - -func interpolationFuncMd5() ast.Function { - return ast.Function{ - ArgTypes: []ast.Type{ast.TypeString}, - ReturnType: ast.TypeString, - Callback: func(args []interface{}) (interface{}, error) { - s := args[0].(string) - h := md5.New() - h.Write([]byte(s)) - hash := hex.EncodeToString(h.Sum(nil)) - return hash, nil - }, - } -} - -func interpolationFuncMerge() ast.Function { - return ast.Function{ - ArgTypes: []ast.Type{ast.TypeMap}, - ReturnType: ast.TypeMap, - Variadic: true, - VariadicType: ast.TypeMap, - Callback: func(args []interface{}) (interface{}, error) { - outputMap := make(map[string]ast.Variable) - - for _, arg := range args { - for k, v := range arg.(map[string]ast.Variable) { - outputMap[k] = v - } - } - - return outputMap, nil - }, - } -} - -// interpolationFuncUpper implements the "upper" function that does -// string upper casing. -func interpolationFuncUpper() ast.Function { - return ast.Function{ - ArgTypes: []ast.Type{ast.TypeString}, - ReturnType: ast.TypeString, - Callback: func(args []interface{}) (interface{}, error) { - toUpper := args[0].(string) - return strings.ToUpper(toUpper), nil - }, - } -} - -func interpolationFuncSha1() ast.Function { - return ast.Function{ - ArgTypes: []ast.Type{ast.TypeString}, - ReturnType: ast.TypeString, - Callback: func(args []interface{}) (interface{}, error) { - s := args[0].(string) - h := sha1.New() - h.Write([]byte(s)) - hash := hex.EncodeToString(h.Sum(nil)) - return hash, nil - }, - } -} - -// hexadecimal representation of sha256 sum -func interpolationFuncSha256() ast.Function { - return ast.Function{ - ArgTypes: []ast.Type{ast.TypeString}, - ReturnType: ast.TypeString, - Callback: func(args []interface{}) (interface{}, error) { - s := args[0].(string) - h := sha256.New() - h.Write([]byte(s)) - hash := hex.EncodeToString(h.Sum(nil)) - return hash, nil - }, - } -} - -func interpolationFuncSha512() ast.Function { - return ast.Function{ - ArgTypes: []ast.Type{ast.TypeString}, - ReturnType: ast.TypeString, - Callback: func(args []interface{}) (interface{}, error) { - s := args[0].(string) - h := sha512.New() - h.Write([]byte(s)) - hash := hex.EncodeToString(h.Sum(nil)) - return hash, nil - }, - } -} - -func interpolationFuncTrimSpace() ast.Function { - return ast.Function{ - ArgTypes: []ast.Type{ast.TypeString}, - ReturnType: ast.TypeString, - Callback: func(args []interface{}) (interface{}, error) { - trimSpace := args[0].(string) - return strings.TrimSpace(trimSpace), nil - }, - } -} - -func interpolationFuncBase64Sha256() ast.Function { - return ast.Function{ - ArgTypes: []ast.Type{ast.TypeString}, - ReturnType: ast.TypeString, - Callback: func(args []interface{}) (interface{}, error) { - s := args[0].(string) - h := sha256.New() - h.Write([]byte(s)) - shaSum := h.Sum(nil) - encoded := base64.StdEncoding.EncodeToString(shaSum[:]) - return encoded, nil - }, - } -} - -func interpolationFuncBase64Sha512() ast.Function { - return ast.Function{ - ArgTypes: []ast.Type{ast.TypeString}, - ReturnType: ast.TypeString, - Callback: func(args []interface{}) (interface{}, error) { - s := args[0].(string) - h := sha512.New() - h.Write([]byte(s)) - shaSum := h.Sum(nil) - encoded := base64.StdEncoding.EncodeToString(shaSum[:]) - return encoded, nil - }, - } -} - -func interpolationFuncBcrypt() ast.Function { - return ast.Function{ - ArgTypes: []ast.Type{ast.TypeString}, - Variadic: true, - VariadicType: ast.TypeString, - ReturnType: ast.TypeString, - Callback: func(args []interface{}) (interface{}, error) { - defaultCost := 10 - - if len(args) > 1 { - costStr := args[1].(string) - cost, err := strconv.Atoi(costStr) - if err != nil { - return "", err - } - - defaultCost = cost - } - - if len(args) > 2 { - return "", fmt.Errorf("bcrypt() takes no more than two arguments") - } - - input := args[0].(string) - out, err := bcrypt.GenerateFromPassword([]byte(input), defaultCost) - if err != nil { - return "", fmt.Errorf("error occured generating password %s", err.Error()) - } - - return string(out), nil - }, - } -} - -func interpolationFuncUUID() ast.Function { - return ast.Function{ - ArgTypes: []ast.Type{}, - ReturnType: ast.TypeString, - Callback: func(args []interface{}) (interface{}, error) { - return uuid.GenerateUUID() - }, - } -} - -// interpolationFuncTimestamp -func interpolationFuncTimestamp() ast.Function { - return ast.Function{ - ArgTypes: []ast.Type{}, - ReturnType: ast.TypeString, - Callback: func(args []interface{}) (interface{}, error) { - return time.Now().UTC().Format(time.RFC3339), nil - }, - } -} - -func interpolationFuncTimeAdd() ast.Function { - return ast.Function{ - ArgTypes: []ast.Type{ - ast.TypeString, // input timestamp string in RFC3339 format - ast.TypeString, // duration to add to input timestamp that should be parsable by time.ParseDuration - }, - ReturnType: ast.TypeString, - Callback: func(args []interface{}) (interface{}, error) { - - ts, err := time.Parse(time.RFC3339, args[0].(string)) - if err != nil { - return nil, err - } - duration, err := time.ParseDuration(args[1].(string)) - if err != nil { - return nil, err - } - - return ts.Add(duration).Format(time.RFC3339), nil - }, - } -} - -// interpolationFuncTitle implements the "title" function that returns a copy of the -// string in which first characters of all the words are capitalized. -func interpolationFuncTitle() ast.Function { - return ast.Function{ - ArgTypes: []ast.Type{ast.TypeString}, - ReturnType: ast.TypeString, - Callback: func(args []interface{}) (interface{}, error) { - toTitle := args[0].(string) - return strings.Title(toTitle), nil - }, - } -} - -// interpolationFuncSubstr implements the "substr" function that allows strings -// to be truncated. -func interpolationFuncSubstr() ast.Function { - return ast.Function{ - ArgTypes: []ast.Type{ - ast.TypeString, // input string - ast.TypeInt, // offset - ast.TypeInt, // length - }, - ReturnType: ast.TypeString, - Callback: func(args []interface{}) (interface{}, error) { - str := args[0].(string) - offset := args[1].(int) - length := args[2].(int) - - // Interpret a negative offset as being equivalent to a positive - // offset taken from the end of the string. - if offset < 0 { - offset += len(str) - } - - // Interpret a length of `-1` as indicating that the substring - // should start at `offset` and continue until the end of the - // string. Any other negative length (other than `-1`) is invalid. - if length == -1 { - length = len(str) - } else if length >= 0 { - length += offset - } else { - return nil, fmt.Errorf("length should be a non-negative integer") - } - - if offset > len(str) || offset < 0 { - return nil, fmt.Errorf("offset cannot be larger than the length of the string") - } - - if length > len(str) { - return nil, fmt.Errorf("'offset + length' cannot be larger than the length of the string") - } - - return str[offset:length], nil - }, - } -} - -// Flatten until it's not ast.TypeList -func flattener(finalList []ast.Variable, flattenList []ast.Variable) []ast.Variable { - for _, val := range flattenList { - if val.Type == ast.TypeList { - finalList = flattener(finalList, val.Value.([]ast.Variable)) - } else { - finalList = append(finalList, val) - } - } - return finalList -} - -// Flatten to single list -func interpolationFuncFlatten() ast.Function { - return ast.Function{ - ArgTypes: []ast.Type{ast.TypeList}, - ReturnType: ast.TypeList, - Variadic: false, - Callback: func(args []interface{}) (interface{}, error) { - inputList := args[0].([]ast.Variable) - - var outputList []ast.Variable - return flattener(outputList, inputList), nil - }, - } -} - -func interpolationFuncURLEncode() ast.Function { - return ast.Function{ - ArgTypes: []ast.Type{ast.TypeString}, - ReturnType: ast.TypeString, - Callback: func(args []interface{}) (interface{}, error) { - s := args[0].(string) - return url.QueryEscape(s), nil - }, - } -} - -// interpolationFuncTranspose implements the "transpose" function -// that converts a map (string,list) to a map (string,list) where -// the unique values of the original lists become the keys of the -// new map and the keys of the original map become values for the -// corresponding new keys. -func interpolationFuncTranspose() ast.Function { - return ast.Function{ - ArgTypes: []ast.Type{ast.TypeMap}, - ReturnType: ast.TypeMap, - Callback: func(args []interface{}) (interface{}, error) { - - inputMap := args[0].(map[string]ast.Variable) - outputMap := make(map[string]ast.Variable) - tmpMap := make(map[string][]string) - - for inKey, inVal := range inputMap { - if inVal.Type != ast.TypeList { - return nil, fmt.Errorf("transpose requires a map of lists of strings") - } - values := inVal.Value.([]ast.Variable) - for _, listVal := range values { - if listVal.Type != ast.TypeString { - return nil, fmt.Errorf("transpose requires the given map values to be lists of strings") - } - outKey := listVal.Value.(string) - if _, ok := tmpMap[outKey]; !ok { - tmpMap[outKey] = make([]string, 0) - } - outVal := tmpMap[outKey] - outVal = append(outVal, inKey) - sort.Strings(outVal) - tmpMap[outKey] = outVal - } - } - - for outKey, outVal := range tmpMap { - values := make([]ast.Variable, 0) - for _, v := range outVal { - values = append(values, ast.Variable{Type: ast.TypeString, Value: v}) - } - outputMap[outKey] = ast.Variable{Type: ast.TypeList, Value: values} - } - return outputMap, nil - }, - } -} - -// interpolationFuncAbs returns the absolute value of a given float. -func interpolationFuncAbs() ast.Function { - return ast.Function{ - ArgTypes: []ast.Type{ast.TypeFloat}, - ReturnType: ast.TypeFloat, - Callback: func(args []interface{}) (interface{}, error) { - return math.Abs(args[0].(float64)), nil - }, - } -} - -// interpolationFuncRsaDecrypt implements the "rsadecrypt" function that does -// RSA decryption. -func interpolationFuncRsaDecrypt() ast.Function { - return ast.Function{ - ArgTypes: []ast.Type{ast.TypeString, ast.TypeString}, - ReturnType: ast.TypeString, - Callback: func(args []interface{}) (interface{}, error) { - s := args[0].(string) - key := args[1].(string) - - b, err := base64.StdEncoding.DecodeString(s) - if err != nil { - return "", fmt.Errorf("Failed to decode input %q: cipher text must be base64-encoded", s) - } - - block, _ := pem.Decode([]byte(key)) - if block == nil { - return "", fmt.Errorf("Failed to read key %q: no key found", key) - } - if block.Headers["Proc-Type"] == "4,ENCRYPTED" { - return "", fmt.Errorf( - "Failed to read key %q: password protected keys are\n"+ - "not supported. Please decrypt the key prior to use.", key) - } - - x509Key, err := x509.ParsePKCS1PrivateKey(block.Bytes) - if err != nil { - return "", err - } - - out, err := rsa.DecryptPKCS1v15(nil, x509Key, b) - if err != nil { - return "", err - } - - return string(out), nil - }, - } + return nil } diff --git a/config/interpolate_funcs_test.go b/config/interpolate_funcs_test.go deleted file mode 100644 index e883429e9..000000000 --- a/config/interpolate_funcs_test.go +++ /dev/null @@ -1,2995 +0,0 @@ -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{}{"", ""}, - 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 TestInterpolateFuncReverse(t *testing.T) { - testFunction(t, testFunctionConfig{ - Vars: map[string]ast.Variable{ - "var.inputlist": { - Type: ast.TypeList, - Value: []ast.Variable{ - {Type: ast.TypeString, Value: "a"}, - {Type: ast.TypeString, Value: "b"}, - {Type: ast.TypeString, Value: "1"}, - {Type: ast.TypeString, Value: "d"}, - }, - }, - "var.emptylist": { - Type: ast.TypeList, - // Intentionally 0-lengthed list - Value: []ast.Variable{}, - }, - }, - Cases: []testFunctionCase{ - { - `${reverse(var.inputlist)}`, - []interface{}{"d", "1", "b", "a"}, - false, - }, - { - `${reverse(var.emptylist)}`, - []interface{}{}, - false, - }, - }, - }) -} - -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, - }, - }, - }) -} diff --git a/config/raw_config.go b/config/raw_config.go index a7d4d595e..27bcd1da0 100644 --- a/config/raw_config.go +++ b/config/raw_config.go @@ -7,9 +7,6 @@ import ( "strconv" "sync" - "github.com/zclconf/go-cty/cty" - "github.com/zclconf/go-cty/cty/convert" - hcl2 "github.com/hashicorp/hcl/v2" "github.com/hashicorp/hil" "github.com/hashicorp/hil/ast" @@ -349,37 +346,9 @@ func (r *RawConfig) couldBeInteger() bool { _, err := strconv.ParseInt(r.Value().(string), 0, 0) return err == nil } else { - // HCL2 experiment path: using the HCL2 API via shims - // - // This path catches fewer situations because we have to assume all - // variables are entirely unknown in HCL2, rather than the assumption - // above that all variables can be numbers because names like "var.foo" - // are considered a single variable rather than an attribute access. - // This is fine in practice, because we get a definitive answer - // during the graph walk when we have real values to work with. - attrs, diags := r.Body.JustAttributes() - if diags.HasErrors() { - // This body is not just a single attribute with a value, so - // this can't be a number. - return false - } - attr, hasAttr := attrs[r.Key] - if !hasAttr { - return false - } - result, diags := hcl2EvalWithUnknownVars(attr.Expr) - if diags.HasErrors() { - // We'll conservatively assume that this error is a result of - // us not being ready to fully-populate the scope, and catch - // any further problems during the main graph walk. - return true - } - - // If the result is convertable to number then we'll allow it. - // We do this because an unknown string is optimistically convertable - // to number (might be "5") but a _known_ string "hello" is not. - _, err := convert.Convert(result, cty.Number) - return err == nil + // We briefly tried to gradually implement HCL2 support by adding a + // branch here, but that experiment was not successful. + panic("HCL2 experimental path no longer supported") } } @@ -432,14 +401,14 @@ type gobRawConfig struct { } // langEvalConfig returns the evaluation configuration we use to execute. +// +// The interpolation functions are no longer available here, because this +// codepath is no longer used. Instead, see ../lang/functions.go . func langEvalConfig(vs map[string]ast.Variable) *hil.EvalConfig { funcMap := make(map[string]ast.Function) for k, v := range Funcs() { funcMap[k] = v } - funcMap["lookup"] = interpolationFuncLookup(vs) - funcMap["keys"] = interpolationFuncKeys(vs) - funcMap["values"] = interpolationFuncValues(vs) return &hil.EvalConfig{ GlobalScope: &ast.BasicScope{