1728 lines
48 KiB
Go
1728 lines
48 KiB
Go
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
|
|
// required to be returned from interpolation functions which return
|
|
// TypeList.
|
|
func stringSliceToVariableValue(values []string) []ast.Variable {
|
|
output := make([]ast.Variable, len(values))
|
|
for index, value := range values {
|
|
output[index] = ast.Variable{
|
|
Type: ast.TypeString,
|
|
Value: value,
|
|
}
|
|
}
|
|
return output
|
|
}
|
|
|
|
func listVariableValueToStringSlice(values []ast.Variable) ([]string, error) {
|
|
output := make([]string, len(values))
|
|
for index, value := range values {
|
|
if value.Type != ast.TypeString {
|
|
return []string{}, fmt.Errorf("list has non-string element (%T)", value.Type.String())
|
|
}
|
|
output[index] = value.Value.(string)
|
|
}
|
|
return output, nil
|
|
}
|
|
|
|
// Funcs is the mapping of built-in functions for configuration.
|
|
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(),
|
|
"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
|
|
},
|
|
}
|
|
}
|
|
|
|
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
|
|
},
|
|
}
|
|
}
|