github.com/richardmarshall/terraform@v0.9.5-0.20170429023105-15704cc6ee35/helper/schema/field_reader.go (about) 1 package schema 2 3 import ( 4 "fmt" 5 "strconv" 6 ) 7 8 // FieldReaders are responsible for decoding fields out of data into 9 // the proper typed representation. ResourceData uses this to query data 10 // out of multiple sources: config, state, diffs, etc. 11 type FieldReader interface { 12 ReadField([]string) (FieldReadResult, error) 13 } 14 15 // FieldReadResult encapsulates all the resulting data from reading 16 // a field. 17 type FieldReadResult struct { 18 // Value is the actual read value. NegValue is the _negative_ value 19 // or the items that should be removed (if they existed). NegValue 20 // doesn't make sense for primitives but is important for any 21 // container types such as maps, sets, lists. 22 Value interface{} 23 ValueProcessed interface{} 24 25 // Exists is true if the field was found in the data. False means 26 // it wasn't found if there was no error. 27 Exists bool 28 29 // Computed is true if the field was found but the value 30 // is computed. 31 Computed bool 32 } 33 34 // ValueOrZero returns the value of this result or the zero value of the 35 // schema type, ensuring a consistent non-nil return value. 36 func (r *FieldReadResult) ValueOrZero(s *Schema) interface{} { 37 if r.Value != nil { 38 return r.Value 39 } 40 41 return s.ZeroValue() 42 } 43 44 // addrToSchema finds the final element schema for the given address 45 // and the given schema. It returns all the schemas that led to the final 46 // schema. These are in order of the address (out to in). 47 func addrToSchema(addr []string, schemaMap map[string]*Schema) []*Schema { 48 current := &Schema{ 49 Type: typeObject, 50 Elem: schemaMap, 51 } 52 53 // If we aren't given an address, then the user is requesting the 54 // full object, so we return the special value which is the full object. 55 if len(addr) == 0 { 56 return []*Schema{current} 57 } 58 59 result := make([]*Schema, 0, len(addr)) 60 for len(addr) > 0 { 61 k := addr[0] 62 addr = addr[1:] 63 64 REPEAT: 65 // We want to trim off the first "typeObject" since its not a 66 // real lookup that people do. i.e. []string{"foo"} in a structure 67 // isn't {typeObject, typeString}, its just a {typeString}. 68 if len(result) > 0 || current.Type != typeObject { 69 result = append(result, current) 70 } 71 72 switch t := current.Type; t { 73 case TypeBool, TypeInt, TypeFloat, TypeString: 74 if len(addr) > 0 { 75 return nil 76 } 77 case TypeList, TypeSet: 78 isIndex := len(addr) > 0 && addr[0] == "#" 79 80 switch v := current.Elem.(type) { 81 case *Resource: 82 current = &Schema{ 83 Type: typeObject, 84 Elem: v.Schema, 85 } 86 case *Schema: 87 current = v 88 case ValueType: 89 current = &Schema{Type: v} 90 default: 91 // we may not know the Elem type and are just looking for the 92 // index 93 if isIndex { 94 break 95 } 96 97 if len(addr) == 0 { 98 // we've processed the address, so return what we've 99 // collected 100 return result 101 } 102 103 if len(addr) == 1 { 104 if _, err := strconv.Atoi(addr[0]); err == nil { 105 // we're indexing a value without a schema. This can 106 // happen if the list is nested in another schema type. 107 // Default to a TypeString like we do with a map 108 current = &Schema{Type: TypeString} 109 break 110 } 111 } 112 113 return nil 114 } 115 116 // If we only have one more thing and the next thing 117 // is a #, then we're accessing the index which is always 118 // an int. 119 if isIndex { 120 current = &Schema{Type: TypeInt} 121 break 122 } 123 124 case TypeMap: 125 if len(addr) > 0 { 126 switch v := current.Elem.(type) { 127 case ValueType: 128 current = &Schema{Type: v} 129 default: 130 // maps default to string values. This is all we can have 131 // if this is nested in another list or map. 132 current = &Schema{Type: TypeString} 133 } 134 } 135 case typeObject: 136 // If we're already in the object, then we want to handle Sets 137 // and Lists specially. Basically, their next key is the lookup 138 // key (the set value or the list element). For these scenarios, 139 // we just want to skip it and move to the next element if there 140 // is one. 141 if len(result) > 0 { 142 lastType := result[len(result)-2].Type 143 if lastType == TypeSet || lastType == TypeList { 144 if len(addr) == 0 { 145 break 146 } 147 148 k = addr[0] 149 addr = addr[1:] 150 } 151 } 152 153 m := current.Elem.(map[string]*Schema) 154 val, ok := m[k] 155 if !ok { 156 return nil 157 } 158 159 current = val 160 goto REPEAT 161 } 162 } 163 164 return result 165 } 166 167 // readListField is a generic method for reading a list field out of a 168 // a FieldReader. It does this based on the assumption that there is a key 169 // "foo.#" for a list "foo" and that the indexes are "foo.0", "foo.1", etc. 170 // after that point. 171 func readListField( 172 r FieldReader, addr []string, schema *Schema) (FieldReadResult, error) { 173 addrPadded := make([]string, len(addr)+1) 174 copy(addrPadded, addr) 175 addrPadded[len(addrPadded)-1] = "#" 176 177 // Get the number of elements in the list 178 countResult, err := r.ReadField(addrPadded) 179 if err != nil { 180 return FieldReadResult{}, err 181 } 182 if !countResult.Exists { 183 // No count, means we have no list 184 countResult.Value = 0 185 } 186 187 // If we have an empty list, then return an empty list 188 if countResult.Computed || countResult.Value.(int) == 0 { 189 return FieldReadResult{ 190 Value: []interface{}{}, 191 Exists: countResult.Exists, 192 Computed: countResult.Computed, 193 }, nil 194 } 195 196 // Go through each count, and get the item value out of it 197 result := make([]interface{}, countResult.Value.(int)) 198 for i, _ := range result { 199 is := strconv.FormatInt(int64(i), 10) 200 addrPadded[len(addrPadded)-1] = is 201 rawResult, err := r.ReadField(addrPadded) 202 if err != nil { 203 return FieldReadResult{}, err 204 } 205 if !rawResult.Exists { 206 // This should never happen, because by the time the data 207 // gets to the FieldReaders, all the defaults should be set by 208 // Schema. 209 rawResult.Value = nil 210 } 211 212 result[i] = rawResult.Value 213 } 214 215 return FieldReadResult{ 216 Value: result, 217 Exists: true, 218 }, nil 219 } 220 221 // readObjectField is a generic method for reading objects out of FieldReaders 222 // based on the assumption that building an address of []string{k, FIELD} 223 // will result in the proper field data. 224 func readObjectField( 225 r FieldReader, 226 addr []string, 227 schema map[string]*Schema) (FieldReadResult, error) { 228 result := make(map[string]interface{}) 229 exists := false 230 for field, s := range schema { 231 addrRead := make([]string, len(addr), len(addr)+1) 232 copy(addrRead, addr) 233 addrRead = append(addrRead, field) 234 rawResult, err := r.ReadField(addrRead) 235 if err != nil { 236 return FieldReadResult{}, err 237 } 238 if rawResult.Exists { 239 exists = true 240 } 241 242 result[field] = rawResult.ValueOrZero(s) 243 } 244 245 return FieldReadResult{ 246 Value: result, 247 Exists: exists, 248 }, nil 249 } 250 251 // convert map values to the proper primitive type based on schema.Elem 252 func mapValuesToPrimitive(m map[string]interface{}, schema *Schema) error { 253 254 elemType := TypeString 255 if et, ok := schema.Elem.(ValueType); ok { 256 elemType = et 257 } 258 259 switch elemType { 260 case TypeInt, TypeFloat, TypeBool: 261 for k, v := range m { 262 vs, ok := v.(string) 263 if !ok { 264 continue 265 } 266 267 v, err := stringToPrimitive(vs, false, &Schema{Type: elemType}) 268 if err != nil { 269 return err 270 } 271 272 m[k] = v 273 } 274 } 275 return nil 276 } 277 278 func stringToPrimitive( 279 value string, computed bool, schema *Schema) (interface{}, error) { 280 var returnVal interface{} 281 switch schema.Type { 282 case TypeBool: 283 if value == "" { 284 returnVal = false 285 break 286 } 287 if computed { 288 break 289 } 290 291 v, err := strconv.ParseBool(value) 292 if err != nil { 293 return nil, err 294 } 295 296 returnVal = v 297 case TypeFloat: 298 if value == "" { 299 returnVal = 0.0 300 break 301 } 302 if computed { 303 break 304 } 305 306 v, err := strconv.ParseFloat(value, 64) 307 if err != nil { 308 return nil, err 309 } 310 311 returnVal = v 312 case TypeInt: 313 if value == "" { 314 returnVal = 0 315 break 316 } 317 if computed { 318 break 319 } 320 321 v, err := strconv.ParseInt(value, 0, 0) 322 if err != nil { 323 return nil, err 324 } 325 326 returnVal = int(v) 327 case TypeString: 328 returnVal = value 329 default: 330 panic(fmt.Sprintf("Unknown type: %s", schema.Type)) 331 } 332 333 return returnVal, nil 334 }