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