github.com/tarrant/terraform@v0.3.8-0.20150402012457-f68c9eee638e/config/interpolate_funcs.go (about)

     1  package config
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"io/ioutil"
     7  	"regexp"
     8  	"strconv"
     9  	"strings"
    10  
    11  	"github.com/hashicorp/terraform/config/lang/ast"
    12  	"github.com/mitchellh/go-homedir"
    13  )
    14  
    15  // Funcs is the mapping of built-in functions for configuration.
    16  var Funcs map[string]ast.Function
    17  
    18  func init() {
    19  	Funcs = map[string]ast.Function{
    20  		"file":    interpolationFuncFile(),
    21  		"format":  interpolationFuncFormat(),
    22  		"join":    interpolationFuncJoin(),
    23  		"element": interpolationFuncElement(),
    24  		"replace": interpolationFuncReplace(),
    25  		"split":   interpolationFuncSplit(),
    26  
    27  		// Concat is a little useless now since we supported embeddded
    28  		// interpolations but we keep it around for backwards compat reasons.
    29  		"concat": interpolationFuncConcat(),
    30  	}
    31  }
    32  
    33  // interpolationFuncConcat implements the "concat" function that
    34  // concatenates multiple strings. This isn't actually necessary anymore
    35  // since our language supports string concat natively, but for backwards
    36  // compat we do this.
    37  func interpolationFuncConcat() ast.Function {
    38  	return ast.Function{
    39  		ArgTypes:     []ast.Type{ast.TypeString},
    40  		ReturnType:   ast.TypeString,
    41  		Variadic:     true,
    42  		VariadicType: ast.TypeString,
    43  		Callback: func(args []interface{}) (interface{}, error) {
    44  			var b bytes.Buffer
    45  			for _, v := range args {
    46  				b.WriteString(v.(string))
    47  			}
    48  
    49  			return b.String(), nil
    50  		},
    51  	}
    52  }
    53  
    54  // interpolationFuncFile implements the "file" function that allows
    55  // loading contents from a file.
    56  func interpolationFuncFile() ast.Function {
    57  	return ast.Function{
    58  		ArgTypes:   []ast.Type{ast.TypeString},
    59  		ReturnType: ast.TypeString,
    60  		Callback: func(args []interface{}) (interface{}, error) {
    61  			path, err := homedir.Expand(args[0].(string))
    62  			if err != nil {
    63  				return "", err
    64  			}
    65  			data, err := ioutil.ReadFile(path)
    66  			if err != nil {
    67  				return "", err
    68  			}
    69  
    70  			return string(data), nil
    71  		},
    72  	}
    73  }
    74  
    75  // interpolationFuncFormat implements the "replace" function that does
    76  // string replacement.
    77  func interpolationFuncFormat() ast.Function {
    78  	return ast.Function{
    79  		ArgTypes:     []ast.Type{ast.TypeString},
    80  		Variadic:     true,
    81  		VariadicType: ast.TypeAny,
    82  		ReturnType:   ast.TypeString,
    83  		Callback: func(args []interface{}) (interface{}, error) {
    84  			format := args[0].(string)
    85  			return fmt.Sprintf(format, args[1:]...), nil
    86  		},
    87  	}
    88  }
    89  
    90  // interpolationFuncJoin implements the "join" function that allows
    91  // multi-variable values to be joined by some character.
    92  func interpolationFuncJoin() ast.Function {
    93  	return ast.Function{
    94  		ArgTypes:   []ast.Type{ast.TypeString, ast.TypeString},
    95  		ReturnType: ast.TypeString,
    96  		Callback: func(args []interface{}) (interface{}, error) {
    97  			var list []string
    98  			for _, arg := range args[1:] {
    99  				parts := strings.Split(arg.(string), InterpSplitDelim)
   100  				list = append(list, parts...)
   101  			}
   102  
   103  			return strings.Join(list, args[0].(string)), nil
   104  		},
   105  	}
   106  }
   107  
   108  // interpolationFuncReplace implements the "replace" function that does
   109  // string replacement.
   110  func interpolationFuncReplace() ast.Function {
   111  	return ast.Function{
   112  		ArgTypes:   []ast.Type{ast.TypeString, ast.TypeString, ast.TypeString},
   113  		ReturnType: ast.TypeString,
   114  		Callback: func(args []interface{}) (interface{}, error) {
   115  			s := args[0].(string)
   116  			search := args[1].(string)
   117  			replace := args[2].(string)
   118  
   119  			// We search/replace using a regexp if the string is surrounded
   120  			// in forward slashes.
   121  			if len(search) > 1 && search[0] == '/' && search[len(search)-1] == '/' {
   122  				re, err := regexp.Compile(search[1 : len(search)-1])
   123  				if err != nil {
   124  					return nil, err
   125  				}
   126  
   127  				return re.ReplaceAllString(s, replace), nil
   128  			}
   129  
   130  			return strings.Replace(s, search, replace, -1), nil
   131  		},
   132  	}
   133  }
   134  
   135  // interpolationFuncSplit implements the "split" function that allows
   136  // strings to split into multi-variable values
   137  func interpolationFuncSplit() ast.Function {
   138  	return ast.Function{
   139  		ArgTypes:   []ast.Type{ast.TypeString, ast.TypeString},
   140  		ReturnType: ast.TypeString,
   141  		Callback: func(args []interface{}) (interface{}, error) {
   142  			return strings.Replace(args[1].(string), args[0].(string), InterpSplitDelim, -1), nil
   143  		},
   144  	}
   145  }
   146  
   147  // interpolationFuncLookup implements the "lookup" function that allows
   148  // dynamic lookups of map types within a Terraform configuration.
   149  func interpolationFuncLookup(vs map[string]ast.Variable) ast.Function {
   150  	return ast.Function{
   151  		ArgTypes:   []ast.Type{ast.TypeString, ast.TypeString},
   152  		ReturnType: ast.TypeString,
   153  		Callback: func(args []interface{}) (interface{}, error) {
   154  			k := fmt.Sprintf("var.%s.%s", args[0].(string), args[1].(string))
   155  			v, ok := vs[k]
   156  			if !ok {
   157  				return "", fmt.Errorf(
   158  					"lookup in '%s' failed to find '%s'",
   159  					args[0].(string), args[1].(string))
   160  			}
   161  			if v.Type != ast.TypeString {
   162  				return "", fmt.Errorf(
   163  					"lookup in '%s' for '%s' has bad type %s",
   164  					args[0].(string), args[1].(string), v.Type)
   165  			}
   166  
   167  			return v.Value.(string), nil
   168  		},
   169  	}
   170  }
   171  
   172  // interpolationFuncElement implements the "element" function that allows
   173  // a specific index to be looked up in a multi-variable value. Note that this will
   174  // wrap if the index is larger than the number of elements in the multi-variable value.
   175  func interpolationFuncElement() ast.Function {
   176  	return ast.Function{
   177  		ArgTypes:   []ast.Type{ast.TypeString, ast.TypeString},
   178  		ReturnType: ast.TypeString,
   179  		Callback: func(args []interface{}) (interface{}, error) {
   180  			list := strings.Split(args[0].(string), InterpSplitDelim)
   181  
   182  			index, err := strconv.Atoi(args[1].(string))
   183  			if err != nil {
   184  				return "", fmt.Errorf(
   185  					"invalid number for index, got %s", args[1])
   186  			}
   187  
   188  			v := list[index%len(list)]
   189  			return v, nil
   190  		},
   191  	}
   192  }