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{