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