github.com/armen/terraform@v0.5.2-0.20150529052519-caa8117a08f1/config/interpolate_funcs.go (about) 1 package config 2 3 import ( 4 "bytes" 5 "errors" 6 "fmt" 7 "io/ioutil" 8 "regexp" 9 "strconv" 10 "strings" 11 12 "github.com/hashicorp/terraform/config/lang/ast" 13 "github.com/mitchellh/go-homedir" 14 ) 15 16 // Funcs is the mapping of built-in functions for configuration. 17 var Funcs map[string]ast.Function 18 19 func init() { 20 Funcs = map[string]ast.Function{ 21 "file": interpolationFuncFile(), 22 "format": interpolationFuncFormat(), 23 "formatlist": interpolationFuncFormatList(), 24 "join": interpolationFuncJoin(), 25 "element": interpolationFuncElement(), 26 "replace": interpolationFuncReplace(), 27 "split": interpolationFuncSplit(), 28 "length": interpolationFuncLength(), 29 30 // Concat is a little useless now since we supported embeddded 31 // interpolations but we keep it around for backwards compat reasons. 32 "concat": interpolationFuncConcat(), 33 } 34 } 35 36 // interpolationFuncConcat implements the "concat" function that 37 // concatenates multiple strings. This isn't actually necessary anymore 38 // since our language supports string concat natively, but for backwards 39 // compat we do this. 40 func interpolationFuncConcat() ast.Function { 41 return ast.Function{ 42 ArgTypes: []ast.Type{ast.TypeString}, 43 ReturnType: ast.TypeString, 44 Variadic: true, 45 VariadicType: ast.TypeString, 46 Callback: func(args []interface{}) (interface{}, error) { 47 var b bytes.Buffer 48 for _, v := range args { 49 b.WriteString(v.(string)) 50 } 51 52 return b.String(), nil 53 }, 54 } 55 } 56 57 // interpolationFuncFile implements the "file" function that allows 58 // loading contents from a file. 59 func interpolationFuncFile() ast.Function { 60 return ast.Function{ 61 ArgTypes: []ast.Type{ast.TypeString}, 62 ReturnType: ast.TypeString, 63 Callback: func(args []interface{}) (interface{}, error) { 64 path, err := homedir.Expand(args[0].(string)) 65 if err != nil { 66 return "", err 67 } 68 data, err := ioutil.ReadFile(path) 69 if err != nil { 70 return "", err 71 } 72 73 return string(data), nil 74 }, 75 } 76 } 77 78 // interpolationFuncFormat implements the "format" function that does 79 // string formatting. 80 func interpolationFuncFormat() ast.Function { 81 return ast.Function{ 82 ArgTypes: []ast.Type{ast.TypeString}, 83 Variadic: true, 84 VariadicType: ast.TypeAny, 85 ReturnType: ast.TypeString, 86 Callback: func(args []interface{}) (interface{}, error) { 87 format := args[0].(string) 88 return fmt.Sprintf(format, args[1:]...), nil 89 }, 90 } 91 } 92 93 // interpolationFuncFormatList implements the "formatlist" function that does 94 // string formatting on lists. 95 func interpolationFuncFormatList() ast.Function { 96 return ast.Function{ 97 ArgTypes: []ast.Type{ast.TypeString}, 98 Variadic: true, 99 VariadicType: ast.TypeAny, 100 ReturnType: ast.TypeString, 101 Callback: func(args []interface{}) (interface{}, error) { 102 // Make a copy of the variadic part of args 103 // to avoid modifying the original. 104 varargs := make([]interface{}, len(args)-1) 105 copy(varargs, args[1:]) 106 107 // Convert arguments that are lists into slices. 108 // Confirm along the way that all lists have the same length (n). 109 var n int 110 for i := 1; i < len(args); i++ { 111 s, ok := args[i].(string) 112 if !ok { 113 continue 114 } 115 parts := strings.Split(s, InterpSplitDelim) 116 if len(parts) == 1 { 117 continue 118 } 119 varargs[i-1] = parts 120 if n == 0 { 121 // first list we've seen 122 n = len(parts) 123 continue 124 } 125 if n != len(parts) { 126 return nil, fmt.Errorf("format: mismatched list lengths: %d != %d", n, len(parts)) 127 } 128 } 129 130 if n == 0 { 131 return nil, errors.New("no lists in arguments to formatlist") 132 } 133 134 // Do the formatting. 135 format := args[0].(string) 136 137 // Generate a list of formatted strings. 138 list := make([]string, n) 139 fmtargs := make([]interface{}, len(varargs)) 140 for i := 0; i < n; i++ { 141 for j, arg := range varargs { 142 switch arg := arg.(type) { 143 default: 144 fmtargs[j] = arg 145 case []string: 146 fmtargs[j] = arg[i] 147 } 148 } 149 list[i] = fmt.Sprintf(format, fmtargs...) 150 } 151 return strings.Join(list, InterpSplitDelim), nil 152 }, 153 } 154 } 155 156 // interpolationFuncJoin implements the "join" function that allows 157 // multi-variable values to be joined by some character. 158 func interpolationFuncJoin() ast.Function { 159 return ast.Function{ 160 ArgTypes: []ast.Type{ast.TypeString, ast.TypeString}, 161 ReturnType: ast.TypeString, 162 Callback: func(args []interface{}) (interface{}, error) { 163 var list []string 164 for _, arg := range args[1:] { 165 parts := strings.Split(arg.(string), InterpSplitDelim) 166 list = append(list, parts...) 167 } 168 169 return strings.Join(list, args[0].(string)), nil 170 }, 171 } 172 } 173 174 // interpolationFuncReplace implements the "replace" function that does 175 // string replacement. 176 func interpolationFuncReplace() ast.Function { 177 return ast.Function{ 178 ArgTypes: []ast.Type{ast.TypeString, ast.TypeString, ast.TypeString}, 179 ReturnType: ast.TypeString, 180 Callback: func(args []interface{}) (interface{}, error) { 181 s := args[0].(string) 182 search := args[1].(string) 183 replace := args[2].(string) 184 185 // We search/replace using a regexp if the string is surrounded 186 // in forward slashes. 187 if len(search) > 1 && search[0] == '/' && search[len(search)-1] == '/' { 188 re, err := regexp.Compile(search[1 : len(search)-1]) 189 if err != nil { 190 return nil, err 191 } 192 193 return re.ReplaceAllString(s, replace), nil 194 } 195 196 return strings.Replace(s, search, replace, -1), nil 197 }, 198 } 199 } 200 201 func interpolationFuncLength() ast.Function { 202 return ast.Function{ 203 ArgTypes: []ast.Type{ast.TypeString}, 204 ReturnType: ast.TypeInt, 205 Variadic: false, 206 Callback: func(args []interface{}) (interface{}, error) { 207 if !strings.Contains(args[0].(string), InterpSplitDelim) { 208 return len(args[0].(string)), nil 209 } 210 211 var list []string 212 for _, arg := range args { 213 parts := strings.Split(arg.(string), InterpSplitDelim) 214 for _, part := range parts { 215 list = append(list, part) 216 } 217 } 218 return len(list), nil 219 }, 220 } 221 } 222 223 // interpolationFuncSplit implements the "split" function that allows 224 // strings to split into multi-variable values 225 func interpolationFuncSplit() ast.Function { 226 return ast.Function{ 227 ArgTypes: []ast.Type{ast.TypeString, ast.TypeString}, 228 ReturnType: ast.TypeString, 229 Callback: func(args []interface{}) (interface{}, error) { 230 return strings.Replace(args[1].(string), args[0].(string), InterpSplitDelim, -1), nil 231 }, 232 } 233 } 234 235 // interpolationFuncLookup implements the "lookup" function that allows 236 // dynamic lookups of map types within a Terraform configuration. 237 func interpolationFuncLookup(vs map[string]ast.Variable) ast.Function { 238 return ast.Function{ 239 ArgTypes: []ast.Type{ast.TypeString, ast.TypeString}, 240 ReturnType: ast.TypeString, 241 Callback: func(args []interface{}) (interface{}, error) { 242 k := fmt.Sprintf("var.%s.%s", args[0].(string), args[1].(string)) 243 v, ok := vs[k] 244 if !ok { 245 return "", fmt.Errorf( 246 "lookup in '%s' failed to find '%s'", 247 args[0].(string), args[1].(string)) 248 } 249 if v.Type != ast.TypeString { 250 return "", fmt.Errorf( 251 "lookup in '%s' for '%s' has bad type %s", 252 args[0].(string), args[1].(string), v.Type) 253 } 254 255 return v.Value.(string), nil 256 }, 257 } 258 } 259 260 // interpolationFuncElement implements the "element" function that allows 261 // a specific index to be looked up in a multi-variable value. Note that this will 262 // wrap if the index is larger than the number of elements in the multi-variable value. 263 func interpolationFuncElement() ast.Function { 264 return ast.Function{ 265 ArgTypes: []ast.Type{ast.TypeString, ast.TypeString}, 266 ReturnType: ast.TypeString, 267 Callback: func(args []interface{}) (interface{}, error) { 268 list := strings.Split(args[0].(string), InterpSplitDelim) 269 270 index, err := strconv.Atoi(args[1].(string)) 271 if err != nil { 272 return "", fmt.Errorf( 273 "invalid number for index, got %s", args[1]) 274 } 275 276 v := list[index%len(list)] 277 return v, nil 278 }, 279 } 280 }