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