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 }