github.com/armen/terraform@v0.5.2-0.20150529052519-caa8117a08f1/config/interpolate_funcs.go (about)

     1  package config
     2  
     3  import (
     4  	"bytes"
     5  	"errors"
     6  	"fmt"
     7  	"io/ioutil"
     8  	"regexp"
     9  	"strconv"
    10  	"strings"
    11  
    12  	"github.com/hashicorp/terraform/config/lang/ast"
    13  	"github.com/mitchellh/go-homedir"
    14  )
    15  
    16  // Funcs is the mapping of built-in functions for configuration.
    17  var Funcs map[string]ast.Function
    18  
    19  func init() {
    20  	Funcs = map[string]ast.Function{
    21  		"file":       interpolationFuncFile(),
    22  		"format":     interpolationFuncFormat(),
    23  		"formatlist": interpolationFuncFormatList(),
    24  		"join":       interpolationFuncJoin(),
    25  		"element":    interpolationFuncElement(),
    26  		"replace":    interpolationFuncReplace(),
    27  		"split":      interpolationFuncSplit(),
    28  		"length":     interpolationFuncLength(),
    29  
    30  		// Concat is a little useless now since we supported embeddded
    31  		// interpolations but we keep it around for backwards compat reasons.
    32  		"concat": interpolationFuncConcat(),
    33  	}
    34  }
    35  
    36  // interpolationFuncConcat implements the "concat" function that
    37  // concatenates multiple strings. This isn't actually necessary anymore
    38  // since our language supports string concat natively, but for backwards
    39  // compat we do this.
    40  func interpolationFuncConcat() ast.Function {
    41  	return ast.Function{
    42  		ArgTypes:     []ast.Type{ast.TypeString},
    43  		ReturnType:   ast.TypeString,
    44  		Variadic:     true,
    45  		VariadicType: ast.TypeString,
    46  		Callback: func(args []interface{}) (interface{}, error) {
    47  			var b bytes.Buffer
    48  			for _, v := range args {
    49  				b.WriteString(v.(string))
    50  			}
    51  
    52  			return b.String(), nil
    53  		},
    54  	}
    55  }
    56  
    57  // interpolationFuncFile implements the "file" function that allows
    58  // loading contents from a file.
    59  func interpolationFuncFile() ast.Function {
    60  	return ast.Function{
    61  		ArgTypes:   []ast.Type{ast.TypeString},
    62  		ReturnType: ast.TypeString,
    63  		Callback: func(args []interface{}) (interface{}, error) {
    64  			path, err := homedir.Expand(args[0].(string))
    65  			if err != nil {
    66  				return "", err
    67  			}
    68  			data, err := ioutil.ReadFile(path)
    69  			if err != nil {
    70  				return "", err
    71  			}
    72  
    73  			return string(data), nil
    74  		},
    75  	}
    76  }
    77  
    78  // interpolationFuncFormat implements the "format" function that does
    79  // string formatting.
    80  func interpolationFuncFormat() ast.Function {
    81  	return ast.Function{
    82  		ArgTypes:     []ast.Type{ast.TypeString},
    83  		Variadic:     true,
    84  		VariadicType: ast.TypeAny,
    85  		ReturnType:   ast.TypeString,
    86  		Callback: func(args []interface{}) (interface{}, error) {
    87  			format := args[0].(string)
    88  			return fmt.Sprintf(format, args[1:]...), nil
    89  		},
    90  	}
    91  }
    92  
    93  // interpolationFuncFormatList implements the "formatlist" function that does
    94  // string formatting on lists.
    95  func interpolationFuncFormatList() ast.Function {
    96  	return ast.Function{
    97  		ArgTypes:     []ast.Type{ast.TypeString},
    98  		Variadic:     true,
    99  		VariadicType: ast.TypeAny,
   100  		ReturnType:   ast.TypeString,
   101  		Callback: func(args []interface{}) (interface{}, error) {
   102  			// Make a copy of the variadic part of args
   103  			// to avoid modifying the original.
   104  			varargs := make([]interface{}, len(args)-1)
   105  			copy(varargs, args[1:])
   106  
   107  			// Convert arguments that are lists into slices.
   108  			// Confirm along the way that all lists have the same length (n).
   109  			var n int
   110  			for i := 1; i < len(args); i++ {
   111  				s, ok := args[i].(string)
   112  				if !ok {
   113  					continue
   114  				}
   115  				parts := strings.Split(s, InterpSplitDelim)
   116  				if len(parts) == 1 {
   117  					continue
   118  				}
   119  				varargs[i-1] = parts
   120  				if n == 0 {
   121  					// first list we've seen
   122  					n = len(parts)
   123  					continue
   124  				}
   125  				if n != len(parts) {
   126  					return nil, fmt.Errorf("format: mismatched list lengths: %d != %d", n, len(parts))
   127  				}
   128  			}
   129  
   130  			if n == 0 {
   131  				return nil, errors.New("no lists in arguments to formatlist")
   132  			}
   133  
   134  			// Do the formatting.
   135  			format := args[0].(string)
   136  
   137  			// Generate a list of formatted strings.
   138  			list := make([]string, n)
   139  			fmtargs := make([]interface{}, len(varargs))
   140  			for i := 0; i < n; i++ {
   141  				for j, arg := range varargs {
   142  					switch arg := arg.(type) {
   143  					default:
   144  						fmtargs[j] = arg
   145  					case []string:
   146  						fmtargs[j] = arg[i]
   147  					}
   148  				}
   149  				list[i] = fmt.Sprintf(format, fmtargs...)
   150  			}
   151  			return strings.Join(list, InterpSplitDelim), nil
   152  		},
   153  	}
   154  }
   155  
   156  // interpolationFuncJoin implements the "join" function that allows
   157  // multi-variable values to be joined by some character.
   158  func interpolationFuncJoin() ast.Function {
   159  	return ast.Function{
   160  		ArgTypes:   []ast.Type{ast.TypeString, ast.TypeString},
   161  		ReturnType: ast.TypeString,
   162  		Callback: func(args []interface{}) (interface{}, error) {
   163  			var list []string
   164  			for _, arg := range args[1:] {
   165  				parts := strings.Split(arg.(string), InterpSplitDelim)
   166  				list = append(list, parts...)
   167  			}
   168  
   169  			return strings.Join(list, args[0].(string)), nil
   170  		},
   171  	}
   172  }
   173  
   174  // interpolationFuncReplace implements the "replace" function that does
   175  // string replacement.
   176  func interpolationFuncReplace() ast.Function {
   177  	return ast.Function{
   178  		ArgTypes:   []ast.Type{ast.TypeString, ast.TypeString, ast.TypeString},
   179  		ReturnType: ast.TypeString,
   180  		Callback: func(args []interface{}) (interface{}, error) {
   181  			s := args[0].(string)
   182  			search := args[1].(string)
   183  			replace := args[2].(string)
   184  
   185  			// We search/replace using a regexp if the string is surrounded
   186  			// in forward slashes.
   187  			if len(search) > 1 && search[0] == '/' && search[len(search)-1] == '/' {
   188  				re, err := regexp.Compile(search[1 : len(search)-1])
   189  				if err != nil {
   190  					return nil, err
   191  				}
   192  
   193  				return re.ReplaceAllString(s, replace), nil
   194  			}
   195  
   196  			return strings.Replace(s, search, replace, -1), nil
   197  		},
   198  	}
   199  }
   200  
   201  func interpolationFuncLength() ast.Function {
   202  	return ast.Function{
   203  		ArgTypes:   []ast.Type{ast.TypeString},
   204  		ReturnType: ast.TypeInt,
   205  		Variadic:   false,
   206  		Callback: func(args []interface{}) (interface{}, error) {
   207  			if !strings.Contains(args[0].(string), InterpSplitDelim) {
   208  				return len(args[0].(string)), nil
   209  			}
   210  
   211  			var list []string
   212  			for _, arg := range args {
   213  				parts := strings.Split(arg.(string), InterpSplitDelim)
   214  				for _, part := range parts {
   215  					list = append(list, part)
   216  				}
   217  			}
   218  			return len(list), nil
   219  		},
   220  	}
   221  }
   222  
   223  // interpolationFuncSplit implements the "split" function that allows
   224  // strings to split into multi-variable values
   225  func interpolationFuncSplit() ast.Function {
   226  	return ast.Function{
   227  		ArgTypes:   []ast.Type{ast.TypeString, ast.TypeString},
   228  		ReturnType: ast.TypeString,
   229  		Callback: func(args []interface{}) (interface{}, error) {
   230  			return strings.Replace(args[1].(string), args[0].(string), InterpSplitDelim, -1), nil
   231  		},
   232  	}
   233  }
   234  
   235  // interpolationFuncLookup implements the "lookup" function that allows
   236  // dynamic lookups of map types within a Terraform configuration.
   237  func interpolationFuncLookup(vs map[string]ast.Variable) ast.Function {
   238  	return ast.Function{
   239  		ArgTypes:   []ast.Type{ast.TypeString, ast.TypeString},
   240  		ReturnType: ast.TypeString,
   241  		Callback: func(args []interface{}) (interface{}, error) {
   242  			k := fmt.Sprintf("var.%s.%s", args[0].(string), args[1].(string))
   243  			v, ok := vs[k]
   244  			if !ok {
   245  				return "", fmt.Errorf(
   246  					"lookup in '%s' failed to find '%s'",
   247  					args[0].(string), args[1].(string))
   248  			}
   249  			if v.Type != ast.TypeString {
   250  				return "", fmt.Errorf(
   251  					"lookup in '%s' for '%s' has bad type %s",
   252  					args[0].(string), args[1].(string), v.Type)
   253  			}
   254  
   255  			return v.Value.(string), nil
   256  		},
   257  	}
   258  }
   259  
   260  // interpolationFuncElement implements the "element" function that allows
   261  // a specific index to be looked up in a multi-variable value. Note that this will
   262  // wrap if the index is larger than the number of elements in the multi-variable value.
   263  func interpolationFuncElement() ast.Function {
   264  	return ast.Function{
   265  		ArgTypes:   []ast.Type{ast.TypeString, ast.TypeString},
   266  		ReturnType: ast.TypeString,
   267  		Callback: func(args []interface{}) (interface{}, error) {
   268  			list := strings.Split(args[0].(string), InterpSplitDelim)
   269  
   270  			index, err := strconv.Atoi(args[1].(string))
   271  			if err != nil {
   272  				return "", fmt.Errorf(
   273  					"invalid number for index, got %s", args[1])
   274  			}
   275  
   276  			v := list[index%len(list)]
   277  			return v, nil
   278  		},
   279  	}
   280  }