github.com/jrasell/terraform@v0.6.17-0.20160523115548-2652f5232949/config/interpolate_walk.go (about) 1 package config 2 3 import ( 4 "fmt" 5 "reflect" 6 "strings" 7 8 "github.com/hashicorp/hil" 9 "github.com/hashicorp/hil/ast" 10 "github.com/mitchellh/reflectwalk" 11 ) 12 13 // interpolationWalker implements interfaces for the reflectwalk package 14 // (github.com/mitchellh/reflectwalk) that can be used to automatically 15 // execute a callback for an interpolation. 16 type interpolationWalker struct { 17 // F is the function to call for every interpolation. It can be nil. 18 // 19 // If Replace is true, then the return value of F will be used to 20 // replace the interpolation. 21 F interpolationWalkerFunc 22 Replace bool 23 24 // ContextF is an advanced version of F that also receives the 25 // location of where it is in the structure. This lets you do 26 // context-aware validation. 27 ContextF interpolationWalkerContextFunc 28 29 key []string 30 lastValue reflect.Value 31 loc reflectwalk.Location 32 cs []reflect.Value 33 csKey []reflect.Value 34 csData interface{} 35 sliceIndex int 36 unknownKeys []string 37 } 38 39 // interpolationWalkerFunc is the callback called by interpolationWalk. 40 // It is called with any interpolation found. It should return a value 41 // to replace the interpolation with, along with any errors. 42 // 43 // If Replace is set to false in interpolationWalker, then the replace 44 // value can be anything as it will have no effect. 45 type interpolationWalkerFunc func(ast.Node) (interface{}, error) 46 47 // interpolationWalkerContextFunc is called by interpolationWalk if 48 // ContextF is set. This receives both the interpolation and the location 49 // where the interpolation is. 50 // 51 // This callback can be used to validate the location of the interpolation 52 // within the configuration. 53 type interpolationWalkerContextFunc func(reflectwalk.Location, ast.Node) 54 55 func (w *interpolationWalker) Enter(loc reflectwalk.Location) error { 56 w.loc = loc 57 return nil 58 } 59 60 func (w *interpolationWalker) Exit(loc reflectwalk.Location) error { 61 w.loc = reflectwalk.None 62 63 switch loc { 64 case reflectwalk.Map: 65 w.cs = w.cs[:len(w.cs)-1] 66 case reflectwalk.MapValue: 67 w.key = w.key[:len(w.key)-1] 68 w.csKey = w.csKey[:len(w.csKey)-1] 69 case reflectwalk.Slice: 70 // Split any values that need to be split 71 w.splitSlice() 72 w.cs = w.cs[:len(w.cs)-1] 73 case reflectwalk.SliceElem: 74 w.csKey = w.csKey[:len(w.csKey)-1] 75 } 76 77 return nil 78 } 79 80 func (w *interpolationWalker) Map(m reflect.Value) error { 81 w.cs = append(w.cs, m) 82 return nil 83 } 84 85 func (w *interpolationWalker) MapElem(m, k, v reflect.Value) error { 86 w.csData = k 87 w.csKey = append(w.csKey, k) 88 w.key = append(w.key, k.String()) 89 w.lastValue = v 90 return nil 91 } 92 93 func (w *interpolationWalker) Slice(s reflect.Value) error { 94 w.cs = append(w.cs, s) 95 return nil 96 } 97 98 func (w *interpolationWalker) SliceElem(i int, elem reflect.Value) error { 99 w.csKey = append(w.csKey, reflect.ValueOf(i)) 100 w.sliceIndex = i 101 return nil 102 } 103 104 func (w *interpolationWalker) Primitive(v reflect.Value) error { 105 setV := v 106 107 // We only care about strings 108 if v.Kind() == reflect.Interface { 109 setV = v 110 v = v.Elem() 111 } 112 if v.Kind() != reflect.String { 113 return nil 114 } 115 116 astRoot, err := hil.Parse(v.String()) 117 if err != nil { 118 return err 119 } 120 121 // If the AST we got is just a literal string value with the same 122 // value then we ignore it. We have to check if its the same value 123 // because it is possible to input a string, get out a string, and 124 // have it be different. For example: "foo-$${bar}" turns into 125 // "foo-${bar}" 126 if n, ok := astRoot.(*ast.LiteralNode); ok { 127 if s, ok := n.Value.(string); ok && s == v.String() { 128 return nil 129 } 130 } 131 132 if w.ContextF != nil { 133 w.ContextF(w.loc, astRoot) 134 } 135 136 if w.F == nil { 137 return nil 138 } 139 140 replaceVal, err := w.F(astRoot) 141 if err != nil { 142 return fmt.Errorf( 143 "%s in:\n\n%s", 144 err, v.String()) 145 } 146 147 if w.Replace { 148 // We need to determine if we need to remove this element 149 // if the result contains any "UnknownVariableValue" which is 150 // set if it is computed. This behavior is different if we're 151 // splitting (in a SliceElem) or not. 152 remove := false 153 if w.loc == reflectwalk.SliceElem { 154 switch typedReplaceVal := replaceVal.(type) { 155 case string: 156 if typedReplaceVal == UnknownVariableValue { 157 remove = true 158 } 159 case []interface{}: 160 if hasUnknownValue(typedReplaceVal) { 161 remove = true 162 } 163 } 164 } else if replaceVal == UnknownVariableValue { 165 remove = true 166 } 167 if remove { 168 w.removeCurrent() 169 return nil 170 } 171 172 resultVal := reflect.ValueOf(replaceVal) 173 switch w.loc { 174 case reflectwalk.MapKey: 175 m := w.cs[len(w.cs)-1] 176 177 // Delete the old value 178 var zero reflect.Value 179 m.SetMapIndex(w.csData.(reflect.Value), zero) 180 181 // Set the new key with the existing value 182 m.SetMapIndex(resultVal, w.lastValue) 183 184 // Set the key to be the new key 185 w.csData = resultVal 186 case reflectwalk.MapValue: 187 // If we're in a map, then the only way to set a map value is 188 // to set it directly. 189 m := w.cs[len(w.cs)-1] 190 mk := w.csData.(reflect.Value) 191 m.SetMapIndex(mk, resultVal) 192 default: 193 // Otherwise, we should be addressable 194 setV.Set(resultVal) 195 } 196 } 197 198 return nil 199 } 200 201 func (w *interpolationWalker) removeCurrent() { 202 // Append the key to the unknown keys 203 w.unknownKeys = append(w.unknownKeys, strings.Join(w.key, ".")) 204 205 for i := 1; i <= len(w.cs); i++ { 206 c := w.cs[len(w.cs)-i] 207 switch c.Kind() { 208 case reflect.Map: 209 // Zero value so that we delete the map key 210 var val reflect.Value 211 212 // Get the key and delete it 213 k := w.csData.(reflect.Value) 214 c.SetMapIndex(k, val) 215 return 216 } 217 } 218 219 panic("No container found for removeCurrent") 220 } 221 222 func (w *interpolationWalker) replaceCurrent(v reflect.Value) { 223 c := w.cs[len(w.cs)-2] 224 switch c.Kind() { 225 case reflect.Map: 226 // Get the key and delete it 227 k := w.csKey[len(w.csKey)-1] 228 c.SetMapIndex(k, v) 229 } 230 } 231 232 func hasUnknownValue(variable []interface{}) bool { 233 for _, value := range variable { 234 if strVal, ok := value.(string); ok { 235 if strVal == UnknownVariableValue { 236 return true 237 } 238 } 239 } 240 return false 241 } 242 243 func (w *interpolationWalker) splitSlice() { 244 raw := w.cs[len(w.cs)-1] 245 246 var s []interface{} 247 switch v := raw.Interface().(type) { 248 case []interface{}: 249 s = v 250 case []map[string]interface{}: 251 return 252 } 253 254 split := false 255 for _, val := range s { 256 if varVal, ok := val.(ast.Variable); ok && varVal.Type == ast.TypeList { 257 split = true 258 } 259 if _, ok := val.([]interface{}); ok { 260 split = true 261 } 262 } 263 264 if !split { 265 return 266 } 267 268 result := make([]interface{}, 0) 269 for _, v := range s { 270 switch val := v.(type) { 271 case ast.Variable: 272 switch val.Type { 273 case ast.TypeList: 274 elements := val.Value.([]ast.Variable) 275 for _, element := range elements { 276 result = append(result, element.Value) 277 } 278 default: 279 result = append(result, val.Value) 280 } 281 case []interface{}: 282 for _, element := range val { 283 result = append(result, element) 284 } 285 default: 286 result = append(result, v) 287 } 288 } 289 290 w.replaceCurrent(reflect.ValueOf(result)) 291 }