github.com/hashicorp/terraform-plugin-sdk@v1.17.2/helper/schema/field_reader_config.go (about) 1 package schema 2 3 import ( 4 "fmt" 5 "log" 6 "strconv" 7 "strings" 8 "sync" 9 10 "github.com/hashicorp/terraform-plugin-sdk/terraform" 11 "github.com/mitchellh/mapstructure" 12 ) 13 14 // ConfigFieldReader reads fields out of an untyped map[string]string to the 15 // best of its ability. It also applies defaults from the Schema. (The other 16 // field readers do not need default handling because they source fully 17 // populated data structures.) 18 type ConfigFieldReader struct { 19 Config *terraform.ResourceConfig 20 Schema map[string]*Schema 21 22 indexMaps map[string]map[string]int 23 once sync.Once 24 } 25 26 func (r *ConfigFieldReader) ReadField(address []string) (FieldReadResult, error) { 27 r.once.Do(func() { r.indexMaps = make(map[string]map[string]int) }) 28 return r.readField(address, false) 29 } 30 31 func (r *ConfigFieldReader) readField( 32 address []string, nested bool) (FieldReadResult, error) { 33 schemaList := addrToSchema(address, r.Schema) 34 if len(schemaList) == 0 { 35 return FieldReadResult{}, nil 36 } 37 38 if !nested { 39 // If we have a set anywhere in the address, then we need to 40 // read that set out in order and actually replace that part of 41 // the address with the real list index. i.e. set.50 might actually 42 // map to set.12 in the config, since it is in list order in the 43 // config, not indexed by set value. 44 for i, v := range schemaList { 45 // Sets are the only thing that cause this issue. 46 if v.Type != TypeSet { 47 continue 48 } 49 50 // If we're at the end of the list, then we don't have to worry 51 // about this because we're just requesting the whole set. 52 if i == len(schemaList)-1 { 53 continue 54 } 55 56 // If we're looking for the count, then ignore... 57 if address[i+1] == "#" { 58 continue 59 } 60 61 indexMap, ok := r.indexMaps[strings.Join(address[:i+1], ".")] 62 if !ok { 63 // Get the set so we can get the index map that tells us the 64 // mapping of the hash code to the list index 65 _, err := r.readSet(address[:i+1], v) 66 if err != nil { 67 return FieldReadResult{}, err 68 } 69 indexMap = r.indexMaps[strings.Join(address[:i+1], ".")] 70 } 71 72 index, ok := indexMap[address[i+1]] 73 if !ok { 74 return FieldReadResult{}, nil 75 } 76 77 address[i+1] = strconv.FormatInt(int64(index), 10) 78 } 79 } 80 81 k := strings.Join(address, ".") 82 schema := schemaList[len(schemaList)-1] 83 84 // If we're getting the single element of a promoted list, then 85 // check to see if we have a single element we need to promote. 86 if address[len(address)-1] == "0" && len(schemaList) > 1 { 87 lastSchema := schemaList[len(schemaList)-2] 88 if lastSchema.Type == TypeList && lastSchema.PromoteSingle { 89 k := strings.Join(address[:len(address)-1], ".") 90 result, err := r.readPrimitive(k, schema) 91 if err == nil { 92 return result, nil 93 } 94 } 95 } 96 97 if protoVersion5 { 98 switch schema.Type { 99 case TypeList, TypeSet, TypeMap, typeObject: 100 // Check if the value itself is unknown. 101 // The new protocol shims will add unknown values to this list of 102 // ComputedKeys. This is the only way we have to indicate that a 103 // collection is unknown in the config 104 for _, unknown := range r.Config.ComputedKeys { 105 if k == unknown { 106 log.Printf("[DEBUG] setting computed for %q from ComputedKeys", k) 107 return FieldReadResult{Computed: true, Exists: true}, nil 108 } 109 } 110 } 111 } 112 113 switch schema.Type { 114 case TypeBool, TypeFloat, TypeInt, TypeString: 115 return r.readPrimitive(k, schema) 116 case TypeList: 117 // If we support promotion then we first check if we have a lone 118 // value that we must promote. 119 // a value that is alone. 120 if schema.PromoteSingle { 121 result, err := r.readPrimitive(k, schema.Elem.(*Schema)) 122 if err == nil && result.Exists { 123 result.Value = []interface{}{result.Value} 124 return result, nil 125 } 126 } 127 128 return readListField(&nestedConfigFieldReader{r}, address, schema) 129 case TypeMap: 130 return r.readMap(k, schema) 131 case TypeSet: 132 return r.readSet(address, schema) 133 case typeObject: 134 return readObjectField( 135 &nestedConfigFieldReader{r}, 136 address, schema.Elem.(map[string]*Schema)) 137 default: 138 panic(fmt.Sprintf("Unknown type: %s", schema.Type)) 139 } 140 } 141 142 func (r *ConfigFieldReader) readMap(k string, schema *Schema) (FieldReadResult, error) { 143 // We want both the raw value and the interpolated. We use the interpolated 144 // to store actual values and we use the raw one to check for 145 // computed keys. Actual values are obtained in the switch, depending on 146 // the type of the raw value. 147 mraw, ok := r.Config.GetRaw(k) 148 if !ok { 149 // check if this is from an interpolated field by seeing if it exists 150 // in the config 151 _, ok := r.Config.Get(k) 152 if !ok { 153 // this really doesn't exist 154 return FieldReadResult{}, nil 155 } 156 157 // We couldn't fetch the value from a nested data structure, so treat the 158 // raw value as an interpolation string. The mraw value is only used 159 // for the type switch below. 160 mraw = "${INTERPOLATED}" 161 } 162 163 result := make(map[string]interface{}) 164 computed := false 165 switch m := mraw.(type) { 166 case string: 167 // This is a map which has come out of an interpolated variable, so we 168 // can just get the value directly from config. Values cannot be computed 169 // currently. 170 v, _ := r.Config.Get(k) 171 172 // If this isn't a map[string]interface, it must be computed. 173 mapV, ok := v.(map[string]interface{}) 174 if !ok { 175 return FieldReadResult{ 176 Exists: true, 177 Computed: true, 178 }, nil 179 } 180 181 // Otherwise we can proceed as usual. 182 for i, iv := range mapV { 183 result[i] = iv 184 } 185 case []interface{}: 186 for i, innerRaw := range m { 187 for ik := range innerRaw.(map[string]interface{}) { 188 key := fmt.Sprintf("%s.%d.%s", k, i, ik) 189 if r.Config.IsComputed(key) { 190 computed = true 191 break 192 } 193 194 v, _ := r.Config.Get(key) 195 result[ik] = v 196 } 197 } 198 case []map[string]interface{}: 199 for i, innerRaw := range m { 200 for ik := range innerRaw { 201 key := fmt.Sprintf("%s.%d.%s", k, i, ik) 202 if r.Config.IsComputed(key) { 203 computed = true 204 break 205 } 206 207 v, _ := r.Config.Get(key) 208 result[ik] = v 209 } 210 } 211 case map[string]interface{}: 212 for ik := range m { 213 key := fmt.Sprintf("%s.%s", k, ik) 214 if r.Config.IsComputed(key) { 215 computed = true 216 break 217 } 218 219 v, _ := r.Config.Get(key) 220 result[ik] = v 221 } 222 case nil: 223 // the map may have been empty on the configuration, so we leave the 224 // empty result 225 default: 226 panic(fmt.Sprintf("unknown type: %#v", mraw)) 227 } 228 229 err := mapValuesToPrimitive(k, result, schema) 230 if err != nil { 231 return FieldReadResult{}, nil 232 } 233 234 var value interface{} 235 if !computed { 236 value = result 237 } 238 239 return FieldReadResult{ 240 Value: value, 241 Exists: true, 242 Computed: computed, 243 }, nil 244 } 245 246 func (r *ConfigFieldReader) readPrimitive( 247 k string, schema *Schema) (FieldReadResult, error) { 248 raw, ok := r.Config.Get(k) 249 if !ok { 250 // Nothing in config, but we might still have a default from the schema 251 var err error 252 raw, err = schema.DefaultValue() 253 if err != nil { 254 return FieldReadResult{}, fmt.Errorf("%s, error loading default: %s", k, err) 255 } 256 257 if raw == nil { 258 return FieldReadResult{}, nil 259 } 260 } 261 262 var result string 263 if err := mapstructure.WeakDecode(raw, &result); err != nil { 264 return FieldReadResult{}, err 265 } 266 267 computed := r.Config.IsComputed(k) 268 returnVal, err := stringToPrimitive(result, computed, schema) 269 if err != nil { 270 return FieldReadResult{}, err 271 } 272 273 return FieldReadResult{ 274 Value: returnVal, 275 Exists: true, 276 Computed: computed, 277 }, nil 278 } 279 280 func (r *ConfigFieldReader) readSet( 281 address []string, schema *Schema) (FieldReadResult, error) { 282 indexMap := make(map[string]int) 283 // Create the set that will be our result 284 set := schema.ZeroValue().(*Set) 285 286 raw, err := readListField(&nestedConfigFieldReader{r}, address, schema) 287 if err != nil { 288 return FieldReadResult{}, err 289 } 290 if !raw.Exists { 291 return FieldReadResult{Value: set}, nil 292 } 293 294 // If the list is computed, the set is necessarilly computed 295 if raw.Computed { 296 return FieldReadResult{ 297 Value: set, 298 Exists: true, 299 Computed: raw.Computed, 300 }, nil 301 } 302 303 // Build up the set from the list elements 304 for i, v := range raw.Value.([]interface{}) { 305 // Check if any of the keys in this item are computed 306 computed := r.hasComputedSubKeys( 307 fmt.Sprintf("%s.%d", strings.Join(address, "."), i), schema) 308 309 code := set.add(v, computed) 310 indexMap[code] = i 311 } 312 313 r.indexMaps[strings.Join(address, ".")] = indexMap 314 315 return FieldReadResult{ 316 Value: set, 317 Exists: true, 318 }, nil 319 } 320 321 // hasComputedSubKeys walks through a schema and returns whether or not the 322 // given key contains any subkeys that are computed. 323 func (r *ConfigFieldReader) hasComputedSubKeys(key string, schema *Schema) bool { 324 prefix := key + "." 325 326 switch t := schema.Elem.(type) { 327 case *Resource: 328 for k, schema := range t.Schema { 329 if r.Config.IsComputed(prefix + k) { 330 return true 331 } 332 333 if r.hasComputedSubKeys(prefix+k, schema) { 334 return true 335 } 336 } 337 } 338 339 return false 340 } 341 342 // nestedConfigFieldReader is a funny little thing that just wraps a 343 // ConfigFieldReader to call readField when ReadField is called so that 344 // we don't recalculate the set rewrites in the address, which leads to 345 // an infinite loop. 346 type nestedConfigFieldReader struct { 347 Reader *ConfigFieldReader 348 } 349 350 func (r *nestedConfigFieldReader) ReadField( 351 address []string) (FieldReadResult, error) { 352 return r.Reader.readField(address, true) 353 }