github.com/2matz/terraform@v0.6.1-0.20150714181608-a03cbdb5d5bd/config/interpolate_funcs.go (about)

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