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