github.com/turtlemonvh/terraform@v0.6.9-0.20151204001754-8e40b6b855e8/config/interpolate_funcs.go (about)

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