github.com/nicgrayson/terraform@v0.4.3-0.20150415203910-c4de50829380/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 "length": interpolationFuncLength(), 27 28 // Concat is a little useless now since we supported embeddded 29 // interpolations but we keep it around for backwards compat reasons. 30 "concat": interpolationFuncConcat(), 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 for _, v := range args { 47 b.WriteString(v.(string)) 48 } 49 50 return b.String(), nil 51 }, 52 } 53 } 54 55 // interpolationFuncFile implements the "file" function that allows 56 // loading contents from a file. 57 func interpolationFuncFile() ast.Function { 58 return ast.Function{ 59 ArgTypes: []ast.Type{ast.TypeString}, 60 ReturnType: ast.TypeString, 61 Callback: func(args []interface{}) (interface{}, error) { 62 path, err := homedir.Expand(args[0].(string)) 63 if err != nil { 64 return "", err 65 } 66 data, err := ioutil.ReadFile(path) 67 if err != nil { 68 return "", err 69 } 70 71 return string(data), nil 72 }, 73 } 74 } 75 76 // interpolationFuncFormat implements the "replace" function that does 77 // string replacement. 78 func interpolationFuncFormat() ast.Function { 79 return ast.Function{ 80 ArgTypes: []ast.Type{ast.TypeString}, 81 Variadic: true, 82 VariadicType: ast.TypeAny, 83 ReturnType: ast.TypeString, 84 Callback: func(args []interface{}) (interface{}, error) { 85 format := args[0].(string) 86 return fmt.Sprintf(format, args[1:]...), nil 87 }, 88 } 89 } 90 91 // interpolationFuncJoin implements the "join" function that allows 92 // multi-variable values to be joined by some character. 93 func interpolationFuncJoin() ast.Function { 94 return ast.Function{ 95 ArgTypes: []ast.Type{ast.TypeString, ast.TypeString}, 96 ReturnType: ast.TypeString, 97 Callback: func(args []interface{}) (interface{}, error) { 98 var list []string 99 for _, arg := range args[1:] { 100 parts := strings.Split(arg.(string), InterpSplitDelim) 101 list = append(list, parts...) 102 } 103 104 return strings.Join(list, args[0].(string)), nil 105 }, 106 } 107 } 108 109 // interpolationFuncReplace implements the "replace" function that does 110 // string replacement. 111 func interpolationFuncReplace() ast.Function { 112 return ast.Function{ 113 ArgTypes: []ast.Type{ast.TypeString, ast.TypeString, ast.TypeString}, 114 ReturnType: ast.TypeString, 115 Callback: func(args []interface{}) (interface{}, error) { 116 s := args[0].(string) 117 search := args[1].(string) 118 replace := args[2].(string) 119 120 // We search/replace using a regexp if the string is surrounded 121 // in forward slashes. 122 if len(search) > 1 && search[0] == '/' && search[len(search)-1] == '/' { 123 re, err := regexp.Compile(search[1 : len(search)-1]) 124 if err != nil { 125 return nil, err 126 } 127 128 return re.ReplaceAllString(s, replace), nil 129 } 130 131 return strings.Replace(s, search, replace, -1), nil 132 }, 133 } 134 } 135 136 func interpolationFuncLength() ast.Function { 137 return ast.Function{ 138 ArgTypes: []ast.Type{ast.TypeString}, 139 ReturnType: ast.TypeInt, 140 Variadic: false, 141 Callback: func(args []interface{}) (interface{}, error) { 142 if !strings.Contains(args[0].(string), InterpSplitDelim) { 143 return len(args[0].(string)), nil 144 } 145 146 var list []string 147 for _, arg := range args { 148 parts := strings.Split(arg.(string), InterpSplitDelim) 149 for _, part := range parts { 150 list = append(list, part) 151 } 152 } 153 return len(list), nil 154 }, 155 } 156 } 157 158 // interpolationFuncSplit implements the "split" function that allows 159 // strings to split into multi-variable values 160 func interpolationFuncSplit() ast.Function { 161 return ast.Function{ 162 ArgTypes: []ast.Type{ast.TypeString, ast.TypeString}, 163 ReturnType: ast.TypeString, 164 Callback: func(args []interface{}) (interface{}, error) { 165 return strings.Replace(args[1].(string), args[0].(string), InterpSplitDelim, -1), nil 166 }, 167 } 168 } 169 170 // interpolationFuncLookup implements the "lookup" function that allows 171 // dynamic lookups of map types within a Terraform configuration. 172 func interpolationFuncLookup(vs map[string]ast.Variable) ast.Function { 173 return ast.Function{ 174 ArgTypes: []ast.Type{ast.TypeString, ast.TypeString}, 175 ReturnType: ast.TypeString, 176 Callback: func(args []interface{}) (interface{}, error) { 177 k := fmt.Sprintf("var.%s.%s", args[0].(string), args[1].(string)) 178 v, ok := vs[k] 179 if !ok { 180 return "", fmt.Errorf( 181 "lookup in '%s' failed to find '%s'", 182 args[0].(string), args[1].(string)) 183 } 184 if v.Type != ast.TypeString { 185 return "", fmt.Errorf( 186 "lookup in '%s' for '%s' has bad type %s", 187 args[0].(string), args[1].(string), v.Type) 188 } 189 190 return v.Value.(string), nil 191 }, 192 } 193 } 194 195 // interpolationFuncElement implements the "element" function that allows 196 // a specific index to be looked up in a multi-variable value. Note that this will 197 // wrap if the index is larger than the number of elements in the multi-variable value. 198 func interpolationFuncElement() ast.Function { 199 return ast.Function{ 200 ArgTypes: []ast.Type{ast.TypeString, ast.TypeString}, 201 ReturnType: ast.TypeString, 202 Callback: func(args []interface{}) (interface{}, error) { 203 list := strings.Split(args[0].(string), InterpSplitDelim) 204 205 index, err := strconv.Atoi(args[1].(string)) 206 if err != nil { 207 return "", fmt.Errorf( 208 "invalid number for index, got %s", args[1]) 209 } 210 211 v := list[index%len(list)] 212 return v, nil 213 }, 214 } 215 }