2014-07-21 21:56:03 +02:00
|
|
|
package config
|
|
|
|
|
2014-07-21 22:12:43 +02:00
|
|
|
import (
|
2015-01-13 21:47:54 +01:00
|
|
|
"bytes"
|
2014-07-21 22:12:43 +02:00
|
|
|
"fmt"
|
2014-07-23 03:35:36 +02:00
|
|
|
"io/ioutil"
|
2015-03-02 18:37:40 +01:00
|
|
|
"regexp"
|
2014-11-07 19:23:02 +01:00
|
|
|
"strconv"
|
2014-07-21 22:12:43 +02:00
|
|
|
"strings"
|
2015-01-13 20:50:44 +01:00
|
|
|
|
|
|
|
"github.com/hashicorp/terraform/config/lang/ast"
|
2015-03-31 01:52:39 +02:00
|
|
|
"github.com/mitchellh/go-homedir"
|
2014-07-21 22:12:43 +02:00
|
|
|
)
|
|
|
|
|
2014-07-21 21:56:03 +02:00
|
|
|
// Funcs is the mapping of built-in functions for configuration.
|
2015-01-15 07:01:42 +01:00
|
|
|
var Funcs map[string]ast.Function
|
2014-07-21 21:56:03 +02:00
|
|
|
|
|
|
|
func init() {
|
2015-01-15 07:01:42 +01:00
|
|
|
Funcs = map[string]ast.Function{
|
2015-01-13 21:06:04 +01:00
|
|
|
"file": interpolationFuncFile(),
|
2015-03-02 19:26:06 +01:00
|
|
|
"format": interpolationFuncFormat(),
|
2015-01-13 21:06:04 +01:00
|
|
|
"join": interpolationFuncJoin(),
|
2015-01-13 20:50:44 +01:00
|
|
|
"element": interpolationFuncElement(),
|
2015-03-02 18:37:40 +01:00
|
|
|
"replace": interpolationFuncReplace(),
|
2015-01-28 22:59:16 +01:00
|
|
|
"split": interpolationFuncSplit(),
|
2015-04-12 16:17:26 +02:00
|
|
|
"length": interpolationFuncLength(),
|
2015-03-02 18:37:40 +01:00
|
|
|
|
|
|
|
// Concat is a little useless now since we supported embeddded
|
|
|
|
// interpolations but we keep it around for backwards compat reasons.
|
|
|
|
"concat": interpolationFuncConcat(),
|
2014-07-21 21:56:03 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-01-13 21:47:54 +01:00
|
|
|
// interpolationFuncConcat implements the "concat" function that
|
|
|
|
// concatenates multiple strings. This isn't actually necessary anymore
|
|
|
|
// since our language supports string concat natively, but for backwards
|
|
|
|
// compat we do this.
|
2015-01-15 07:01:42 +01:00
|
|
|
func interpolationFuncConcat() ast.Function {
|
|
|
|
return ast.Function{
|
2015-01-13 21:47:54 +01:00
|
|
|
ArgTypes: []ast.Type{ast.TypeString},
|
|
|
|
ReturnType: ast.TypeString,
|
|
|
|
Variadic: true,
|
|
|
|
VariadicType: ast.TypeString,
|
|
|
|
Callback: func(args []interface{}) (interface{}, error) {
|
|
|
|
var b bytes.Buffer
|
|
|
|
for _, v := range args {
|
|
|
|
b.WriteString(v.(string))
|
|
|
|
}
|
|
|
|
|
|
|
|
return b.String(), nil
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-07-23 03:35:36 +02:00
|
|
|
// interpolationFuncFile implements the "file" function that allows
|
|
|
|
// loading contents from a file.
|
2015-01-15 07:01:42 +01:00
|
|
|
func interpolationFuncFile() ast.Function {
|
|
|
|
return ast.Function{
|
2015-01-13 20:50:44 +01:00
|
|
|
ArgTypes: []ast.Type{ast.TypeString},
|
|
|
|
ReturnType: ast.TypeString,
|
|
|
|
Callback: func(args []interface{}) (interface{}, error) {
|
2015-03-31 01:52:39 +02:00
|
|
|
path, err := homedir.Expand(args[0].(string))
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
data, err := ioutil.ReadFile(path)
|
2015-01-13 20:50:44 +01:00
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
|
|
|
|
return string(data), nil
|
|
|
|
},
|
2014-07-23 03:35:36 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-03-02 19:26:06 +01:00
|
|
|
// interpolationFuncFormat implements the "replace" function that does
|
|
|
|
// string replacement.
|
|
|
|
func interpolationFuncFormat() ast.Function {
|
|
|
|
return ast.Function{
|
|
|
|
ArgTypes: []ast.Type{ast.TypeString},
|
|
|
|
Variadic: true,
|
|
|
|
VariadicType: ast.TypeAny,
|
|
|
|
ReturnType: ast.TypeString,
|
|
|
|
Callback: func(args []interface{}) (interface{}, error) {
|
|
|
|
format := args[0].(string)
|
|
|
|
return fmt.Sprintf(format, args[1:]...), nil
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-10-10 06:22:35 +02:00
|
|
|
// interpolationFuncJoin implements the "join" function that allows
|
|
|
|
// multi-variable values to be joined by some character.
|
2015-01-15 07:01:42 +01:00
|
|
|
func interpolationFuncJoin() ast.Function {
|
|
|
|
return ast.Function{
|
2015-01-13 20:50:44 +01:00
|
|
|
ArgTypes: []ast.Type{ast.TypeString, ast.TypeString},
|
|
|
|
ReturnType: ast.TypeString,
|
|
|
|
Callback: func(args []interface{}) (interface{}, error) {
|
|
|
|
var list []string
|
|
|
|
for _, arg := range args[1:] {
|
|
|
|
parts := strings.Split(arg.(string), InterpSplitDelim)
|
|
|
|
list = append(list, parts...)
|
|
|
|
}
|
|
|
|
|
|
|
|
return strings.Join(list, args[0].(string)), nil
|
|
|
|
},
|
2014-10-10 06:22:35 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-03-02 18:37:40 +01:00
|
|
|
// interpolationFuncReplace implements the "replace" function that does
|
|
|
|
// string replacement.
|
|
|
|
func interpolationFuncReplace() ast.Function {
|
|
|
|
return ast.Function{
|
|
|
|
ArgTypes: []ast.Type{ast.TypeString, ast.TypeString, ast.TypeString},
|
|
|
|
ReturnType: ast.TypeString,
|
|
|
|
Callback: func(args []interface{}) (interface{}, error) {
|
|
|
|
s := args[0].(string)
|
|
|
|
search := args[1].(string)
|
|
|
|
replace := args[2].(string)
|
|
|
|
|
|
|
|
// We search/replace using a regexp if the string is surrounded
|
|
|
|
// in forward slashes.
|
2015-03-02 18:46:46 +01:00
|
|
|
if len(search) > 1 && search[0] == '/' && search[len(search)-1] == '/' {
|
2015-03-02 18:37:40 +01:00
|
|
|
re, err := regexp.Compile(search[1 : len(search)-1])
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return re.ReplaceAllString(s, replace), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
return strings.Replace(s, search, replace, -1), nil
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-04-12 16:17:26 +02:00
|
|
|
func interpolationFuncLength() ast.Function {
|
|
|
|
return ast.Function{
|
|
|
|
ArgTypes: []ast.Type{ast.TypeString},
|
|
|
|
ReturnType: ast.TypeInt,
|
|
|
|
Variadic: false,
|
|
|
|
Callback: func(args []interface{}) (interface{}, error) {
|
|
|
|
if !strings.Contains(args[0].(string), InterpSplitDelim) {
|
|
|
|
return len(args[0].(string)), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
var list []string
|
|
|
|
for _, arg := range args {
|
|
|
|
parts := strings.Split(arg.(string), InterpSplitDelim)
|
|
|
|
for _, part := range parts {
|
|
|
|
list = append(list, part)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return len(list), nil
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-01-28 22:59:16 +01:00
|
|
|
// interpolationFuncSplit implements the "split" function that allows
|
|
|
|
// strings to split into multi-variable values
|
|
|
|
func interpolationFuncSplit() ast.Function {
|
|
|
|
return ast.Function{
|
|
|
|
ArgTypes: []ast.Type{ast.TypeString, ast.TypeString},
|
|
|
|
ReturnType: ast.TypeString,
|
|
|
|
Callback: func(args []interface{}) (interface{}, error) {
|
|
|
|
return strings.Replace(args[1].(string), args[0].(string), InterpSplitDelim, -1), nil
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-07-21 21:56:03 +02:00
|
|
|
// interpolationFuncLookup implements the "lookup" function that allows
|
|
|
|
// dynamic lookups of map types within a Terraform configuration.
|
2015-01-15 07:01:42 +01:00
|
|
|
func interpolationFuncLookup(vs map[string]ast.Variable) ast.Function {
|
|
|
|
return ast.Function{
|
2015-01-13 21:06:04 +01:00
|
|
|
ArgTypes: []ast.Type{ast.TypeString, ast.TypeString},
|
|
|
|
ReturnType: ast.TypeString,
|
|
|
|
Callback: func(args []interface{}) (interface{}, error) {
|
|
|
|
k := fmt.Sprintf("var.%s.%s", args[0].(string), args[1].(string))
|
|
|
|
v, ok := vs[k]
|
|
|
|
if !ok {
|
|
|
|
return "", fmt.Errorf(
|
|
|
|
"lookup in '%s' failed to find '%s'",
|
|
|
|
args[0].(string), args[1].(string))
|
|
|
|
}
|
2015-01-14 19:40:43 +01:00
|
|
|
if v.Type != ast.TypeString {
|
|
|
|
return "", fmt.Errorf(
|
|
|
|
"lookup in '%s' for '%s' has bad type %s",
|
|
|
|
args[0].(string), args[1].(string), v.Type)
|
|
|
|
}
|
2014-07-21 22:12:43 +02:00
|
|
|
|
2015-01-14 19:40:43 +01:00
|
|
|
return v.Value.(string), nil
|
2015-01-13 21:06:04 +01:00
|
|
|
},
|
2014-07-21 22:12:43 +02:00
|
|
|
}
|
2014-07-21 21:56:03 +02:00
|
|
|
}
|
2014-11-07 19:23:02 +01:00
|
|
|
|
|
|
|
// interpolationFuncElement implements the "element" function that allows
|
|
|
|
// a specific index to be looked up in a multi-variable value. Note that this will
|
|
|
|
// wrap if the index is larger than the number of elements in the multi-variable value.
|
2015-01-15 07:01:42 +01:00
|
|
|
func interpolationFuncElement() ast.Function {
|
|
|
|
return ast.Function{
|
2015-01-13 20:50:44 +01:00
|
|
|
ArgTypes: []ast.Type{ast.TypeString, ast.TypeString},
|
|
|
|
ReturnType: ast.TypeString,
|
|
|
|
Callback: func(args []interface{}) (interface{}, error) {
|
|
|
|
list := strings.Split(args[0].(string), InterpSplitDelim)
|
|
|
|
|
|
|
|
index, err := strconv.Atoi(args[1].(string))
|
|
|
|
if err != nil {
|
|
|
|
return "", fmt.Errorf(
|
|
|
|
"invalid number for index, got %s", args[1])
|
|
|
|
}
|
|
|
|
|
|
|
|
v := list[index%len(list)]
|
|
|
|
return v, nil
|
|
|
|
},
|
2014-11-07 19:23:02 +01:00
|
|
|
}
|
|
|
|
}
|