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  }