github.com/cmalfait/terraform@v0.11.12-beta1/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 case *Schema: 130 current, _ = current.Elem.(*Schema) 131 default: 132 // maps default to string values. This is all we can have 133 // if this is nested in another list or map. 134 current = &Schema{Type: TypeString} 135 } 136 } 137 case typeObject: 138 // If we're already in the object, then we want to handle Sets 139 // and Lists specially. Basically, their next key is the lookup 140 // key (the set value or the list element). For these scenarios, 141 // we just want to skip it and move to the next element if there 142 // is one. 143 if len(result) > 0 { 144 lastType := result[len(result)-2].Type 145 if lastType == TypeSet || lastType == TypeList { 146 if len(addr) == 0 { 147 break 148 } 149 150 k = addr[0] 151 addr = addr[1:] 152 } 153 } 154 155 m := current.Elem.(map[string]*Schema) 156 val, ok := m[k] 157 if !ok { 158 return nil 159 } 160 161 current = val 162 goto REPEAT 163 } 164 } 165 166 return result 167 } 168 169 // readListField is a generic method for reading a list field out of a 170 // a FieldReader. It does this based on the assumption that there is a key 171 // "foo.#" for a list "foo" and that the indexes are "foo.0", "foo.1", etc. 172 // after that point. 173 func readListField( 174 r FieldReader, addr []string, schema *Schema) (FieldReadResult, error) { 175 addrPadded := make([]string, len(addr)+1) 176 copy(addrPadded, addr) 177 addrPadded[len(addrPadded)-1] = "#" 178 179 // Get the number of elements in the list 180 countResult, err := r.ReadField(addrPadded) 181 if err != nil { 182 return FieldReadResult{}, err 183 } 184 if !countResult.Exists { 185 // No count, means we have no list 186 countResult.Value = 0 187 } 188 189 // If we have an empty list, then return an empty list 190 if countResult.Computed || countResult.Value.(int) == 0 { 191 return FieldReadResult{ 192 Value: []interface{}{}, 193 Exists: countResult.Exists, 194 Computed: countResult.Computed, 195 }, nil 196 } 197 198 // Go through each count, and get the item value out of it 199 result := make([]interface{}, countResult.Value.(int)) 200 for i, _ := range result { 201 is := strconv.FormatInt(int64(i), 10) 202 addrPadded[len(addrPadded)-1] = is 203 rawResult, err := r.ReadField(addrPadded) 204 if err != nil { 205 return FieldReadResult{}, err 206 } 207 if !rawResult.Exists { 208 // This should never happen, because by the time the data 209 // gets to the FieldReaders, all the defaults should be set by 210 // Schema. 211 rawResult.Value = nil 212 } 213 214 result[i] = rawResult.Value 215 } 216 217 return FieldReadResult{ 218 Value: result, 219 Exists: true, 220 }, nil 221 } 222 223 // readObjectField is a generic method for reading objects out of FieldReaders 224 // based on the assumption that building an address of []string{k, FIELD} 225 // will result in the proper field data. 226 func readObjectField( 227 r FieldReader, 228 addr []string, 229 schema map[string]*Schema) (FieldReadResult, error) { 230 result := make(map[string]interface{}) 231 exists := false 232 for field, s := range schema { 233 addrRead := make([]string, len(addr), len(addr)+1) 234 copy(addrRead, addr) 235 addrRead = append(addrRead, field) 236 rawResult, err := r.ReadField(addrRead) 237 if err != nil { 238 return FieldReadResult{}, err 239 } 240 if rawResult.Exists { 241 exists = true 242 } 243 244 result[field] = rawResult.ValueOrZero(s) 245 } 246 247 return FieldReadResult{ 248 Value: result, 249 Exists: exists, 250 }, nil 251 } 252 253 // convert map values to the proper primitive type based on schema.Elem 254 func mapValuesToPrimitive(k string, m map[string]interface{}, schema *Schema) error { 255 elemType, err := getValueType(k, schema) 256 if err != nil { 257 return err 258 } 259 260 switch elemType { 261 case TypeInt, TypeFloat, TypeBool: 262 for k, v := range m { 263 vs, ok := v.(string) 264 if !ok { 265 continue 266 } 267 268 v, err := stringToPrimitive(vs, false, &Schema{Type: elemType}) 269 if err != nil { 270 return err 271 } 272 273 m[k] = v 274 } 275 } 276 return nil 277 } 278 279 func stringToPrimitive( 280 value string, computed bool, schema *Schema) (interface{}, error) { 281 var returnVal interface{} 282 switch schema.Type { 283 case TypeBool: 284 if value == "" { 285 returnVal = false 286 break 287 } 288 if computed { 289 break 290 } 291 292 v, err := strconv.ParseBool(value) 293 if err != nil { 294 return nil, err 295 } 296 297 returnVal = v 298 case TypeFloat: 299 if value == "" { 300 returnVal = 0.0 301 break 302 } 303 if computed { 304 break 305 } 306 307 v, err := strconv.ParseFloat(value, 64) 308 if err != nil { 309 return nil, err 310 } 311 312 returnVal = v 313 case TypeInt: 314 if value == "" { 315 returnVal = 0 316 break 317 } 318 if computed { 319 break 320 } 321 322 v, err := strconv.ParseInt(value, 0, 0) 323 if err != nil { 324 return nil, err 325 } 326 327 returnVal = int(v) 328 case TypeString: 329 returnVal = value 330 default: 331 panic(fmt.Sprintf("Unknown type: %s", schema.Type)) 332 } 333 334 return returnVal, nil 335 }