github.com/adamar/terraform@v0.2.2-0.20141016210445-2e703afdad0e/config/interpolate_walk.go (about) 1 package config 2 3 import ( 4 "fmt" 5 "reflect" 6 "regexp" 7 "strings" 8 9 "github.com/mitchellh/reflectwalk" 10 ) 11 12 // InterpSplitDelim is the delimeter that is looked for to split when 13 // it is returned. This is a comma right now but should eventually become 14 // a value that a user is very unlikely to use (such as UUID). 15 const InterpSplitDelim = `B780FFEC-B661-4EB8-9236-A01737AD98B6` 16 17 // interpRegexp is a regexp that matches interpolations such as ${foo.bar} 18 var interpRegexp *regexp.Regexp = regexp.MustCompile( 19 `(?i)(\$+)\{([\s*-.,\\/\(\)a-z0-9_"]+)\}`) 20 21 // interpolationWalker implements interfaces for the reflectwalk package 22 // (github.com/mitchellh/reflectwalk) that can be used to automatically 23 // execute a callback for an interpolation. 24 type interpolationWalker struct { 25 // F is the function to call for every interpolation. It can be nil. 26 // 27 // If Replace is true, then the return value of F will be used to 28 // replace the interpolation. 29 F interpolationWalkerFunc 30 Replace bool 31 32 // ContextF is an advanced version of F that also receives the 33 // location of where it is in the structure. This lets you do 34 // context-aware validation. 35 ContextF interpolationWalkerContextFunc 36 37 key []string 38 lastValue reflect.Value 39 loc reflectwalk.Location 40 cs []reflect.Value 41 csKey []reflect.Value 42 csData interface{} 43 sliceIndex int 44 unknownKeys []string 45 } 46 47 // interpolationWalkerFunc is the callback called by interpolationWalk. 48 // It is called with any interpolation found. It should return a value 49 // to replace the interpolation with, along with any errors. 50 // 51 // If Replace is set to false in interpolationWalker, then the replace 52 // value can be anything as it will have no effect. 53 type interpolationWalkerFunc func(Interpolation) (string, error) 54 55 // interpolationWalkerContextFunc is called by interpolationWalk if 56 // ContextF is set. This receives both the interpolation and the location 57 // where the interpolation is. 58 // 59 // This callback can be used to validate the location of the interpolation 60 // within the configuration. 61 type interpolationWalkerContextFunc func(reflectwalk.Location, Interpolation) 62 63 func (w *interpolationWalker) Enter(loc reflectwalk.Location) error { 64 w.loc = loc 65 return nil 66 } 67 68 func (w *interpolationWalker) Exit(loc reflectwalk.Location) error { 69 w.loc = reflectwalk.None 70 71 switch loc { 72 case reflectwalk.Map: 73 w.cs = w.cs[:len(w.cs)-1] 74 case reflectwalk.MapValue: 75 w.key = w.key[:len(w.key)-1] 76 w.csKey = w.csKey[:len(w.csKey)-1] 77 case reflectwalk.Slice: 78 // Split any values that need to be split 79 w.splitSlice() 80 w.cs = w.cs[:len(w.cs)-1] 81 case reflectwalk.SliceElem: 82 w.csKey = w.csKey[:len(w.csKey)-1] 83 } 84 85 return nil 86 } 87 88 func (w *interpolationWalker) Map(m reflect.Value) error { 89 w.cs = append(w.cs, m) 90 return nil 91 } 92 93 func (w *interpolationWalker) MapElem(m, k, v reflect.Value) error { 94 w.csData = k 95 w.csKey = append(w.csKey, k) 96 w.key = append(w.key, k.String()) 97 w.lastValue = v 98 return nil 99 } 100 101 func (w *interpolationWalker) Slice(s reflect.Value) error { 102 w.cs = append(w.cs, s) 103 return nil 104 } 105 106 func (w *interpolationWalker) SliceElem(i int, elem reflect.Value) error { 107 w.csKey = append(w.csKey, reflect.ValueOf(i)) 108 w.sliceIndex = i 109 return nil 110 } 111 112 func (w *interpolationWalker) Primitive(v reflect.Value) error { 113 setV := v 114 115 // We only care about strings 116 if v.Kind() == reflect.Interface { 117 setV = v 118 v = v.Elem() 119 } 120 if v.Kind() != reflect.String { 121 return nil 122 } 123 124 // XXX: This can be a lot more efficient if we used a real 125 // parser. A regexp is a hammer though that will get this working. 126 127 matches := interpRegexp.FindAllStringSubmatch(v.String(), -1) 128 if len(matches) == 0 { 129 return nil 130 } 131 132 result := v.String() 133 for _, match := range matches { 134 dollars := len(match[1]) 135 136 // If there are even amounts of dollar signs, then it is escaped 137 if dollars%2 == 0 { 138 continue 139 } 140 141 // Interpolation found, instantiate it 142 key := match[2] 143 144 i, err := ExprParse(key) 145 if err != nil { 146 return err 147 } 148 149 if w.ContextF != nil { 150 w.ContextF(w.loc, i) 151 } 152 153 if w.F == nil { 154 continue 155 } 156 157 replaceVal, err := w.F(i) 158 if err != nil { 159 return fmt.Errorf( 160 "%s: %s", 161 key, 162 err) 163 } 164 165 if w.Replace { 166 // We need to determine if we need to remove this element 167 // if the result contains any "UnknownVariableValue" which is 168 // set if it is computed. This behavior is different if we're 169 // splitting (in a SliceElem) or not. 170 remove := false 171 if w.loc == reflectwalk.SliceElem { 172 parts := strings.Split(replaceVal, InterpSplitDelim) 173 for _, p := range parts { 174 if p == UnknownVariableValue { 175 remove = true 176 break 177 } 178 } 179 } else if replaceVal == UnknownVariableValue { 180 remove = true 181 } 182 if remove { 183 w.removeCurrent() 184 return nil 185 } 186 187 // Replace in our interpolation and continue on. 188 result = strings.Replace(result, match[0], replaceVal, -1) 189 } 190 } 191 192 if w.Replace { 193 resultVal := reflect.ValueOf(result) 194 switch w.loc { 195 case reflectwalk.MapKey: 196 m := w.cs[len(w.cs)-1] 197 198 // Delete the old value 199 var zero reflect.Value 200 m.SetMapIndex(w.csData.(reflect.Value), zero) 201 202 // Set the new key with the existing value 203 m.SetMapIndex(resultVal, w.lastValue) 204 205 // Set the key to be the new key 206 w.csData = resultVal 207 case reflectwalk.MapValue: 208 // If we're in a map, then the only way to set a map value is 209 // to set it directly. 210 m := w.cs[len(w.cs)-1] 211 mk := w.csData.(reflect.Value) 212 m.SetMapIndex(mk, resultVal) 213 default: 214 // Otherwise, we should be addressable 215 setV.Set(resultVal) 216 } 217 } 218 219 return nil 220 } 221 222 func (w *interpolationWalker) removeCurrent() { 223 // Append the key to the unknown keys 224 w.unknownKeys = append(w.unknownKeys, strings.Join(w.key, ".")) 225 226 for i := 1; i <= len(w.cs); i++ { 227 c := w.cs[len(w.cs)-i] 228 switch c.Kind() { 229 case reflect.Map: 230 // Zero value so that we delete the map key 231 var val reflect.Value 232 233 // Get the key and delete it 234 k := w.csData.(reflect.Value) 235 c.SetMapIndex(k, val) 236 return 237 } 238 } 239 240 panic("No container found for removeCurrent") 241 } 242 243 func (w *interpolationWalker) replaceCurrent(v reflect.Value) { 244 c := w.cs[len(w.cs)-2] 245 switch c.Kind() { 246 case reflect.Map: 247 // Get the key and delete it 248 k := w.csKey[len(w.csKey)-1] 249 c.SetMapIndex(k, v) 250 } 251 } 252 253 func (w *interpolationWalker) splitSlice() { 254 // Get the []interface{} slice so we can do some operations on 255 // it without dealing with reflection. We'll document each step 256 // here to be clear. 257 var s []interface{} 258 raw := w.cs[len(w.cs)-1] 259 switch v := raw.Interface().(type) { 260 case []interface{}: 261 s = v 262 case []map[string]interface{}: 263 return 264 default: 265 panic("Unknown kind: " + raw.Kind().String()) 266 } 267 268 // Check if we have any elements that we need to split. If not, then 269 // just return since we're done. 270 split := false 271 for _, v := range s { 272 sv, ok := v.(string) 273 if !ok { 274 continue 275 } 276 if idx := strings.Index(sv, InterpSplitDelim); idx >= 0 { 277 split = true 278 break 279 } 280 } 281 if !split { 282 return 283 } 284 285 // Make a new result slice that is twice the capacity to fit our growth. 286 result := make([]interface{}, 0, len(s)*2) 287 288 // Go over each element of the original slice and start building up 289 // the resulting slice by splitting where we have to. 290 for _, v := range s { 291 sv, ok := v.(string) 292 if !ok { 293 // Not a string, so just set it 294 result = append(result, v) 295 continue 296 } 297 298 // Split on the delimiter 299 for _, p := range strings.Split(sv, InterpSplitDelim) { 300 result = append(result, p) 301 } 302 } 303 304 // Our slice is now done, we have to replace the slice now 305 // with this new one that we have. 306 w.replaceCurrent(reflect.ValueOf(result)) 307 }