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  }