github.com/opentofu/opentofu@v1.7.1/internal/legacy/helper/schema/field_writer_map.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 "reflect" 11 "strconv" 12 "strings" 13 "sync" 14 15 "github.com/go-viper/mapstructure/v2" 16 ) 17 18 // MapFieldWriter writes data into a single map[string]string structure. 19 type MapFieldWriter struct { 20 Schema map[string]*Schema 21 22 lock sync.Mutex 23 result map[string]string 24 } 25 26 // Map returns the underlying map that is being written to. 27 func (w *MapFieldWriter) Map() map[string]string { 28 w.lock.Lock() 29 defer w.lock.Unlock() 30 if w.result == nil { 31 w.result = make(map[string]string) 32 } 33 34 return w.result 35 } 36 37 func (w *MapFieldWriter) unsafeWriteField(addr string, value string) { 38 w.lock.Lock() 39 defer w.lock.Unlock() 40 if w.result == nil { 41 w.result = make(map[string]string) 42 } 43 44 w.result[addr] = value 45 } 46 47 // clearTree clears a field and any sub-fields of the given address out of the 48 // map. This should be used to reset some kind of complex structures (namely 49 // sets) before writing to make sure that any conflicting data is removed (for 50 // example, if the set was previously written to the writer's layer). 51 func (w *MapFieldWriter) clearTree(addr []string) { 52 prefix := strings.Join(addr, ".") + "." 53 for k := range w.result { 54 if strings.HasPrefix(k, prefix) { 55 delete(w.result, k) 56 } 57 } 58 } 59 60 func (w *MapFieldWriter) WriteField(addr []string, value interface{}) error { 61 w.lock.Lock() 62 defer w.lock.Unlock() 63 if w.result == nil { 64 w.result = make(map[string]string) 65 } 66 67 schemaList := addrToSchema(addr, w.Schema) 68 if len(schemaList) == 0 { 69 return fmt.Errorf("Invalid address to set: %#v", addr) 70 } 71 72 // If we're setting anything other than a list root or set root, 73 // then disallow it. 74 for _, schema := range schemaList[:len(schemaList)-1] { 75 if schema.Type == TypeList { 76 return fmt.Errorf( 77 "%s: can only set full list", 78 strings.Join(addr, ".")) 79 } 80 81 if schema.Type == TypeMap { 82 return fmt.Errorf( 83 "%s: can only set full map", 84 strings.Join(addr, ".")) 85 } 86 87 if schema.Type == TypeSet { 88 return fmt.Errorf( 89 "%s: can only set full set", 90 strings.Join(addr, ".")) 91 } 92 } 93 94 return w.set(addr, value) 95 } 96 97 func (w *MapFieldWriter) set(addr []string, value interface{}) error { 98 schemaList := addrToSchema(addr, w.Schema) 99 if len(schemaList) == 0 { 100 return fmt.Errorf("Invalid address to set: %#v", addr) 101 } 102 103 schema := schemaList[len(schemaList)-1] 104 switch schema.Type { 105 case TypeBool, TypeInt, TypeFloat, TypeString: 106 return w.setPrimitive(addr, value, schema) 107 case TypeList: 108 return w.setList(addr, value, schema) 109 case TypeMap: 110 return w.setMap(addr, value, schema) 111 case TypeSet: 112 return w.setSet(addr, value, schema) 113 case typeObject: 114 return w.setObject(addr, value, schema) 115 default: 116 panic(fmt.Sprintf("Unknown type: %#v", schema.Type)) 117 } 118 } 119 120 func (w *MapFieldWriter) setList( 121 addr []string, 122 v interface{}, 123 schema *Schema) error { 124 k := strings.Join(addr, ".") 125 setElement := func(idx string, value interface{}) error { 126 addrCopy := make([]string, len(addr), len(addr)+1) 127 copy(addrCopy, addr) 128 return w.set(append(addrCopy, idx), value) 129 } 130 131 var vs []interface{} 132 if err := mapstructure.Decode(v, &vs); err != nil { 133 return fmt.Errorf("%s: %w", k, err) 134 } 135 136 // Wipe the set from the current writer prior to writing if it exists. 137 // Multiple writes to the same layer is a lot safer for lists than sets due 138 // to the fact that indexes are always deterministic and the length will 139 // always be updated with the current length on the last write, but making 140 // sure we have a clean namespace removes any chance for edge cases to pop up 141 // and ensures that the last write to the set is the correct value. 142 w.clearTree(addr) 143 144 // Set the entire list. 145 var err error 146 for i, elem := range vs { 147 is := strconv.FormatInt(int64(i), 10) 148 err = setElement(is, elem) 149 if err != nil { 150 break 151 } 152 } 153 if err != nil { 154 for i, _ := range vs { 155 is := strconv.FormatInt(int64(i), 10) 156 setElement(is, nil) 157 } 158 159 return err 160 } 161 162 w.result[k+".#"] = strconv.FormatInt(int64(len(vs)), 10) 163 return nil 164 } 165 166 func (w *MapFieldWriter) setMap( 167 addr []string, 168 value interface{}, 169 schema *Schema) error { 170 k := strings.Join(addr, ".") 171 v := reflect.ValueOf(value) 172 vs := make(map[string]interface{}) 173 174 if value == nil { 175 // The empty string here means the map is removed. 176 w.result[k] = "" 177 return nil 178 } 179 180 if v.Kind() != reflect.Map { 181 return fmt.Errorf("%s: must be a map", k) 182 } 183 if v.Type().Key().Kind() != reflect.String { 184 return fmt.Errorf("%s: keys must strings", k) 185 } 186 for _, mk := range v.MapKeys() { 187 mv := v.MapIndex(mk) 188 vs[mk.String()] = mv.Interface() 189 } 190 191 // Wipe this address tree. The contents of the map should always reflect the 192 // last write made to it. 193 w.clearTree(addr) 194 195 // Remove the pure key since we're setting the full map value 196 delete(w.result, k) 197 198 // Set each subkey 199 addrCopy := make([]string, len(addr), len(addr)+1) 200 copy(addrCopy, addr) 201 for subKey, v := range vs { 202 if err := w.set(append(addrCopy, subKey), v); err != nil { 203 return err 204 } 205 } 206 207 // Set the count 208 w.result[k+".%"] = strconv.Itoa(len(vs)) 209 210 return nil 211 } 212 213 func (w *MapFieldWriter) setObject( 214 addr []string, 215 value interface{}, 216 schema *Schema) error { 217 // Set the entire object. First decode into a proper structure 218 var v map[string]interface{} 219 if err := mapstructure.Decode(value, &v); err != nil { 220 return fmt.Errorf("%s: %w", strings.Join(addr, "."), err) 221 } 222 223 // Make space for additional elements in the address 224 addrCopy := make([]string, len(addr), len(addr)+1) 225 copy(addrCopy, addr) 226 227 // Set each element in turn 228 var err error 229 for k1, v1 := range v { 230 if err = w.set(append(addrCopy, k1), v1); err != nil { 231 break 232 } 233 } 234 if err != nil { 235 for k1, _ := range v { 236 w.set(append(addrCopy, k1), nil) 237 } 238 } 239 240 return err 241 } 242 243 func (w *MapFieldWriter) setPrimitive( 244 addr []string, 245 v interface{}, 246 schema *Schema) error { 247 k := strings.Join(addr, ".") 248 249 if v == nil { 250 // The empty string here means the value is removed. 251 w.result[k] = "" 252 return nil 253 } 254 255 var set string 256 switch schema.Type { 257 case TypeBool: 258 var b bool 259 if err := mapstructure.Decode(v, &b); err != nil { 260 return fmt.Errorf("%s: %w", k, err) 261 } 262 263 set = strconv.FormatBool(b) 264 case TypeString: 265 if err := mapstructure.Decode(v, &set); err != nil { 266 return fmt.Errorf("%s: %w", k, err) 267 } 268 case TypeInt: 269 var n int 270 if err := mapstructure.Decode(v, &n); err != nil { 271 return fmt.Errorf("%s: %w", k, err) 272 } 273 set = strconv.FormatInt(int64(n), 10) 274 case TypeFloat: 275 var n float64 276 if err := mapstructure.Decode(v, &n); err != nil { 277 return fmt.Errorf("%s: %w", k, err) 278 } 279 set = strconv.FormatFloat(float64(n), 'G', -1, 64) 280 default: 281 return fmt.Errorf("Unknown type: %#v", schema.Type) 282 } 283 284 w.result[k] = set 285 return nil 286 } 287 288 func (w *MapFieldWriter) setSet( 289 addr []string, 290 value interface{}, 291 schema *Schema) error { 292 addrCopy := make([]string, len(addr), len(addr)+1) 293 copy(addrCopy, addr) 294 k := strings.Join(addr, ".") 295 296 if value == nil { 297 w.result[k+".#"] = "0" 298 return nil 299 } 300 301 // If it is a slice, then we have to turn it into a *Set so that 302 // we get the proper order back based on the hash code. 303 if v := reflect.ValueOf(value); v.Kind() == reflect.Slice { 304 // Build a temp *ResourceData to use for the conversion 305 tempAddr := addr[len(addr)-1:] 306 tempSchema := *schema 307 tempSchema.Type = TypeList 308 tempSchemaMap := map[string]*Schema{tempAddr[0]: &tempSchema} 309 tempW := &MapFieldWriter{Schema: tempSchemaMap} 310 311 // Set the entire list, this lets us get values out of it 312 if err := tempW.WriteField(tempAddr, value); err != nil { 313 return err 314 } 315 316 // Build the set by going over the list items in order and 317 // hashing them into the set. The reason we go over the list and 318 // not the `value` directly is because this forces all types 319 // to become []interface{} (generic) instead of []string, which 320 // most hash functions are expecting. 321 s := schema.ZeroValue().(*Set) 322 tempR := &MapFieldReader{ 323 Map: BasicMapReader(tempW.Map()), 324 Schema: tempSchemaMap, 325 } 326 for i := 0; i < v.Len(); i++ { 327 is := strconv.FormatInt(int64(i), 10) 328 result, err := tempR.ReadField(append(tempAddr, is)) 329 if err != nil { 330 return err 331 } 332 if !result.Exists { 333 panic("set item just set doesn't exist") 334 } 335 336 s.Add(result.Value) 337 } 338 339 value = s 340 } 341 342 // Clear any keys that match the set address first. This is necessary because 343 // it's always possible and sometimes may be necessary to write to a certain 344 // writer layer more than once with different set data each time, which will 345 // lead to different keys being inserted, which can lead to determinism 346 // problems when the old data isn't wiped first. 347 w.clearTree(addr) 348 349 if value.(*Set) == nil { 350 w.result[k+".#"] = "0" 351 return nil 352 } 353 354 for code, elem := range value.(*Set).m { 355 if err := w.set(append(addrCopy, code), elem); err != nil { 356 return err 357 } 358 } 359 360 w.result[k+".#"] = strconv.Itoa(value.(*Set).Len()) 361 return nil 362 }