github.com/nicgrayson/terraform@v0.4.3-0.20150415203910-c4de50829380/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  		"length":  interpolationFuncLength(),
    27  
    28  		// Concat is a little useless now since we supported embeddded
    29  		// interpolations but we keep it around for backwards compat reasons.
    30  		"concat": interpolationFuncConcat(),
    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  			for _, v := range args {
    47  				b.WriteString(v.(string))
    48  			}
    49  
    50  			return b.String(), nil
    51  		},
    52  	}
    53  }
    54  
    55  // interpolationFuncFile implements the "file" function that allows
    56  // loading contents from a file.
    57  func interpolationFuncFile() ast.Function {
    58  	return ast.Function{
    59  		ArgTypes:   []ast.Type{ast.TypeString},
    60  		ReturnType: ast.TypeString,
    61  		Callback: func(args []interface{}) (interface{}, error) {
    62  			path, err := homedir.Expand(args[0].(string))
    63  			if err != nil {
    64  				return "", err
    65  			}
    66  			data, err := ioutil.ReadFile(path)
    67  			if err != nil {
    68  				return "", err
    69  			}
    70  
    71  			return string(data), nil
    72  		},
    73  	}
    74  }
    75  
    76  // interpolationFuncFormat implements the "replace" function that does
    77  // string replacement.
    78  func interpolationFuncFormat() ast.Function {
    79  	return ast.Function{
    80  		ArgTypes:     []ast.Type{ast.TypeString},
    81  		Variadic:     true,
    82  		VariadicType: ast.TypeAny,
    83  		ReturnType:   ast.TypeString,
    84  		Callback: func(args []interface{}) (interface{}, error) {
    85  			format := args[0].(string)
    86  			return fmt.Sprintf(format, args[1:]...), nil
    87  		},
    88  	}
    89  }
    90  
    91  // interpolationFuncJoin implements the "join" function that allows
    92  // multi-variable values to be joined by some character.
    93  func interpolationFuncJoin() ast.Function {
    94  	return ast.Function{
    95  		ArgTypes:   []ast.Type{ast.TypeString, ast.TypeString},
    96  		ReturnType: ast.TypeString,
    97  		Callback: func(args []interface{}) (interface{}, error) {
    98  			var list []string
    99  			for _, arg := range args[1:] {
   100  				parts := strings.Split(arg.(string), InterpSplitDelim)
   101  				list = append(list, parts...)
   102  			}
   103  
   104  			return strings.Join(list, args[0].(string)), nil
   105  		},
   106  	}
   107  }
   108  
   109  // interpolationFuncReplace implements the "replace" function that does
   110  // string replacement.
   111  func interpolationFuncReplace() ast.Function {
   112  	return ast.Function{
   113  		ArgTypes:   []ast.Type{ast.TypeString, ast.TypeString, ast.TypeString},
   114  		ReturnType: ast.TypeString,
   115  		Callback: func(args []interface{}) (interface{}, error) {
   116  			s := args[0].(string)
   117  			search := args[1].(string)
   118  			replace := args[2].(string)
   119  
   120  			// We search/replace using a regexp if the string is surrounded
   121  			// in forward slashes.
   122  			if len(search) > 1 && search[0] == '/' && search[len(search)-1] == '/' {
   123  				re, err := regexp.Compile(search[1 : len(search)-1])
   124  				if err != nil {
   125  					return nil, err
   126  				}
   127  
   128  				return re.ReplaceAllString(s, replace), nil
   129  			}
   130  
   131  			return strings.Replace(s, search, replace, -1), nil
   132  		},
   133  	}
   134  }
   135  
   136  func interpolationFuncLength() ast.Function {
   137  	return ast.Function{
   138  		ArgTypes:   []ast.Type{ast.TypeString},
   139  		ReturnType: ast.TypeInt,
   140  		Variadic:   false,
   141  		Callback: func(args []interface{}) (interface{}, error) {
   142  			if !strings.Contains(args[0].(string), InterpSplitDelim) {
   143  				return len(args[0].(string)), nil
   144  			}
   145  
   146  			var list []string
   147  			for _, arg := range args {
   148  				parts := strings.Split(arg.(string), InterpSplitDelim)
   149  				for _, part := range parts {
   150  					list = append(list, part)
   151  				}
   152  			}
   153  			return len(list), nil
   154  		},
   155  	}
   156  }
   157  
   158  // interpolationFuncSplit implements the "split" function that allows
   159  // strings to split into multi-variable values
   160  func interpolationFuncSplit() ast.Function {
   161  	return ast.Function{
   162  		ArgTypes:   []ast.Type{ast.TypeString, ast.TypeString},
   163  		ReturnType: ast.TypeString,
   164  		Callback: func(args []interface{}) (interface{}, error) {
   165  			return strings.Replace(args[1].(string), args[0].(string), InterpSplitDelim, -1), nil
   166  		},
   167  	}
   168  }
   169  
   170  // interpolationFuncLookup implements the "lookup" function that allows
   171  // dynamic lookups of map types within a Terraform configuration.
   172  func interpolationFuncLookup(vs map[string]ast.Variable) ast.Function {
   173  	return ast.Function{
   174  		ArgTypes:   []ast.Type{ast.TypeString, ast.TypeString},
   175  		ReturnType: ast.TypeString,
   176  		Callback: func(args []interface{}) (interface{}, error) {
   177  			k := fmt.Sprintf("var.%s.%s", args[0].(string), args[1].(string))
   178  			v, ok := vs[k]
   179  			if !ok {
   180  				return "", fmt.Errorf(
   181  					"lookup in '%s' failed to find '%s'",
   182  					args[0].(string), args[1].(string))
   183  			}
   184  			if v.Type != ast.TypeString {
   185  				return "", fmt.Errorf(
   186  					"lookup in '%s' for '%s' has bad type %s",
   187  					args[0].(string), args[1].(string), v.Type)
   188  			}
   189  
   190  			return v.Value.(string), nil
   191  		},
   192  	}
   193  }
   194  
   195  // interpolationFuncElement implements the "element" function that allows
   196  // a specific index to be looked up in a multi-variable value. Note that this will
   197  // wrap if the index is larger than the number of elements in the multi-variable value.
   198  func interpolationFuncElement() ast.Function {
   199  	return ast.Function{
   200  		ArgTypes:   []ast.Type{ast.TypeString, ast.TypeString},
   201  		ReturnType: ast.TypeString,
   202  		Callback: func(args []interface{}) (interface{}, error) {
   203  			list := strings.Split(args[0].(string), InterpSplitDelim)
   204  
   205  			index, err := strconv.Atoi(args[1].(string))
   206  			if err != nil {
   207  				return "", fmt.Errorf(
   208  					"invalid number for index, got %s", args[1])
   209  			}
   210  
   211  			v := list[index%len(list)]
   212  			return v, nil
   213  		},
   214  	}
   215  }