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