github.com/icebourg/terraform@v0.6.5-0.20151015205227-263cc1b85535/config/interpolate_funcs.go (about)

     1  package config
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/base64"
     6  	"errors"
     7  	"fmt"
     8  	"io/ioutil"
     9  	"regexp"
    10  	"sort"
    11  	"strconv"
    12  	"strings"
    13  
    14  	"github.com/hashicorp/terraform/config/lang/ast"
    15  	"github.com/mitchellh/go-homedir"
    16  )
    17  
    18  // Funcs is the mapping of built-in functions for configuration.
    19  var Funcs map[string]ast.Function
    20  
    21  func init() {
    22  	Funcs = map[string]ast.Function{
    23  		"compact":      interpolationFuncCompact(),
    24  		"concat":       interpolationFuncConcat(),
    25  		"element":      interpolationFuncElement(),
    26  		"file":         interpolationFuncFile(),
    27  		"format":       interpolationFuncFormat(),
    28  		"formatlist":   interpolationFuncFormatList(),
    29  		"index":        interpolationFuncIndex(),
    30  		"join":         interpolationFuncJoin(),
    31  		"length":       interpolationFuncLength(),
    32  		"replace":      interpolationFuncReplace(),
    33  		"split":        interpolationFuncSplit(),
    34  		"base64encode": interpolationFuncBase64Encode(),
    35  		"base64decode": interpolationFuncBase64Decode(),
    36  	}
    37  }
    38  
    39  // interpolationFuncCompact strips a list of multi-variable values
    40  // (e.g. as returned by "split") of any empty strings.
    41  func interpolationFuncCompact() ast.Function {
    42  	return ast.Function{
    43  		ArgTypes:   []ast.Type{ast.TypeString},
    44  		ReturnType: ast.TypeString,
    45  		Variadic:   false,
    46  		Callback: func(args []interface{}) (interface{}, error) {
    47  			if !IsStringList(args[0].(string)) {
    48  				return args[0].(string), nil
    49  			}
    50  			return StringList(args[0].(string)).Compact().String(), nil
    51  		},
    52  	}
    53  }
    54  
    55  // interpolationFuncConcat implements the "concat" function that
    56  // concatenates multiple strings. This isn't actually necessary anymore
    57  // since our language supports string concat natively, but for backwards
    58  // compat we do this.
    59  func interpolationFuncConcat() ast.Function {
    60  	return ast.Function{
    61  		ArgTypes:     []ast.Type{ast.TypeString},
    62  		ReturnType:   ast.TypeString,
    63  		Variadic:     true,
    64  		VariadicType: ast.TypeString,
    65  		Callback: func(args []interface{}) (interface{}, error) {
    66  			var b bytes.Buffer
    67  			var finalList []string
    68  
    69  			var isDeprecated = true
    70  
    71  			for _, arg := range args {
    72  				argument := arg.(string)
    73  
    74  				if len(argument) == 0 {
    75  					continue
    76  				}
    77  
    78  				if IsStringList(argument) {
    79  					isDeprecated = false
    80  					finalList = append(finalList, StringList(argument).Slice()...)
    81  				} else {
    82  					finalList = append(finalList, argument)
    83  				}
    84  
    85  				// Deprecated concat behaviour
    86  				b.WriteString(argument)
    87  			}
    88  
    89  			if isDeprecated {
    90  				return b.String(), nil
    91  			}
    92  
    93  			return NewStringList(finalList).String(), nil
    94  		},
    95  	}
    96  }
    97  
    98  // interpolationFuncFile implements the "file" function that allows
    99  // loading contents from a file.
   100  func interpolationFuncFile() ast.Function {
   101  	return ast.Function{
   102  		ArgTypes:   []ast.Type{ast.TypeString},
   103  		ReturnType: ast.TypeString,
   104  		Callback: func(args []interface{}) (interface{}, error) {
   105  			path, err := homedir.Expand(args[0].(string))
   106  			if err != nil {
   107  				return "", err
   108  			}
   109  			data, err := ioutil.ReadFile(path)
   110  			if err != nil {
   111  				return "", err
   112  			}
   113  
   114  			return string(data), nil
   115  		},
   116  	}
   117  }
   118  
   119  // interpolationFuncFormat implements the "format" function that does
   120  // string formatting.
   121  func interpolationFuncFormat() ast.Function {
   122  	return ast.Function{
   123  		ArgTypes:     []ast.Type{ast.TypeString},
   124  		Variadic:     true,
   125  		VariadicType: ast.TypeAny,
   126  		ReturnType:   ast.TypeString,
   127  		Callback: func(args []interface{}) (interface{}, error) {
   128  			format := args[0].(string)
   129  			return fmt.Sprintf(format, args[1:]...), nil
   130  		},
   131  	}
   132  }
   133  
   134  // interpolationFuncFormatList implements the "formatlist" function that does
   135  // string formatting on lists.
   136  func interpolationFuncFormatList() ast.Function {
   137  	return ast.Function{
   138  		ArgTypes:     []ast.Type{ast.TypeString},
   139  		Variadic:     true,
   140  		VariadicType: ast.TypeAny,
   141  		ReturnType:   ast.TypeString,
   142  		Callback: func(args []interface{}) (interface{}, error) {
   143  			// Make a copy of the variadic part of args
   144  			// to avoid modifying the original.
   145  			varargs := make([]interface{}, len(args)-1)
   146  			copy(varargs, args[1:])
   147  
   148  			// Convert arguments that are lists into slices.
   149  			// Confirm along the way that all lists have the same length (n).
   150  			var n int
   151  			for i := 1; i < len(args); i++ {
   152  				s, ok := args[i].(string)
   153  				if !ok {
   154  					continue
   155  				}
   156  				if !IsStringList(s) {
   157  					continue
   158  				}
   159  
   160  				parts := StringList(s).Slice()
   161  
   162  				// otherwise the list is sent down to be indexed
   163  				varargs[i-1] = parts
   164  
   165  				// Check length
   166  				if n == 0 {
   167  					// first list we've seen
   168  					n = len(parts)
   169  					continue
   170  				}
   171  				if n != len(parts) {
   172  					return nil, fmt.Errorf("format: mismatched list lengths: %d != %d", n, len(parts))
   173  				}
   174  			}
   175  
   176  			if n == 0 {
   177  				return nil, errors.New("no lists in arguments to formatlist")
   178  			}
   179  
   180  			// Do the formatting.
   181  			format := args[0].(string)
   182  
   183  			// Generate a list of formatted strings.
   184  			list := make([]string, n)
   185  			fmtargs := make([]interface{}, len(varargs))
   186  			for i := 0; i < n; i++ {
   187  				for j, arg := range varargs {
   188  					switch arg := arg.(type) {
   189  					default:
   190  						fmtargs[j] = arg
   191  					case []string:
   192  						fmtargs[j] = arg[i]
   193  					}
   194  				}
   195  				list[i] = fmt.Sprintf(format, fmtargs...)
   196  			}
   197  			return NewStringList(list).String(), nil
   198  		},
   199  	}
   200  }
   201  
   202  // interpolationFuncIndex implements the "index" function that allows one to
   203  // find the index of a specific element in a list
   204  func interpolationFuncIndex() ast.Function {
   205  	return ast.Function{
   206  		ArgTypes:   []ast.Type{ast.TypeString, ast.TypeString},
   207  		ReturnType: ast.TypeInt,
   208  		Callback: func(args []interface{}) (interface{}, error) {
   209  			haystack := StringList(args[0].(string)).Slice()
   210  			needle := args[1].(string)
   211  			for index, element := range haystack {
   212  				if needle == element {
   213  					return index, nil
   214  				}
   215  			}
   216  			return nil, fmt.Errorf("Could not find '%s' in '%s'", needle, haystack)
   217  		},
   218  	}
   219  }
   220  
   221  // interpolationFuncJoin implements the "join" function that allows
   222  // multi-variable values to be joined by some character.
   223  func interpolationFuncJoin() ast.Function {
   224  	return ast.Function{
   225  		ArgTypes:   []ast.Type{ast.TypeString, ast.TypeString},
   226  		ReturnType: ast.TypeString,
   227  		Callback: func(args []interface{}) (interface{}, error) {
   228  			var list []string
   229  			for _, arg := range args[1:] {
   230  				parts := StringList(arg.(string)).Slice()
   231  				list = append(list, parts...)
   232  			}
   233  
   234  			return strings.Join(list, args[0].(string)), nil
   235  		},
   236  	}
   237  }
   238  
   239  // interpolationFuncReplace implements the "replace" function that does
   240  // string replacement.
   241  func interpolationFuncReplace() ast.Function {
   242  	return ast.Function{
   243  		ArgTypes:   []ast.Type{ast.TypeString, ast.TypeString, ast.TypeString},
   244  		ReturnType: ast.TypeString,
   245  		Callback: func(args []interface{}) (interface{}, error) {
   246  			s := args[0].(string)
   247  			search := args[1].(string)
   248  			replace := args[2].(string)
   249  
   250  			// We search/replace using a regexp if the string is surrounded
   251  			// in forward slashes.
   252  			if len(search) > 1 && search[0] == '/' && search[len(search)-1] == '/' {
   253  				re, err := regexp.Compile(search[1 : len(search)-1])
   254  				if err != nil {
   255  					return nil, err
   256  				}
   257  
   258  				return re.ReplaceAllString(s, replace), nil
   259  			}
   260  
   261  			return strings.Replace(s, search, replace, -1), nil
   262  		},
   263  	}
   264  }
   265  
   266  func interpolationFuncLength() ast.Function {
   267  	return ast.Function{
   268  		ArgTypes:   []ast.Type{ast.TypeString},
   269  		ReturnType: ast.TypeInt,
   270  		Variadic:   false,
   271  		Callback: func(args []interface{}) (interface{}, error) {
   272  			if !IsStringList(args[0].(string)) {
   273  				return len(args[0].(string)), nil
   274  			}
   275  
   276  			length := 0
   277  			for _, arg := range args {
   278  				length += StringList(arg.(string)).Length()
   279  			}
   280  			return length, nil
   281  		},
   282  	}
   283  }
   284  
   285  // interpolationFuncSplit implements the "split" function that allows
   286  // strings to split into multi-variable values
   287  func interpolationFuncSplit() ast.Function {
   288  	return ast.Function{
   289  		ArgTypes:   []ast.Type{ast.TypeString, ast.TypeString},
   290  		ReturnType: ast.TypeString,
   291  		Callback: func(args []interface{}) (interface{}, error) {
   292  			sep := args[0].(string)
   293  			s := args[1].(string)
   294  			return NewStringList(strings.Split(s, sep)).String(), nil
   295  		},
   296  	}
   297  }
   298  
   299  // interpolationFuncLookup implements the "lookup" function that allows
   300  // dynamic lookups of map types within a Terraform configuration.
   301  func interpolationFuncLookup(vs map[string]ast.Variable) ast.Function {
   302  	return ast.Function{
   303  		ArgTypes:   []ast.Type{ast.TypeString, ast.TypeString},
   304  		ReturnType: ast.TypeString,
   305  		Callback: func(args []interface{}) (interface{}, error) {
   306  			k := fmt.Sprintf("var.%s.%s", args[0].(string), args[1].(string))
   307  			v, ok := vs[k]
   308  			if !ok {
   309  				return "", fmt.Errorf(
   310  					"lookup in '%s' failed to find '%s'",
   311  					args[0].(string), args[1].(string))
   312  			}
   313  			if v.Type != ast.TypeString {
   314  				return "", fmt.Errorf(
   315  					"lookup in '%s' for '%s' has bad type %s",
   316  					args[0].(string), args[1].(string), v.Type)
   317  			}
   318  
   319  			return v.Value.(string), nil
   320  		},
   321  	}
   322  }
   323  
   324  // interpolationFuncElement implements the "element" function that allows
   325  // a specific index to be looked up in a multi-variable value. Note that this will
   326  // wrap if the index is larger than the number of elements in the multi-variable value.
   327  func interpolationFuncElement() ast.Function {
   328  	return ast.Function{
   329  		ArgTypes:   []ast.Type{ast.TypeString, ast.TypeString},
   330  		ReturnType: ast.TypeString,
   331  		Callback: func(args []interface{}) (interface{}, error) {
   332  			list := StringList(args[0].(string))
   333  
   334  			index, err := strconv.Atoi(args[1].(string))
   335  			if err != nil {
   336  				return "", fmt.Errorf(
   337  					"invalid number for index, got %s", args[1])
   338  			}
   339  
   340  			v := list.Element(index)
   341  			return v, nil
   342  		},
   343  	}
   344  }
   345  
   346  // interpolationFuncKeys implements the "keys" function that yields a list of
   347  // keys of map types within a Terraform configuration.
   348  func interpolationFuncKeys(vs map[string]ast.Variable) ast.Function {
   349  	return ast.Function{
   350  		ArgTypes:   []ast.Type{ast.TypeString},
   351  		ReturnType: ast.TypeString,
   352  		Callback: func(args []interface{}) (interface{}, error) {
   353  			// Prefix must include ending dot to be a map
   354  			prefix := fmt.Sprintf("var.%s.", args[0].(string))
   355  			keys := make([]string, 0, len(vs))
   356  			for k, _ := range vs {
   357  				if !strings.HasPrefix(k, prefix) {
   358  					continue
   359  				}
   360  				keys = append(keys, k[len(prefix):])
   361  			}
   362  
   363  			if len(keys) <= 0 {
   364  				return "", fmt.Errorf(
   365  					"failed to find map '%s'",
   366  					args[0].(string))
   367  			}
   368  
   369  			sort.Strings(keys)
   370  
   371  			return NewStringList(keys).String(), nil
   372  		},
   373  	}
   374  }
   375  
   376  // interpolationFuncValues implements the "values" function that yields a list of
   377  // keys of map types within a Terraform configuration.
   378  func interpolationFuncValues(vs map[string]ast.Variable) ast.Function {
   379  	return ast.Function{
   380  		ArgTypes:   []ast.Type{ast.TypeString},
   381  		ReturnType: ast.TypeString,
   382  		Callback: func(args []interface{}) (interface{}, error) {
   383  			// Prefix must include ending dot to be a map
   384  			prefix := fmt.Sprintf("var.%s.", args[0].(string))
   385  			keys := make([]string, 0, len(vs))
   386  			for k, _ := range vs {
   387  				if !strings.HasPrefix(k, prefix) {
   388  					continue
   389  				}
   390  				keys = append(keys, k)
   391  			}
   392  
   393  			if len(keys) <= 0 {
   394  				return "", fmt.Errorf(
   395  					"failed to find map '%s'",
   396  					args[0].(string))
   397  			}
   398  
   399  			sort.Strings(keys)
   400  
   401  			vals := make([]string, 0, len(keys))
   402  
   403  			for _, k := range keys {
   404  				v := vs[k]
   405  				if v.Type != ast.TypeString {
   406  					return "", fmt.Errorf("values(): %q has bad type %s", k, v.Type)
   407  				}
   408  				vals = append(vals, vs[k].Value.(string))
   409  			}
   410  
   411  			return NewStringList(vals).String(), nil
   412  		},
   413  	}
   414  }
   415  
   416  // interpolationFuncBase64Encode implements the "base64encode" function that
   417  // allows Base64 encoding.
   418  func interpolationFuncBase64Encode() ast.Function {
   419  	return ast.Function{
   420  		ArgTypes:   []ast.Type{ast.TypeString},
   421  		ReturnType: ast.TypeString,
   422  		Callback: func(args []interface{}) (interface{}, error) {
   423  			s := args[0].(string)
   424  			return base64.StdEncoding.EncodeToString([]byte(s)), nil
   425  		},
   426  	}
   427  }
   428  
   429  // interpolationFuncBase64Decode implements the "base64decode" function that
   430  // allows Base64 decoding.
   431  func interpolationFuncBase64Decode() ast.Function {
   432  	return ast.Function{
   433  		ArgTypes:   []ast.Type{ast.TypeString},
   434  		ReturnType: ast.TypeString,
   435  		Callback: func(args []interface{}) (interface{}, error) {
   436  			s := args[0].(string)
   437  			sDec, err := base64.StdEncoding.DecodeString(s)
   438  			if err != nil {
   439  				return "", fmt.Errorf("failed to decode base64 data '%s'", s)
   440  			}
   441  			return string(sDec), nil
   442  		},
   443  	}
   444  }