github.com/ojiry/terraform@v0.8.2-0.20161218223921-e50cec712c4a/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 w.sliceIndex = w.sliceIndex[:len(w.sliceIndex)-1] 76 } 77 78 return nil 79 } 80 81 func (w *interpolationWalker) Map(m reflect.Value) error { 82 w.cs = append(w.cs, m) 83 return nil 84 } 85 86 func (w *interpolationWalker) MapElem(m, k, v reflect.Value) error { 87 w.csData = k 88 w.csKey = append(w.csKey, k) 89 90 if l := len(w.sliceIndex); l > 0 { 91 w.key = append(w.key, fmt.Sprintf("%d.%s", w.sliceIndex[l-1], k.String())) 92 } else { 93 w.key = append(w.key, k.String()) 94 } 95 96 w.lastValue = v 97 return nil 98 } 99 100 func (w *interpolationWalker) Slice(s reflect.Value) error { 101 w.cs = append(w.cs, s) 102 return nil 103 } 104 105 func (w *interpolationWalker) SliceElem(i int, elem reflect.Value) error { 106 w.csKey = append(w.csKey, reflect.ValueOf(i)) 107 w.sliceIndex = append(w.sliceIndex, i) 108 return nil 109 } 110 111 func (w *interpolationWalker) Primitive(v reflect.Value) error { 112 setV := v 113 114 // We only care about strings 115 if v.Kind() == reflect.Interface { 116 setV = v 117 v = v.Elem() 118 } 119 if v.Kind() != reflect.String { 120 return nil 121 } 122 123 astRoot, err := hil.Parse(v.String()) 124 if err != nil { 125 return err 126 } 127 128 // If the AST we got is just a literal string value with the same 129 // value then we ignore it. We have to check if its the same value 130 // because it is possible to input a string, get out a string, and 131 // have it be different. For example: "foo-$${bar}" turns into 132 // "foo-${bar}" 133 if n, ok := astRoot.(*ast.LiteralNode); ok { 134 if s, ok := n.Value.(string); ok && s == v.String() { 135 return nil 136 } 137 } 138 139 if w.ContextF != nil { 140 w.ContextF(w.loc, astRoot) 141 } 142 143 if w.F == nil { 144 return nil 145 } 146 147 replaceVal, err := w.F(astRoot) 148 if err != nil { 149 return fmt.Errorf( 150 "%s in:\n\n%s", 151 err, v.String()) 152 } 153 154 if w.Replace { 155 // We need to determine if we need to remove this element 156 // if the result contains any "UnknownVariableValue" which is 157 // set if it is computed. This behavior is different if we're 158 // splitting (in a SliceElem) or not. 159 remove := false 160 if w.loc == reflectwalk.SliceElem { 161 switch typedReplaceVal := replaceVal.(type) { 162 case string: 163 if typedReplaceVal == UnknownVariableValue { 164 remove = true 165 } 166 case []interface{}: 167 if hasUnknownValue(typedReplaceVal) { 168 remove = true 169 } 170 } 171 } else if replaceVal == UnknownVariableValue { 172 remove = true 173 } 174 175 if remove { 176 w.unknownKeys = append(w.unknownKeys, strings.Join(w.key, ".")) 177 } 178 179 resultVal := reflect.ValueOf(replaceVal) 180 switch w.loc { 181 case reflectwalk.MapKey: 182 m := w.cs[len(w.cs)-1] 183 184 // Delete the old value 185 var zero reflect.Value 186 m.SetMapIndex(w.csData.(reflect.Value), zero) 187 188 // Set the new key with the existing value 189 m.SetMapIndex(resultVal, w.lastValue) 190 191 // Set the key to be the new key 192 w.csData = resultVal 193 case reflectwalk.MapValue: 194 // If we're in a map, then the only way to set a map value is 195 // to set it directly. 196 m := w.cs[len(w.cs)-1] 197 mk := w.csData.(reflect.Value) 198 m.SetMapIndex(mk, resultVal) 199 default: 200 // Otherwise, we should be addressable 201 setV.Set(resultVal) 202 } 203 } 204 205 return nil 206 } 207 208 func (w *interpolationWalker) replaceCurrent(v reflect.Value) { 209 c := w.cs[len(w.cs)-2] 210 switch c.Kind() { 211 case reflect.Map: 212 // Get the key and delete it 213 k := w.csKey[len(w.csKey)-1] 214 c.SetMapIndex(k, v) 215 } 216 } 217 218 func hasUnknownValue(variable []interface{}) bool { 219 for _, value := range variable { 220 if strVal, ok := value.(string); ok { 221 if strVal == UnknownVariableValue { 222 return true 223 } 224 } 225 } 226 return false 227 } 228 229 func (w *interpolationWalker) splitSlice() { 230 raw := w.cs[len(w.cs)-1] 231 232 var s []interface{} 233 switch v := raw.Interface().(type) { 234 case []interface{}: 235 s = v 236 case []map[string]interface{}: 237 return 238 } 239 240 split := false 241 for _, val := range s { 242 if varVal, ok := val.(ast.Variable); ok && varVal.Type == ast.TypeList { 243 split = true 244 } 245 if _, ok := val.([]interface{}); ok { 246 split = true 247 } 248 } 249 250 if !split { 251 return 252 } 253 254 result := make([]interface{}, 0) 255 for _, v := range s { 256 switch val := v.(type) { 257 case ast.Variable: 258 switch val.Type { 259 case ast.TypeList: 260 elements := val.Value.([]ast.Variable) 261 for _, element := range elements { 262 result = append(result, element.Value) 263 } 264 default: 265 result = append(result, val.Value) 266 } 267 case []interface{}: 268 for _, element := range val { 269 result = append(result, element) 270 } 271 default: 272 result = append(result, v) 273 } 274 } 275 276 w.replaceCurrent(reflect.ValueOf(result)) 277 }