github.com/erriapo/terraform@v0.6.12-0.20160203182612-0340ea72354f/config/interpolate_funcs.go (about)

     1  package config
     2  
     3  import (
     4  	"bytes"
     5  	"crypto/sha1"
     6  	"crypto/sha256"
     7  	"encoding/base64"
     8  	"encoding/hex"
     9  	"errors"
    10  	"fmt"
    11  	"io/ioutil"
    12  	"net"
    13  	"regexp"
    14  	"sort"
    15  	"strconv"
    16  	"strings"
    17  
    18  	"github.com/apparentlymart/go-cidr/cidr"
    19  	"github.com/hashicorp/hil/ast"
    20  	"github.com/mitchellh/go-homedir"
    21  )
    22  
    23  // Funcs is the mapping of built-in functions for configuration.
    24  func Funcs() map[string]ast.Function {
    25  	return map[string]ast.Function{
    26  		"base64sha256": interpolationFuncBase64Sha256(),
    27  		"cidrhost":     interpolationFuncCidrHost(),
    28  		"cidrnetmask":  interpolationFuncCidrNetmask(),
    29  		"cidrsubnet":   interpolationFuncCidrSubnet(),
    30  		"coalesce":     interpolationFuncCoalesce(),
    31  		"compact":      interpolationFuncCompact(),
    32  		"concat":       interpolationFuncConcat(),
    33  		"element":      interpolationFuncElement(),
    34  		"file":         interpolationFuncFile(),
    35  		"format":       interpolationFuncFormat(),
    36  		"formatlist":   interpolationFuncFormatList(),
    37  		"index":        interpolationFuncIndex(),
    38  		"join":         interpolationFuncJoin(),
    39  		"length":       interpolationFuncLength(),
    40  		"lower":        interpolationFuncLower(),
    41  		"replace":      interpolationFuncReplace(),
    42  		"split":        interpolationFuncSplit(),
    43  		"sha1":         interpolationFuncSha1(),
    44  		"sha256":       interpolationFuncSha256(),
    45  		"trimspace":    interpolationFuncTrimSpace(),
    46  		"base64encode": interpolationFuncBase64Encode(),
    47  		"base64decode": interpolationFuncBase64Decode(),
    48  		"upper":        interpolationFuncUpper(),
    49  	}
    50  }
    51  
    52  // interpolationFuncCompact strips a list of multi-variable values
    53  // (e.g. as returned by "split") of any empty strings.
    54  func interpolationFuncCompact() ast.Function {
    55  	return ast.Function{
    56  		ArgTypes:   []ast.Type{ast.TypeString},
    57  		ReturnType: ast.TypeString,
    58  		Variadic:   false,
    59  		Callback: func(args []interface{}) (interface{}, error) {
    60  			if !IsStringList(args[0].(string)) {
    61  				return args[0].(string), nil
    62  			}
    63  			return StringList(args[0].(string)).Compact().String(), nil
    64  		},
    65  	}
    66  }
    67  
    68  // interpolationFuncCidrHost implements the "cidrhost" function that
    69  // fills in the host part of a CIDR range address to create a single
    70  // host address
    71  func interpolationFuncCidrHost() ast.Function {
    72  	return ast.Function{
    73  		ArgTypes: []ast.Type{
    74  			ast.TypeString, // starting CIDR mask
    75  			ast.TypeInt,    // host number to insert
    76  		},
    77  		ReturnType: ast.TypeString,
    78  		Variadic:   false,
    79  		Callback: func(args []interface{}) (interface{}, error) {
    80  			hostNum := args[1].(int)
    81  			_, network, err := net.ParseCIDR(args[0].(string))
    82  			if err != nil {
    83  				return nil, fmt.Errorf("invalid CIDR expression: %s", err)
    84  			}
    85  
    86  			ip, err := cidr.Host(network, hostNum)
    87  			if err != nil {
    88  				return nil, err
    89  			}
    90  
    91  			return ip.String(), nil
    92  		},
    93  	}
    94  }
    95  
    96  // interpolationFuncCidrNetmask implements the "cidrnetmask" function
    97  // that returns the subnet mask in IP address notation.
    98  func interpolationFuncCidrNetmask() ast.Function {
    99  	return ast.Function{
   100  		ArgTypes: []ast.Type{
   101  			ast.TypeString, // CIDR mask
   102  		},
   103  		ReturnType: ast.TypeString,
   104  		Variadic:   false,
   105  		Callback: func(args []interface{}) (interface{}, error) {
   106  			_, network, err := net.ParseCIDR(args[0].(string))
   107  			if err != nil {
   108  				return nil, fmt.Errorf("invalid CIDR expression: %s", err)
   109  			}
   110  
   111  			return net.IP(network.Mask).String(), nil
   112  		},
   113  	}
   114  }
   115  
   116  // interpolationFuncCidrSubnet implements the "cidrsubnet" function that
   117  // adds an additional subnet of the given length onto an existing
   118  // IP block expressed in CIDR notation.
   119  func interpolationFuncCidrSubnet() ast.Function {
   120  	return ast.Function{
   121  		ArgTypes: []ast.Type{
   122  			ast.TypeString, // starting CIDR mask
   123  			ast.TypeInt,    // number of bits to extend the prefix
   124  			ast.TypeInt,    // network number to append to the prefix
   125  		},
   126  		ReturnType: ast.TypeString,
   127  		Variadic:   false,
   128  		Callback: func(args []interface{}) (interface{}, error) {
   129  			extraBits := args[1].(int)
   130  			subnetNum := args[2].(int)
   131  			_, network, err := net.ParseCIDR(args[0].(string))
   132  			if err != nil {
   133  				return nil, fmt.Errorf("invalid CIDR expression: %s", err)
   134  			}
   135  
   136  			// For portability with 32-bit systems where the subnet number
   137  			// will be a 32-bit int, we only allow extension of 32 bits in
   138  			// one call even if we're running on a 64-bit machine.
   139  			// (Of course, this is significant only for IPv6.)
   140  			if extraBits > 32 {
   141  				return nil, fmt.Errorf("may not extend prefix by more than 32 bits")
   142  			}
   143  
   144  			newNetwork, err := cidr.Subnet(network, extraBits, subnetNum)
   145  			if err != nil {
   146  				return nil, err
   147  			}
   148  
   149  			return newNetwork.String(), nil
   150  		},
   151  	}
   152  }
   153  
   154  // interpolationFuncCoalesce implements the "coalesce" function that
   155  // returns the first non null / empty string from the provided input
   156  func interpolationFuncCoalesce() ast.Function {
   157  	return ast.Function{
   158  		ArgTypes:     []ast.Type{ast.TypeString},
   159  		ReturnType:   ast.TypeString,
   160  		Variadic:     true,
   161  		VariadicType: ast.TypeString,
   162  		Callback: func(args []interface{}) (interface{}, error) {
   163  			if len(args) < 2 {
   164  				return nil, fmt.Errorf("must provide at least two arguments")
   165  			}
   166  			for _, arg := range args {
   167  				argument := arg.(string)
   168  
   169  				if argument != "" {
   170  					return argument, nil
   171  				}
   172  			}
   173  			return "", nil
   174  		},
   175  	}
   176  }
   177  
   178  // interpolationFuncConcat implements the "concat" function that
   179  // concatenates multiple strings. This isn't actually necessary anymore
   180  // since our language supports string concat natively, but for backwards
   181  // compat we do this.
   182  func interpolationFuncConcat() ast.Function {
   183  	return ast.Function{
   184  		ArgTypes:     []ast.Type{ast.TypeString},
   185  		ReturnType:   ast.TypeString,
   186  		Variadic:     true,
   187  		VariadicType: ast.TypeString,
   188  		Callback: func(args []interface{}) (interface{}, error) {
   189  			var b bytes.Buffer
   190  			var finalList []string
   191  
   192  			var isDeprecated = true
   193  
   194  			for _, arg := range args {
   195  				argument := arg.(string)
   196  
   197  				if len(argument) == 0 {
   198  					continue
   199  				}
   200  
   201  				if IsStringList(argument) {
   202  					isDeprecated = false
   203  					finalList = append(finalList, StringList(argument).Slice()...)
   204  				} else {
   205  					finalList = append(finalList, argument)
   206  				}
   207  
   208  				// Deprecated concat behaviour
   209  				b.WriteString(argument)
   210  			}
   211  
   212  			if isDeprecated {
   213  				return b.String(), nil
   214  			}
   215  
   216  			return NewStringList(finalList).String(), nil
   217  		},
   218  	}
   219  }
   220  
   221  // interpolationFuncFile implements the "file" function that allows
   222  // loading contents from a file.
   223  func interpolationFuncFile() ast.Function {
   224  	return ast.Function{
   225  		ArgTypes:   []ast.Type{ast.TypeString},
   226  		ReturnType: ast.TypeString,
   227  		Callback: func(args []interface{}) (interface{}, error) {
   228  			path, err := homedir.Expand(args[0].(string))
   229  			if err != nil {
   230  				return "", err
   231  			}
   232  			data, err := ioutil.ReadFile(path)
   233  			if err != nil {
   234  				return "", err
   235  			}
   236  
   237  			return string(data), nil
   238  		},
   239  	}
   240  }
   241  
   242  // interpolationFuncFormat implements the "format" function that does
   243  // string formatting.
   244  func interpolationFuncFormat() ast.Function {
   245  	return ast.Function{
   246  		ArgTypes:     []ast.Type{ast.TypeString},
   247  		Variadic:     true,
   248  		VariadicType: ast.TypeAny,
   249  		ReturnType:   ast.TypeString,
   250  		Callback: func(args []interface{}) (interface{}, error) {
   251  			format := args[0].(string)
   252  			return fmt.Sprintf(format, args[1:]...), nil
   253  		},
   254  	}
   255  }
   256  
   257  // interpolationFuncFormatList implements the "formatlist" function that does
   258  // string formatting on lists.
   259  func interpolationFuncFormatList() ast.Function {
   260  	return ast.Function{
   261  		ArgTypes:     []ast.Type{ast.TypeString},
   262  		Variadic:     true,
   263  		VariadicType: ast.TypeAny,
   264  		ReturnType:   ast.TypeString,
   265  		Callback: func(args []interface{}) (interface{}, error) {
   266  			// Make a copy of the variadic part of args
   267  			// to avoid modifying the original.
   268  			varargs := make([]interface{}, len(args)-1)
   269  			copy(varargs, args[1:])
   270  
   271  			// Convert arguments that are lists into slices.
   272  			// Confirm along the way that all lists have the same length (n).
   273  			var n int
   274  			for i := 1; i < len(args); i++ {
   275  				s, ok := args[i].(string)
   276  				if !ok {
   277  					continue
   278  				}
   279  				if !IsStringList(s) {
   280  					continue
   281  				}
   282  
   283  				parts := StringList(s).Slice()
   284  
   285  				// otherwise the list is sent down to be indexed
   286  				varargs[i-1] = parts
   287  
   288  				// Check length
   289  				if n == 0 {
   290  					// first list we've seen
   291  					n = len(parts)
   292  					continue
   293  				}
   294  				if n != len(parts) {
   295  					return nil, fmt.Errorf("format: mismatched list lengths: %d != %d", n, len(parts))
   296  				}
   297  			}
   298  
   299  			if n == 0 {
   300  				return nil, errors.New("no lists in arguments to formatlist")
   301  			}
   302  
   303  			// Do the formatting.
   304  			format := args[0].(string)
   305  
   306  			// Generate a list of formatted strings.
   307  			list := make([]string, n)
   308  			fmtargs := make([]interface{}, len(varargs))
   309  			for i := 0; i < n; i++ {
   310  				for j, arg := range varargs {
   311  					switch arg := arg.(type) {
   312  					default:
   313  						fmtargs[j] = arg
   314  					case []string:
   315  						fmtargs[j] = arg[i]
   316  					}
   317  				}
   318  				list[i] = fmt.Sprintf(format, fmtargs...)
   319  			}
   320  			return NewStringList(list).String(), nil
   321  		},
   322  	}
   323  }
   324  
   325  // interpolationFuncIndex implements the "index" function that allows one to
   326  // find the index of a specific element in a list
   327  func interpolationFuncIndex() ast.Function {
   328  	return ast.Function{
   329  		ArgTypes:   []ast.Type{ast.TypeString, ast.TypeString},
   330  		ReturnType: ast.TypeInt,
   331  		Callback: func(args []interface{}) (interface{}, error) {
   332  			haystack := StringList(args[0].(string)).Slice()
   333  			needle := args[1].(string)
   334  			for index, element := range haystack {
   335  				if needle == element {
   336  					return index, nil
   337  				}
   338  			}
   339  			return nil, fmt.Errorf("Could not find '%s' in '%s'", needle, haystack)
   340  		},
   341  	}
   342  }
   343  
   344  // interpolationFuncJoin implements the "join" function that allows
   345  // multi-variable values to be joined by some character.
   346  func interpolationFuncJoin() ast.Function {
   347  	return ast.Function{
   348  		ArgTypes:   []ast.Type{ast.TypeString, ast.TypeString},
   349  		ReturnType: ast.TypeString,
   350  		Callback: func(args []interface{}) (interface{}, error) {
   351  			var list []string
   352  			for _, arg := range args[1:] {
   353  				parts := StringList(arg.(string)).Slice()
   354  				list = append(list, parts...)
   355  			}
   356  
   357  			return strings.Join(list, args[0].(string)), nil
   358  		},
   359  	}
   360  }
   361  
   362  // interpolationFuncReplace implements the "replace" function that does
   363  // string replacement.
   364  func interpolationFuncReplace() ast.Function {
   365  	return ast.Function{
   366  		ArgTypes:   []ast.Type{ast.TypeString, ast.TypeString, ast.TypeString},
   367  		ReturnType: ast.TypeString,
   368  		Callback: func(args []interface{}) (interface{}, error) {
   369  			s := args[0].(string)
   370  			search := args[1].(string)
   371  			replace := args[2].(string)
   372  
   373  			// We search/replace using a regexp if the string is surrounded
   374  			// in forward slashes.
   375  			if len(search) > 1 && search[0] == '/' && search[len(search)-1] == '/' {
   376  				re, err := regexp.Compile(search[1 : len(search)-1])
   377  				if err != nil {
   378  					return nil, err
   379  				}
   380  
   381  				return re.ReplaceAllString(s, replace), nil
   382  			}
   383  
   384  			return strings.Replace(s, search, replace, -1), nil
   385  		},
   386  	}
   387  }
   388  
   389  func interpolationFuncLength() ast.Function {
   390  	return ast.Function{
   391  		ArgTypes:   []ast.Type{ast.TypeString},
   392  		ReturnType: ast.TypeInt,
   393  		Variadic:   false,
   394  		Callback: func(args []interface{}) (interface{}, error) {
   395  			if !IsStringList(args[0].(string)) {
   396  				return len(args[0].(string)), nil
   397  			}
   398  
   399  			length := 0
   400  			for _, arg := range args {
   401  				length += StringList(arg.(string)).Length()
   402  			}
   403  			return length, nil
   404  		},
   405  	}
   406  }
   407  
   408  // interpolationFuncSplit implements the "split" function that allows
   409  // strings to split into multi-variable values
   410  func interpolationFuncSplit() ast.Function {
   411  	return ast.Function{
   412  		ArgTypes:   []ast.Type{ast.TypeString, ast.TypeString},
   413  		ReturnType: ast.TypeString,
   414  		Callback: func(args []interface{}) (interface{}, error) {
   415  			sep := args[0].(string)
   416  			s := args[1].(string)
   417  			return NewStringList(strings.Split(s, sep)).String(), nil
   418  		},
   419  	}
   420  }
   421  
   422  // interpolationFuncLookup implements the "lookup" function that allows
   423  // dynamic lookups of map types within a Terraform configuration.
   424  func interpolationFuncLookup(vs map[string]ast.Variable) ast.Function {
   425  	return ast.Function{
   426  		ArgTypes:   []ast.Type{ast.TypeString, ast.TypeString},
   427  		ReturnType: ast.TypeString,
   428  		Callback: func(args []interface{}) (interface{}, error) {
   429  			k := fmt.Sprintf("var.%s.%s", args[0].(string), args[1].(string))
   430  			v, ok := vs[k]
   431  			if !ok {
   432  				return "", fmt.Errorf(
   433  					"lookup in '%s' failed to find '%s'",
   434  					args[0].(string), args[1].(string))
   435  			}
   436  			if v.Type != ast.TypeString {
   437  				return "", fmt.Errorf(
   438  					"lookup in '%s' for '%s' has bad type %s",
   439  					args[0].(string), args[1].(string), v.Type)
   440  			}
   441  
   442  			return v.Value.(string), nil
   443  		},
   444  	}
   445  }
   446  
   447  // interpolationFuncElement implements the "element" function that allows
   448  // a specific index to be looked up in a multi-variable value. Note that this will
   449  // wrap if the index is larger than the number of elements in the multi-variable value.
   450  func interpolationFuncElement() ast.Function {
   451  	return ast.Function{
   452  		ArgTypes:   []ast.Type{ast.TypeString, ast.TypeString},
   453  		ReturnType: ast.TypeString,
   454  		Callback: func(args []interface{}) (interface{}, error) {
   455  			list := StringList(args[0].(string))
   456  
   457  			index, err := strconv.Atoi(args[1].(string))
   458  			if err != nil {
   459  				return "", fmt.Errorf(
   460  					"invalid number for index, got %s", args[1])
   461  			}
   462  
   463  			v := list.Element(index)
   464  			return v, nil
   465  		},
   466  	}
   467  }
   468  
   469  // interpolationFuncKeys implements the "keys" function that yields a list of
   470  // keys of map types within a Terraform configuration.
   471  func interpolationFuncKeys(vs map[string]ast.Variable) ast.Function {
   472  	return ast.Function{
   473  		ArgTypes:   []ast.Type{ast.TypeString},
   474  		ReturnType: ast.TypeString,
   475  		Callback: func(args []interface{}) (interface{}, error) {
   476  			// Prefix must include ending dot to be a map
   477  			prefix := fmt.Sprintf("var.%s.", args[0].(string))
   478  			keys := make([]string, 0, len(vs))
   479  			for k, _ := range vs {
   480  				if !strings.HasPrefix(k, prefix) {
   481  					continue
   482  				}
   483  				keys = append(keys, k[len(prefix):])
   484  			}
   485  
   486  			if len(keys) <= 0 {
   487  				return "", fmt.Errorf(
   488  					"failed to find map '%s'",
   489  					args[0].(string))
   490  			}
   491  
   492  			sort.Strings(keys)
   493  
   494  			return NewStringList(keys).String(), nil
   495  		},
   496  	}
   497  }
   498  
   499  // interpolationFuncValues implements the "values" function that yields a list of
   500  // keys of map types within a Terraform configuration.
   501  func interpolationFuncValues(vs map[string]ast.Variable) ast.Function {
   502  	return ast.Function{
   503  		ArgTypes:   []ast.Type{ast.TypeString},
   504  		ReturnType: ast.TypeString,
   505  		Callback: func(args []interface{}) (interface{}, error) {
   506  			// Prefix must include ending dot to be a map
   507  			prefix := fmt.Sprintf("var.%s.", args[0].(string))
   508  			keys := make([]string, 0, len(vs))
   509  			for k, _ := range vs {
   510  				if !strings.HasPrefix(k, prefix) {
   511  					continue
   512  				}
   513  				keys = append(keys, k)
   514  			}
   515  
   516  			if len(keys) <= 0 {
   517  				return "", fmt.Errorf(
   518  					"failed to find map '%s'",
   519  					args[0].(string))
   520  			}
   521  
   522  			sort.Strings(keys)
   523  
   524  			vals := make([]string, 0, len(keys))
   525  
   526  			for _, k := range keys {
   527  				v := vs[k]
   528  				if v.Type != ast.TypeString {
   529  					return "", fmt.Errorf("values(): %q has bad type %s", k, v.Type)
   530  				}
   531  				vals = append(vals, vs[k].Value.(string))
   532  			}
   533  
   534  			return NewStringList(vals).String(), nil
   535  		},
   536  	}
   537  }
   538  
   539  // interpolationFuncBase64Encode implements the "base64encode" function that
   540  // allows Base64 encoding.
   541  func interpolationFuncBase64Encode() ast.Function {
   542  	return ast.Function{
   543  		ArgTypes:   []ast.Type{ast.TypeString},
   544  		ReturnType: ast.TypeString,
   545  		Callback: func(args []interface{}) (interface{}, error) {
   546  			s := args[0].(string)
   547  			return base64.StdEncoding.EncodeToString([]byte(s)), nil
   548  		},
   549  	}
   550  }
   551  
   552  // interpolationFuncBase64Decode implements the "base64decode" function that
   553  // allows Base64 decoding.
   554  func interpolationFuncBase64Decode() ast.Function {
   555  	return ast.Function{
   556  		ArgTypes:   []ast.Type{ast.TypeString},
   557  		ReturnType: ast.TypeString,
   558  		Callback: func(args []interface{}) (interface{}, error) {
   559  			s := args[0].(string)
   560  			sDec, err := base64.StdEncoding.DecodeString(s)
   561  			if err != nil {
   562  				return "", fmt.Errorf("failed to decode base64 data '%s'", s)
   563  			}
   564  			return string(sDec), nil
   565  		},
   566  	}
   567  }
   568  
   569  // interpolationFuncLower implements the "lower" function that does
   570  // string lower casing.
   571  func interpolationFuncLower() ast.Function {
   572  	return ast.Function{
   573  		ArgTypes:   []ast.Type{ast.TypeString},
   574  		ReturnType: ast.TypeString,
   575  		Callback: func(args []interface{}) (interface{}, error) {
   576  			toLower := args[0].(string)
   577  			return strings.ToLower(toLower), nil
   578  		},
   579  	}
   580  }
   581  
   582  // interpolationFuncUpper implements the "upper" function that does
   583  // string upper casing.
   584  func interpolationFuncUpper() ast.Function {
   585  	return ast.Function{
   586  		ArgTypes:   []ast.Type{ast.TypeString},
   587  		ReturnType: ast.TypeString,
   588  		Callback: func(args []interface{}) (interface{}, error) {
   589  			toUpper := args[0].(string)
   590  			return strings.ToUpper(toUpper), nil
   591  		},
   592  	}
   593  }
   594  
   595  func interpolationFuncSha1() ast.Function {
   596  	return ast.Function{
   597  		ArgTypes:   []ast.Type{ast.TypeString},
   598  		ReturnType: ast.TypeString,
   599  		Callback: func(args []interface{}) (interface{}, error) {
   600  			s := args[0].(string)
   601  			h := sha1.New()
   602  			h.Write([]byte(s))
   603  			hash := hex.EncodeToString(h.Sum(nil))
   604  			return hash, nil
   605  		},
   606  	}
   607  }
   608  
   609  // hexadecimal representation of sha256 sum
   610  func interpolationFuncSha256() ast.Function {
   611  	return ast.Function{
   612  		ArgTypes:   []ast.Type{ast.TypeString},
   613  		ReturnType: ast.TypeString,
   614  		Callback: func(args []interface{}) (interface{}, error) {
   615  			s := args[0].(string)
   616  			h := sha256.New()
   617  			h.Write([]byte(s))
   618  			hash := hex.EncodeToString(h.Sum(nil))
   619  			return hash, nil
   620  		},
   621  	}
   622  }
   623  
   624  func interpolationFuncTrimSpace() ast.Function {
   625  	return ast.Function{
   626  		ArgTypes:   []ast.Type{ast.TypeString},
   627  		ReturnType: ast.TypeString,
   628  		Callback: func(args []interface{}) (interface{}, error) {
   629  			trimSpace := args[0].(string)
   630  			return strings.TrimSpace(trimSpace), nil
   631  		},
   632  	}
   633  }
   634  
   635  func interpolationFuncBase64Sha256() ast.Function {
   636  	return ast.Function{
   637  		ArgTypes:   []ast.Type{ast.TypeString},
   638  		ReturnType: ast.TypeString,
   639  		Callback: func(args []interface{}) (interface{}, error) {
   640  			s := args[0].(string)
   641  			h := sha256.New()
   642  			h.Write([]byte(s))
   643  			shaSum := h.Sum(nil)
   644  			encoded := base64.StdEncoding.EncodeToString(shaSum[:])
   645  			return encoded, nil
   646  		},
   647  	}
   648  }