github.com/2matz/terraform@v0.6.1-0.20150714181608-a03cbdb5d5bd/config/interpolate_funcs.go (about) 1 package config 2 3 import ( 4 "bytes" 5 "errors" 6 "fmt" 7 "io/ioutil" 8 "regexp" 9 "sort" 10 "strconv" 11 "strings" 12 13 "github.com/hashicorp/terraform/config/lang/ast" 14 "github.com/mitchellh/go-homedir" 15 ) 16 17 // Funcs is the mapping of built-in functions for configuration. 18 var Funcs map[string]ast.Function 19 20 func init() { 21 Funcs = map[string]ast.Function{ 22 "concat": interpolationFuncConcat(), 23 "element": interpolationFuncElement(), 24 "file": interpolationFuncFile(), 25 "format": interpolationFuncFormat(), 26 "formatlist": interpolationFuncFormatList(), 27 "join": interpolationFuncJoin(), 28 "length": interpolationFuncLength(), 29 "replace": interpolationFuncReplace(), 30 "split": interpolationFuncSplit(), 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 var finalList []string 47 48 var isDeprecated = true 49 50 for _, arg := range args { 51 argument := arg.(string) 52 53 if len(argument) == 0 { 54 continue 55 } 56 57 if IsStringList(argument) { 58 isDeprecated = false 59 finalList = append(finalList, StringList(argument).Slice()...) 60 } else { 61 finalList = append(finalList, argument) 62 } 63 64 // Deprecated concat behaviour 65 b.WriteString(argument) 66 } 67 68 if isDeprecated { 69 return b.String(), nil 70 } 71 72 return NewStringList(finalList).String(), nil 73 }, 74 } 75 } 76 77 // interpolationFuncFile implements the "file" function that allows 78 // loading contents from a file. 79 func interpolationFuncFile() ast.Function { 80 return ast.Function{ 81 ArgTypes: []ast.Type{ast.TypeString}, 82 ReturnType: ast.TypeString, 83 Callback: func(args []interface{}) (interface{}, error) { 84 path, err := homedir.Expand(args[0].(string)) 85 if err != nil { 86 return "", err 87 } 88 data, err := ioutil.ReadFile(path) 89 if err != nil { 90 return "", err 91 } 92 93 return string(data), nil 94 }, 95 } 96 } 97 98 // interpolationFuncFormat implements the "format" function that does 99 // string formatting. 100 func interpolationFuncFormat() ast.Function { 101 return ast.Function{ 102 ArgTypes: []ast.Type{ast.TypeString}, 103 Variadic: true, 104 VariadicType: ast.TypeAny, 105 ReturnType: ast.TypeString, 106 Callback: func(args []interface{}) (interface{}, error) { 107 format := args[0].(string) 108 return fmt.Sprintf(format, args[1:]...), nil 109 }, 110 } 111 } 112 113 // interpolationFuncFormatList implements the "formatlist" function that does 114 // string formatting on lists. 115 func interpolationFuncFormatList() ast.Function { 116 return ast.Function{ 117 ArgTypes: []ast.Type{ast.TypeString}, 118 Variadic: true, 119 VariadicType: ast.TypeAny, 120 ReturnType: ast.TypeString, 121 Callback: func(args []interface{}) (interface{}, error) { 122 // Make a copy of the variadic part of args 123 // to avoid modifying the original. 124 varargs := make([]interface{}, len(args)-1) 125 copy(varargs, args[1:]) 126 127 // Convert arguments that are lists into slices. 128 // Confirm along the way that all lists have the same length (n). 129 var n int 130 for i := 1; i < len(args); i++ { 131 s, ok := args[i].(string) 132 if !ok { 133 continue 134 } 135 if !IsStringList(s) { 136 continue 137 } 138 139 parts := StringList(s).Slice() 140 141 // otherwise the list is sent down to be indexed 142 varargs[i-1] = parts 143 144 // Check length 145 if n == 0 { 146 // first list we've seen 147 n = len(parts) 148 continue 149 } 150 if n != len(parts) { 151 return nil, fmt.Errorf("format: mismatched list lengths: %d != %d", n, len(parts)) 152 } 153 } 154 155 if n == 0 { 156 return nil, errors.New("no lists in arguments to formatlist") 157 } 158 159 // Do the formatting. 160 format := args[0].(string) 161 162 // Generate a list of formatted strings. 163 list := make([]string, n) 164 fmtargs := make([]interface{}, len(varargs)) 165 for i := 0; i < n; i++ { 166 for j, arg := range varargs { 167 switch arg := arg.(type) { 168 default: 169 fmtargs[j] = arg 170 case []string: 171 fmtargs[j] = arg[i] 172 } 173 } 174 list[i] = fmt.Sprintf(format, fmtargs...) 175 } 176 return NewStringList(list).String(), nil 177 }, 178 } 179 } 180 181 // interpolationFuncJoin implements the "join" function that allows 182 // multi-variable values to be joined by some character. 183 func interpolationFuncJoin() ast.Function { 184 return ast.Function{ 185 ArgTypes: []ast.Type{ast.TypeString, ast.TypeString}, 186 ReturnType: ast.TypeString, 187 Callback: func(args []interface{}) (interface{}, error) { 188 var list []string 189 for _, arg := range args[1:] { 190 parts := StringList(arg.(string)).Slice() 191 list = append(list, parts...) 192 } 193 194 return strings.Join(list, args[0].(string)), nil 195 }, 196 } 197 } 198 199 // interpolationFuncReplace implements the "replace" function that does 200 // string replacement. 201 func interpolationFuncReplace() ast.Function { 202 return ast.Function{ 203 ArgTypes: []ast.Type{ast.TypeString, ast.TypeString, ast.TypeString}, 204 ReturnType: ast.TypeString, 205 Callback: func(args []interface{}) (interface{}, error) { 206 s := args[0].(string) 207 search := args[1].(string) 208 replace := args[2].(string) 209 210 // We search/replace using a regexp if the string is surrounded 211 // in forward slashes. 212 if len(search) > 1 && search[0] == '/' && search[len(search)-1] == '/' { 213 re, err := regexp.Compile(search[1 : len(search)-1]) 214 if err != nil { 215 return nil, err 216 } 217 218 return re.ReplaceAllString(s, replace), nil 219 } 220 221 return strings.Replace(s, search, replace, -1), nil 222 }, 223 } 224 } 225 226 func interpolationFuncLength() ast.Function { 227 return ast.Function{ 228 ArgTypes: []ast.Type{ast.TypeString}, 229 ReturnType: ast.TypeInt, 230 Variadic: false, 231 Callback: func(args []interface{}) (interface{}, error) { 232 if !IsStringList(args[0].(string)) { 233 return len(args[0].(string)), nil 234 } 235 236 length := 0 237 for _, arg := range args { 238 length += StringList(arg.(string)).Length() 239 } 240 return length, nil 241 }, 242 } 243 } 244 245 // interpolationFuncSplit implements the "split" function that allows 246 // strings to split into multi-variable values 247 func interpolationFuncSplit() ast.Function { 248 return ast.Function{ 249 ArgTypes: []ast.Type{ast.TypeString, ast.TypeString}, 250 ReturnType: ast.TypeString, 251 Callback: func(args []interface{}) (interface{}, error) { 252 sep := args[0].(string) 253 s := args[1].(string) 254 return NewStringList(strings.Split(s, sep)).String(), nil 255 }, 256 } 257 } 258 259 // interpolationFuncLookup implements the "lookup" function that allows 260 // dynamic lookups of map types within a Terraform configuration. 261 func interpolationFuncLookup(vs map[string]ast.Variable) ast.Function { 262 return ast.Function{ 263 ArgTypes: []ast.Type{ast.TypeString, ast.TypeString}, 264 ReturnType: ast.TypeString, 265 Callback: func(args []interface{}) (interface{}, error) { 266 k := fmt.Sprintf("var.%s.%s", args[0].(string), args[1].(string)) 267 v, ok := vs[k] 268 if !ok { 269 return "", fmt.Errorf( 270 "lookup in '%s' failed to find '%s'", 271 args[0].(string), args[1].(string)) 272 } 273 if v.Type != ast.TypeString { 274 return "", fmt.Errorf( 275 "lookup in '%s' for '%s' has bad type %s", 276 args[0].(string), args[1].(string), v.Type) 277 } 278 279 return v.Value.(string), nil 280 }, 281 } 282 } 283 284 // interpolationFuncElement implements the "element" function that allows 285 // a specific index to be looked up in a multi-variable value. Note that this will 286 // wrap if the index is larger than the number of elements in the multi-variable value. 287 func interpolationFuncElement() ast.Function { 288 return ast.Function{ 289 ArgTypes: []ast.Type{ast.TypeString, ast.TypeString}, 290 ReturnType: ast.TypeString, 291 Callback: func(args []interface{}) (interface{}, error) { 292 list := StringList(args[0].(string)) 293 294 index, err := strconv.Atoi(args[1].(string)) 295 if err != nil { 296 return "", fmt.Errorf( 297 "invalid number for index, got %s", args[1]) 298 } 299 300 v := list.Element(index) 301 return v, nil 302 }, 303 } 304 } 305 306 // interpolationFuncKeys implements the "keys" function that yields a list of 307 // keys of map types within a Terraform configuration. 308 func interpolationFuncKeys(vs map[string]ast.Variable) ast.Function { 309 return ast.Function{ 310 ArgTypes: []ast.Type{ast.TypeString}, 311 ReturnType: ast.TypeString, 312 Callback: func(args []interface{}) (interface{}, error) { 313 // Prefix must include ending dot to be a map 314 prefix := fmt.Sprintf("var.%s.", args[0].(string)) 315 keys := make([]string, 0, len(vs)) 316 for k, _ := range vs { 317 if !strings.HasPrefix(k, prefix) { 318 continue 319 } 320 keys = append(keys, k[len(prefix):]) 321 } 322 323 if len(keys) <= 0 { 324 return "", fmt.Errorf( 325 "failed to find map '%s'", 326 args[0].(string)) 327 } 328 329 sort.Strings(keys) 330 331 return NewStringList(keys).String(), nil 332 }, 333 } 334 } 335 336 // interpolationFuncValues implements the "values" function that yields a list of 337 // keys of map types within a Terraform configuration. 338 func interpolationFuncValues(vs map[string]ast.Variable) ast.Function { 339 return ast.Function{ 340 ArgTypes: []ast.Type{ast.TypeString}, 341 ReturnType: ast.TypeString, 342 Callback: func(args []interface{}) (interface{}, error) { 343 // Prefix must include ending dot to be a map 344 prefix := fmt.Sprintf("var.%s.", args[0].(string)) 345 keys := make([]string, 0, len(vs)) 346 for k, _ := range vs { 347 if !strings.HasPrefix(k, prefix) { 348 continue 349 } 350 keys = append(keys, k) 351 } 352 353 if len(keys) <= 0 { 354 return "", fmt.Errorf( 355 "failed to find map '%s'", 356 args[0].(string)) 357 } 358 359 sort.Strings(keys) 360 361 vals := make([]string, 0, len(keys)) 362 363 for _, k := range keys { 364 v := vs[k] 365 if v.Type != ast.TypeString { 366 return "", fmt.Errorf("values(): %q has bad type %s", k, v.Type) 367 } 368 vals = append(vals, vs[k].Value.(string)) 369 } 370 371 return NewStringList(vals).String(), nil 372 }, 373 } 374 }