1package schema
2
3import (
4	"errors"
5	"fmt"
6	"reflect"
7	"strings"
8	"sync"
9
10	"github.com/hashicorp/terraform-plugin-sdk/terraform"
11)
12
13// newValueWriter is a minor re-implementation of MapFieldWriter to include
14// keys that should be marked as computed, to represent the new part of a
15// pseudo-diff.
16type newValueWriter struct {
17	*MapFieldWriter
18
19	// A list of keys that should be marked as computed.
20	computedKeys map[string]bool
21
22	// A lock to prevent races on writes. The underlying writer will have one as
23	// well - this is for computed keys.
24	lock sync.Mutex
25
26	// To be used with init.
27	once sync.Once
28}
29
30// init performs any initialization tasks for the newValueWriter.
31func (w *newValueWriter) init() {
32	if w.computedKeys == nil {
33		w.computedKeys = make(map[string]bool)
34	}
35}
36
37// WriteField overrides MapValueWriter's WriteField, adding the ability to flag
38// the address as computed.
39func (w *newValueWriter) WriteField(address []string, value interface{}, computed bool) error {
40	// Fail the write if we have a non-nil value and computed is true.
41	// NewComputed values should not have a value when written.
42	if value != nil && computed {
43		return errors.New("Non-nil value with computed set")
44	}
45
46	if err := w.MapFieldWriter.WriteField(address, value); err != nil {
47		return err
48	}
49
50	w.once.Do(w.init)
51
52	w.lock.Lock()
53	defer w.lock.Unlock()
54	if computed {
55		w.computedKeys[strings.Join(address, ".")] = true
56	}
57	return nil
58}
59
60// ComputedKeysMap returns the underlying computed keys map.
61func (w *newValueWriter) ComputedKeysMap() map[string]bool {
62	w.once.Do(w.init)
63	return w.computedKeys
64}
65
66// newValueReader is a minor re-implementation of MapFieldReader and is the
67// read counterpart to MapValueWriter, allowing the read of keys flagged as
68// computed to accommodate the diff override logic in ResourceDiff.
69type newValueReader struct {
70	*MapFieldReader
71
72	// The list of computed keys from a newValueWriter.
73	computedKeys map[string]bool
74}
75
76// ReadField reads the values from the underlying writer, returning the
77// computed value if it is found as well.
78func (r *newValueReader) ReadField(address []string) (FieldReadResult, error) {
79	addrKey := strings.Join(address, ".")
80	v, err := r.MapFieldReader.ReadField(address)
81	if err != nil {
82		return FieldReadResult{}, err
83	}
84	for computedKey := range r.computedKeys {
85		if childAddrOf(addrKey, computedKey) {
86			if strings.HasSuffix(addrKey, ".#") {
87				// This is a count value for a list or set that has been marked as
88				// computed, or a sub-list/sub-set of a complex resource that has
89				// been marked as computed.  We need to pass through to other readers
90				// so that an accurate previous count can be fetched for the diff.
91				v.Exists = false
92			}
93			v.Computed = true
94		}
95	}
96
97	return v, nil
98}
99
100// ResourceDiff is used to query and make custom changes to an in-flight diff.
101// It can be used to veto particular changes in the diff, customize the diff
102// that has been created, or diff values not controlled by config.
103//
104// The object functions similar to ResourceData, however most notably lacks
105// Set, SetPartial, and Partial, as it should be used to change diff values
106// only.  Most other first-class ResourceData functions exist, namely Get,
107// GetOk, HasChange, and GetChange exist.
108//
109// All functions in ResourceDiff, save for ForceNew, can only be used on
110// computed fields.
111type ResourceDiff struct {
112	// The schema for the resource being worked on.
113	schema map[string]*Schema
114
115	// The current config for this resource.
116	config *terraform.ResourceConfig
117
118	// The state for this resource as it exists post-refresh, after the initial
119	// diff.
120	state *terraform.InstanceState
121
122	// The diff created by Terraform. This diff is used, along with state,
123	// config, and custom-set diff data, to provide a multi-level reader
124	// experience similar to ResourceData.
125	diff *terraform.InstanceDiff
126
127	// The internal reader structure that contains the state, config, the default
128	// diff, and the new diff.
129	multiReader *MultiLevelFieldReader
130
131	// A writer that writes overridden new fields.
132	newWriter *newValueWriter
133
134	// Tracks which keys have been updated by ResourceDiff to ensure that the
135	// diff does not get re-run on keys that were not touched, or diffs that were
136	// just removed (re-running on the latter would just roll back the removal).
137	updatedKeys map[string]bool
138
139	// Tracks which keys were flagged as forceNew. These keys are not saved in
140	// newWriter, but we need to track them so that they can be re-diffed later.
141	forcedNewKeys map[string]bool
142}
143
144// newResourceDiff creates a new ResourceDiff instance.
145func newResourceDiff(schema map[string]*Schema, config *terraform.ResourceConfig, state *terraform.InstanceState, diff *terraform.InstanceDiff) *ResourceDiff {
146	d := &ResourceDiff{
147		config: config,
148		state:  state,
149		diff:   diff,
150		schema: schema,
151	}
152
153	d.newWriter = &newValueWriter{
154		MapFieldWriter: &MapFieldWriter{Schema: d.schema},
155	}
156	readers := make(map[string]FieldReader)
157	var stateAttributes map[string]string
158	if d.state != nil {
159		stateAttributes = d.state.Attributes
160		readers["state"] = &MapFieldReader{
161			Schema: d.schema,
162			Map:    BasicMapReader(stateAttributes),
163		}
164	}
165	if d.config != nil {
166		readers["config"] = &ConfigFieldReader{
167			Schema: d.schema,
168			Config: d.config,
169		}
170	}
171	if d.diff != nil {
172		readers["diff"] = &DiffFieldReader{
173			Schema: d.schema,
174			Diff:   d.diff,
175			Source: &MultiLevelFieldReader{
176				Levels:  []string{"state", "config"},
177				Readers: readers,
178			},
179		}
180	}
181	readers["newDiff"] = &newValueReader{
182		MapFieldReader: &MapFieldReader{
183			Schema: d.schema,
184			Map:    BasicMapReader(d.newWriter.Map()),
185		},
186		computedKeys: d.newWriter.ComputedKeysMap(),
187	}
188	d.multiReader = &MultiLevelFieldReader{
189		Levels: []string{
190			"state",
191			"config",
192			"diff",
193			"newDiff",
194		},
195
196		Readers: readers,
197	}
198
199	d.updatedKeys = make(map[string]bool)
200	d.forcedNewKeys = make(map[string]bool)
201
202	return d
203}
204
205// UpdatedKeys returns the keys that were updated by this ResourceDiff run.
206// These are the only keys that a diff should be re-calculated for.
207//
208// This is the combined result of both keys for which diff values were updated
209// for or cleared, and also keys that were flagged to be re-diffed as a result
210// of ForceNew.
211func (d *ResourceDiff) UpdatedKeys() []string {
212	var s []string
213	for k := range d.updatedKeys {
214		s = append(s, k)
215	}
216	for k := range d.forcedNewKeys {
217		for _, l := range s {
218			if k == l {
219				break
220			}
221		}
222		s = append(s, k)
223	}
224	return s
225}
226
227// Clear wipes the diff for a particular key. It is called by ResourceDiff's
228// functionality to remove any possibility of conflicts, but can be called on
229// its own to just remove a specific key from the diff completely.
230//
231// Note that this does not wipe an override. This function is only allowed on
232// computed keys.
233func (d *ResourceDiff) Clear(key string) error {
234	if err := d.checkKey(key, "Clear", true); err != nil {
235		return err
236	}
237
238	return d.clear(key)
239}
240
241func (d *ResourceDiff) clear(key string) error {
242	// Check the schema to make sure that this key exists first.
243	schemaL := addrToSchema(strings.Split(key, "."), d.schema)
244	if len(schemaL) == 0 {
245		return fmt.Errorf("%s is not a valid key", key)
246	}
247
248	for k := range d.diff.Attributes {
249		if strings.HasPrefix(k, key) {
250			delete(d.diff.Attributes, k)
251		}
252	}
253	return nil
254}
255
256// GetChangedKeysPrefix helps to implement Resource.CustomizeDiff
257// where we need to act on all nested fields
258// without calling out each one separately
259func (d *ResourceDiff) GetChangedKeysPrefix(prefix string) []string {
260	keys := make([]string, 0)
261	for k := range d.diff.Attributes {
262		if strings.HasPrefix(k, prefix) {
263			keys = append(keys, k)
264		}
265	}
266	return keys
267}
268
269// diffChange helps to implement resourceDiffer and derives its change values
270// from ResourceDiff's own change data, in addition to existing diff, config, and state.
271func (d *ResourceDiff) diffChange(key string) (interface{}, interface{}, bool, bool, bool) {
272	old, new, customized := d.getChange(key)
273
274	if !old.Exists {
275		old.Value = nil
276	}
277	if !new.Exists || d.removed(key) {
278		new.Value = nil
279	}
280
281	return old.Value, new.Value, !reflect.DeepEqual(old.Value, new.Value), new.Computed, customized
282}
283
284// SetNew is used to set a new diff value for the mentioned key. The value must
285// be correct for the attribute's schema (mostly relevant for maps, lists, and
286// sets). The original value from the state is used as the old value.
287//
288// This function is only allowed on computed attributes.
289func (d *ResourceDiff) SetNew(key string, value interface{}) error {
290	if err := d.checkKey(key, "SetNew", false); err != nil {
291		return err
292	}
293
294	return d.setDiff(key, value, false)
295}
296
297// SetNewComputed functions like SetNew, except that it blanks out a new value
298// and marks it as computed.
299//
300// This function is only allowed on computed attributes.
301func (d *ResourceDiff) SetNewComputed(key string) error {
302	if err := d.checkKey(key, "SetNewComputed", false); err != nil {
303		return err
304	}
305
306	return d.setDiff(key, nil, true)
307}
308
309// setDiff performs common diff setting behaviour.
310func (d *ResourceDiff) setDiff(key string, new interface{}, computed bool) error {
311	if err := d.clear(key); err != nil {
312		return err
313	}
314
315	if err := d.newWriter.WriteField(strings.Split(key, "."), new, computed); err != nil {
316		return fmt.Errorf("Cannot set new diff value for key %s: %s", key, err)
317	}
318
319	d.updatedKeys[key] = true
320
321	return nil
322}
323
324// ForceNew force-flags ForceNew in the schema for a specific key, and
325// re-calculates its diff, effectively causing this attribute to force a new
326// resource.
327//
328// Keep in mind that forcing a new resource will force a second run of the
329// resource's CustomizeDiff function (with a new ResourceDiff) once the current
330// one has completed. This second run is performed without state. This behavior
331// will be the same as if a new resource is being created and is performed to
332// ensure that the diff looks like the diff for a new resource as much as
333// possible. CustomizeDiff should expect such a scenario and act correctly.
334//
335// This function is a no-op/error if there is no diff.
336//
337// Note that the change to schema is permanent for the lifecycle of this
338// specific ResourceDiff instance.
339func (d *ResourceDiff) ForceNew(key string) error {
340	if !d.HasChange(key) {
341		return fmt.Errorf("ForceNew: No changes for %s", key)
342	}
343
344	keyParts := strings.Split(key, ".")
345	var schema *Schema
346	schemaL := addrToSchema(keyParts, d.schema)
347	if len(schemaL) > 0 {
348		schema = schemaL[len(schemaL)-1]
349	} else {
350		return fmt.Errorf("ForceNew: %s is not a valid key", key)
351	}
352
353	schema.ForceNew = true
354
355	// Flag this for a re-diff. Don't save any values to guarantee that existing
356	// diffs aren't messed with, as this gets messy when dealing with complex
357	// structures, zero values, etc.
358	d.forcedNewKeys[keyParts[0]] = true
359
360	return nil
361}
362
363// Get hands off to ResourceData.Get.
364func (d *ResourceDiff) Get(key string) interface{} {
365	r, _ := d.GetOk(key)
366	return r
367}
368
369// GetChange gets the change between the state and diff, checking first to see
370// if an overridden diff exists.
371//
372// This implementation differs from ResourceData's in the way that we first get
373// results from the exact levels for the new diff, then from state and diff as
374// per normal.
375func (d *ResourceDiff) GetChange(key string) (interface{}, interface{}) {
376	old, new, _ := d.getChange(key)
377	return old.Value, new.Value
378}
379
380// GetOk functions the same way as ResourceData.GetOk, but it also checks the
381// new diff levels to provide data consistent with the current state of the
382// customized diff.
383func (d *ResourceDiff) GetOk(key string) (interface{}, bool) {
384	r := d.get(strings.Split(key, "."), "newDiff")
385	exists := r.Exists && !r.Computed
386	if exists {
387		// If it exists, we also want to verify it is not the zero-value.
388		value := r.Value
389		zero := r.Schema.Type.Zero()
390
391		if eq, ok := value.(Equal); ok {
392			exists = !eq.Equal(zero)
393		} else {
394			exists = !reflect.DeepEqual(value, zero)
395		}
396	}
397
398	return r.Value, exists
399}
400
401// GetOkExists functions the same way as GetOkExists within ResourceData, but
402// it also checks the new diff levels to provide data consistent with the
403// current state of the customized diff.
404//
405// This is nearly the same function as GetOk, yet it does not check
406// for the zero value of the attribute's type. This allows for attributes
407// without a default, to fully check for a literal assignment, regardless
408// of the zero-value for that type.
409func (d *ResourceDiff) GetOkExists(key string) (interface{}, bool) {
410	r := d.get(strings.Split(key, "."), "newDiff")
411	exists := r.Exists && !r.Computed
412	return r.Value, exists
413}
414
415// NewValueKnown returns true if the new value for the given key is available
416// as its final value at diff time. If the return value is false, this means
417// either the value is based of interpolation that was unavailable at diff
418// time, or that the value was explicitly marked as computed by SetNewComputed.
419func (d *ResourceDiff) NewValueKnown(key string) bool {
420	r := d.get(strings.Split(key, "."), "newDiff")
421	return !r.Computed
422}
423
424// HasChange checks to see if there is a change between state and the diff, or
425// in the overridden diff.
426func (d *ResourceDiff) HasChange(key string) bool {
427	old, new := d.GetChange(key)
428
429	// If the type implements the Equal interface, then call that
430	// instead of just doing a reflect.DeepEqual. An example where this is
431	// needed is *Set
432	if eq, ok := old.(Equal); ok {
433		return !eq.Equal(new)
434	}
435
436	return !reflect.DeepEqual(old, new)
437}
438
439// Id returns the ID of this resource.
440//
441// Note that technically, ID does not change during diffs (it either has
442// already changed in the refresh, or will change on update), hence we do not
443// support updating the ID or fetching it from anything else other than state.
444func (d *ResourceDiff) Id() string {
445	var result string
446
447	if d.state != nil {
448		result = d.state.ID
449	}
450	return result
451}
452
453// getChange gets values from two different levels, designed for use in
454// diffChange, HasChange, and GetChange.
455//
456// This implementation differs from ResourceData's in the way that we first get
457// results from the exact levels for the new diff, then from state and diff as
458// per normal.
459func (d *ResourceDiff) getChange(key string) (getResult, getResult, bool) {
460	old := d.get(strings.Split(key, "."), "state")
461	var new getResult
462	for p := range d.updatedKeys {
463		if childAddrOf(key, p) {
464			new = d.getExact(strings.Split(key, "."), "newDiff")
465			return old, new, true
466		}
467	}
468	new = d.get(strings.Split(key, "."), "newDiff")
469	return old, new, false
470}
471
472// removed checks to see if the key is present in the existing, pre-customized
473// diff and if it was marked as NewRemoved.
474func (d *ResourceDiff) removed(k string) bool {
475	diff, ok := d.diff.Attributes[k]
476	if !ok {
477		return false
478	}
479	return diff.NewRemoved
480}
481
482// get performs the appropriate multi-level reader logic for ResourceDiff,
483// starting at source. Refer to newResourceDiff for the level order.
484func (d *ResourceDiff) get(addr []string, source string) getResult {
485	result, err := d.multiReader.ReadFieldMerge(addr, source)
486	if err != nil {
487		panic(err)
488	}
489
490	return d.finalizeResult(addr, result)
491}
492
493// getExact gets an attribute from the exact level referenced by source.
494func (d *ResourceDiff) getExact(addr []string, source string) getResult {
495	result, err := d.multiReader.ReadFieldExact(addr, source)
496	if err != nil {
497		panic(err)
498	}
499
500	return d.finalizeResult(addr, result)
501}
502
503// finalizeResult does some post-processing of the result produced by get and getExact.
504func (d *ResourceDiff) finalizeResult(addr []string, result FieldReadResult) getResult {
505	// If the result doesn't exist, then we set the value to the zero value
506	var schema *Schema
507	if schemaL := addrToSchema(addr, d.schema); len(schemaL) > 0 {
508		schema = schemaL[len(schemaL)-1]
509	}
510
511	if result.Value == nil && schema != nil {
512		result.Value = result.ValueOrZero(schema)
513	}
514
515	// Transform the FieldReadResult into a getResult. It might be worth
516	// merging these two structures one day.
517	return getResult{
518		Value:          result.Value,
519		ValueProcessed: result.ValueProcessed,
520		Computed:       result.Computed,
521		Exists:         result.Exists,
522		Schema:         schema,
523	}
524}
525
526// childAddrOf does a comparison of two addresses to see if one is the child of
527// the other.
528func childAddrOf(child, parent string) bool {
529	cs := strings.Split(child, ".")
530	ps := strings.Split(parent, ".")
531	if len(ps) > len(cs) {
532		return false
533	}
534	return reflect.DeepEqual(ps, cs[:len(ps)])
535}
536
537// checkKey checks the key to make sure it exists and is computed.
538func (d *ResourceDiff) checkKey(key, caller string, nested bool) error {
539	var schema *Schema
540	if nested {
541		keyParts := strings.Split(key, ".")
542		schemaL := addrToSchema(keyParts, d.schema)
543		if len(schemaL) > 0 {
544			schema = schemaL[len(schemaL)-1]
545		}
546	} else {
547		s, ok := d.schema[key]
548		if ok {
549			schema = s
550		}
551	}
552	if schema == nil {
553		return fmt.Errorf("%s: invalid key: %s", caller, key)
554	}
555	if !schema.Computed {
556		return fmt.Errorf("%s only operates on computed keys - %s is not one", caller, key)
557	}
558	return nil
559}
560