1package schema
2
3import (
4	"log"
5	"reflect"
6	"strings"
7	"sync"
8	"time"
9
10	"github.com/hashicorp/terraform-plugin-sdk/terraform"
11)
12
13// ResourceData is used to query and set the attributes of a resource.
14//
15// ResourceData is the primary argument received for CRUD operations on
16// a resource as well as configuration of a provider. It is a powerful
17// structure that can be used to not only query data, but check for changes,
18// define partial state updates, etc.
19//
20// The most relevant methods to take a look at are Get, Set, and Partial.
21type ResourceData struct {
22	// Settable (internally)
23	schema   map[string]*Schema
24	config   *terraform.ResourceConfig
25	state    *terraform.InstanceState
26	diff     *terraform.InstanceDiff
27	meta     map[string]interface{}
28	timeouts *ResourceTimeout
29
30	// Don't set
31	multiReader *MultiLevelFieldReader
32	setWriter   *MapFieldWriter
33	newState    *terraform.InstanceState
34	partial     bool
35	partialMap  map[string]struct{}
36	once        sync.Once
37	isNew       bool
38
39	panicOnError bool
40}
41
42// getResult is the internal structure that is generated when a Get
43// is called that contains some extra data that might be used.
44type getResult struct {
45	Value          interface{}
46	ValueProcessed interface{}
47	Computed       bool
48	Exists         bool
49	Schema         *Schema
50}
51
52// UnsafeSetFieldRaw allows setting arbitrary values in state to arbitrary
53// values, bypassing schema. This MUST NOT be used in normal circumstances -
54// it exists only to support the remote_state data source.
55//
56// Deprecated: Fully define schema attributes and use Set() instead.
57func (d *ResourceData) UnsafeSetFieldRaw(key string, value string) {
58	d.once.Do(d.init)
59
60	d.setWriter.unsafeWriteField(key, value)
61}
62
63// Get returns the data for the given key, or nil if the key doesn't exist
64// in the schema.
65//
66// If the key does exist in the schema but doesn't exist in the configuration,
67// then the default value for that type will be returned. For strings, this is
68// "", for numbers it is 0, etc.
69//
70// If you want to test if something is set at all in the configuration,
71// use GetOk.
72func (d *ResourceData) Get(key string) interface{} {
73	v, _ := d.GetOk(key)
74	return v
75}
76
77// GetChange returns the old and new value for a given key.
78//
79// HasChange should be used to check if a change exists. It is possible
80// that both the old and new value are the same if the old value was not
81// set and the new value is. This is common, for example, for boolean
82// fields which have a zero value of false.
83func (d *ResourceData) GetChange(key string) (interface{}, interface{}) {
84	o, n := d.getChange(key, getSourceState, getSourceDiff)
85	return o.Value, n.Value
86}
87
88// GetOk returns the data for the given key and whether or not the key
89// has been set to a non-zero value at some point.
90//
91// The first result will not necessarilly be nil if the value doesn't exist.
92// The second result should be checked to determine this information.
93func (d *ResourceData) GetOk(key string) (interface{}, bool) {
94	r := d.getRaw(key, getSourceSet)
95	exists := r.Exists && !r.Computed
96	if exists {
97		// If it exists, we also want to verify it is not the zero-value.
98		value := r.Value
99		zero := r.Schema.Type.Zero()
100
101		if eq, ok := value.(Equal); ok {
102			exists = !eq.Equal(zero)
103		} else {
104			exists = !reflect.DeepEqual(value, zero)
105		}
106	}
107
108	return r.Value, exists
109}
110
111// GetOkExists returns the data for a given key and whether or not the key
112// has been set to a non-zero value. This is only useful for determining
113// if boolean attributes have been set, if they are Optional but do not
114// have a Default value.
115//
116// This is nearly the same function as GetOk, yet it does not check
117// for the zero value of the attribute's type. This allows for attributes
118// without a default, to fully check for a literal assignment, regardless
119// of the zero-value for that type.
120// This should only be used if absolutely required/needed.
121func (d *ResourceData) GetOkExists(key string) (interface{}, bool) {
122	r := d.getRaw(key, getSourceSet)
123	exists := r.Exists && !r.Computed
124	return r.Value, exists
125}
126
127func (d *ResourceData) getRaw(key string, level getSource) getResult {
128	var parts []string
129	if key != "" {
130		parts = strings.Split(key, ".")
131	}
132
133	return d.get(parts, level)
134}
135
136// HasChange returns whether or not the given key has been changed.
137func (d *ResourceData) HasChange(key string) bool {
138	o, n := d.GetChange(key)
139
140	// If the type implements the Equal interface, then call that
141	// instead of just doing a reflect.DeepEqual. An example where this is
142	// needed is *Set
143	if eq, ok := o.(Equal); ok {
144		return !eq.Equal(n)
145	}
146
147	return !reflect.DeepEqual(o, n)
148}
149
150// Partial turns partial state mode on/off.
151//
152// When partial state mode is enabled, then only key prefixes specified
153// by SetPartial will be in the final state. This allows providers to return
154// partial states for partially applied resources (when errors occur).
155func (d *ResourceData) Partial(on bool) {
156	d.partial = on
157	if on {
158		if d.partialMap == nil {
159			d.partialMap = make(map[string]struct{})
160		}
161	} else {
162		d.partialMap = nil
163	}
164}
165
166// Set sets the value for the given key.
167//
168// If the key is invalid or the value is not a correct type, an error
169// will be returned.
170func (d *ResourceData) Set(key string, value interface{}) error {
171	d.once.Do(d.init)
172
173	// If the value is a pointer to a non-struct, get its value and
174	// use that. This allows Set to take a pointer to primitives to
175	// simplify the interface.
176	reflectVal := reflect.ValueOf(value)
177	if reflectVal.Kind() == reflect.Ptr {
178		if reflectVal.IsNil() {
179			// If the pointer is nil, then the value is just nil
180			value = nil
181		} else {
182			// Otherwise, we dereference the pointer as long as its not
183			// a pointer to a struct, since struct pointers are allowed.
184			reflectVal = reflect.Indirect(reflectVal)
185			if reflectVal.Kind() != reflect.Struct {
186				value = reflectVal.Interface()
187			}
188		}
189	}
190
191	err := d.setWriter.WriteField(strings.Split(key, "."), value)
192	if err != nil && d.panicOnError {
193		panic(err)
194	}
195	return err
196}
197
198// SetPartial adds the key to the final state output while
199// in partial state mode. The key must be a root key in the schema (i.e.
200// it cannot be "list.0").
201//
202// If partial state mode is disabled, then this has no effect. Additionally,
203// whenever partial state mode is toggled, the partial data is cleared.
204func (d *ResourceData) SetPartial(k string) {
205	if d.partial {
206		d.partialMap[k] = struct{}{}
207	}
208}
209
210func (d *ResourceData) MarkNewResource() {
211	d.isNew = true
212}
213
214func (d *ResourceData) IsNewResource() bool {
215	return d.isNew
216}
217
218// Id returns the ID of the resource.
219func (d *ResourceData) Id() string {
220	var result string
221
222	if d.state != nil {
223		result = d.state.ID
224		if result == "" {
225			result = d.state.Attributes["id"]
226		}
227	}
228
229	if d.newState != nil {
230		result = d.newState.ID
231		if result == "" {
232			result = d.newState.Attributes["id"]
233		}
234	}
235
236	return result
237}
238
239// ConnInfo returns the connection info for this resource.
240func (d *ResourceData) ConnInfo() map[string]string {
241	if d.newState != nil {
242		return d.newState.Ephemeral.ConnInfo
243	}
244
245	if d.state != nil {
246		return d.state.Ephemeral.ConnInfo
247	}
248
249	return nil
250}
251
252// SetId sets the ID of the resource. If the value is blank, then the
253// resource is destroyed.
254func (d *ResourceData) SetId(v string) {
255	d.once.Do(d.init)
256	d.newState.ID = v
257
258	// once we transition away from the legacy state types, "id" will no longer
259	// be a special field, and will become a normal attribute.
260	// set the attribute normally
261	d.setWriter.unsafeWriteField("id", v)
262
263	// Make sure the newState is also set, otherwise the old value
264	// may get precedence.
265	if d.newState.Attributes == nil {
266		d.newState.Attributes = map[string]string{}
267	}
268	d.newState.Attributes["id"] = v
269}
270
271// SetConnInfo sets the connection info for a resource.
272func (d *ResourceData) SetConnInfo(v map[string]string) {
273	d.once.Do(d.init)
274	d.newState.Ephemeral.ConnInfo = v
275}
276
277// SetType sets the ephemeral type for the data. This is only required
278// for importing.
279func (d *ResourceData) SetType(t string) {
280	d.once.Do(d.init)
281	d.newState.Ephemeral.Type = t
282}
283
284// State returns the new InstanceState after the diff and any Set
285// calls.
286func (d *ResourceData) State() *terraform.InstanceState {
287	var result terraform.InstanceState
288	result.ID = d.Id()
289	result.Meta = d.meta
290
291	// If we have no ID, then this resource doesn't exist and we just
292	// return nil.
293	if result.ID == "" {
294		return nil
295	}
296
297	if d.timeouts != nil {
298		if err := d.timeouts.StateEncode(&result); err != nil {
299			log.Printf("[ERR] Error encoding Timeout meta to Instance State: %s", err)
300		}
301	}
302
303	// Look for a magic key in the schema that determines we skip the
304	// integrity check of fields existing in the schema, allowing dynamic
305	// keys to be created.
306	hasDynamicAttributes := false
307	for k, _ := range d.schema {
308		if k == "__has_dynamic_attributes" {
309			hasDynamicAttributes = true
310			log.Printf("[INFO] Resource %s has dynamic attributes", result.ID)
311		}
312	}
313
314	// In order to build the final state attributes, we read the full
315	// attribute set as a map[string]interface{}, write it to a MapFieldWriter,
316	// and then use that map.
317	rawMap := make(map[string]interface{})
318	for k := range d.schema {
319		source := getSourceSet
320		if d.partial {
321			source = getSourceState
322			if _, ok := d.partialMap[k]; ok {
323				source = getSourceSet
324			}
325		}
326
327		raw := d.get([]string{k}, source)
328		if raw.Exists && !raw.Computed {
329			rawMap[k] = raw.Value
330			if raw.ValueProcessed != nil {
331				rawMap[k] = raw.ValueProcessed
332			}
333		}
334	}
335
336	mapW := &MapFieldWriter{Schema: d.schema}
337	if err := mapW.WriteField(nil, rawMap); err != nil {
338		log.Printf("[ERR] Error writing fields: %s", err)
339		return nil
340	}
341
342	result.Attributes = mapW.Map()
343
344	if hasDynamicAttributes {
345		// If we have dynamic attributes, just copy the attributes map
346		// one for one into the result attributes.
347		for k, v := range d.setWriter.Map() {
348			// Don't clobber schema values. This limits usage of dynamic
349			// attributes to names which _do not_ conflict with schema
350			// keys!
351			if _, ok := result.Attributes[k]; !ok {
352				result.Attributes[k] = v
353			}
354		}
355	}
356
357	if d.newState != nil {
358		result.Ephemeral = d.newState.Ephemeral
359	}
360
361	// TODO: This is hacky and we can remove this when we have a proper
362	// state writer. We should instead have a proper StateFieldWriter
363	// and use that.
364	for k, schema := range d.schema {
365		if schema.Type != TypeMap {
366			continue
367		}
368
369		if result.Attributes[k] == "" {
370			delete(result.Attributes, k)
371		}
372	}
373
374	if v := d.Id(); v != "" {
375		result.Attributes["id"] = d.Id()
376	}
377
378	if d.state != nil {
379		result.Tainted = d.state.Tainted
380	}
381
382	return &result
383}
384
385// Timeout returns the data for the given timeout key
386// Returns a duration of 20 minutes for any key not found, or not found and no default.
387func (d *ResourceData) Timeout(key string) time.Duration {
388	key = strings.ToLower(key)
389
390	// System default of 20 minutes
391	defaultTimeout := 20 * time.Minute
392
393	if d.timeouts == nil {
394		return defaultTimeout
395	}
396
397	var timeout *time.Duration
398	switch key {
399	case TimeoutCreate:
400		timeout = d.timeouts.Create
401	case TimeoutRead:
402		timeout = d.timeouts.Read
403	case TimeoutUpdate:
404		timeout = d.timeouts.Update
405	case TimeoutDelete:
406		timeout = d.timeouts.Delete
407	}
408
409	if timeout != nil {
410		return *timeout
411	}
412
413	if d.timeouts.Default != nil {
414		return *d.timeouts.Default
415	}
416
417	return defaultTimeout
418}
419
420func (d *ResourceData) init() {
421	// Initialize the field that will store our new state
422	var copyState terraform.InstanceState
423	if d.state != nil {
424		copyState = *d.state.DeepCopy()
425	}
426	d.newState = &copyState
427
428	// Initialize the map for storing set data
429	d.setWriter = &MapFieldWriter{Schema: d.schema}
430
431	// Initialize the reader for getting data from the
432	// underlying sources (config, diff, etc.)
433	readers := make(map[string]FieldReader)
434	var stateAttributes map[string]string
435	if d.state != nil {
436		stateAttributes = d.state.Attributes
437		readers["state"] = &MapFieldReader{
438			Schema: d.schema,
439			Map:    BasicMapReader(stateAttributes),
440		}
441	}
442	if d.config != nil {
443		readers["config"] = &ConfigFieldReader{
444			Schema: d.schema,
445			Config: d.config,
446		}
447	}
448	if d.diff != nil {
449		readers["diff"] = &DiffFieldReader{
450			Schema: d.schema,
451			Diff:   d.diff,
452			Source: &MultiLevelFieldReader{
453				Levels:  []string{"state", "config"},
454				Readers: readers,
455			},
456		}
457	}
458	readers["set"] = &MapFieldReader{
459		Schema: d.schema,
460		Map:    BasicMapReader(d.setWriter.Map()),
461	}
462	d.multiReader = &MultiLevelFieldReader{
463		Levels: []string{
464			"state",
465			"config",
466			"diff",
467			"set",
468		},
469
470		Readers: readers,
471	}
472}
473
474func (d *ResourceData) diffChange(
475	k string) (interface{}, interface{}, bool, bool, bool) {
476	// Get the change between the state and the config.
477	o, n := d.getChange(k, getSourceState, getSourceConfig|getSourceExact)
478	if !o.Exists {
479		o.Value = nil
480	}
481	if !n.Exists {
482		n.Value = nil
483	}
484
485	// Return the old, new, and whether there is a change
486	return o.Value, n.Value, !reflect.DeepEqual(o.Value, n.Value), n.Computed, false
487}
488
489func (d *ResourceData) getChange(
490	k string,
491	oldLevel getSource,
492	newLevel getSource) (getResult, getResult) {
493	var parts, parts2 []string
494	if k != "" {
495		parts = strings.Split(k, ".")
496		parts2 = strings.Split(k, ".")
497	}
498
499	o := d.get(parts, oldLevel)
500	n := d.get(parts2, newLevel)
501	return o, n
502}
503
504func (d *ResourceData) get(addr []string, source getSource) getResult {
505	d.once.Do(d.init)
506
507	level := "set"
508	flags := source & ^getSourceLevelMask
509	exact := flags&getSourceExact != 0
510	source = source & getSourceLevelMask
511	if source >= getSourceSet {
512		level = "set"
513	} else if source >= getSourceDiff {
514		level = "diff"
515	} else if source >= getSourceConfig {
516		level = "config"
517	} else {
518		level = "state"
519	}
520
521	var result FieldReadResult
522	var err error
523	if exact {
524		result, err = d.multiReader.ReadFieldExact(addr, level)
525	} else {
526		result, err = d.multiReader.ReadFieldMerge(addr, level)
527	}
528	if err != nil {
529		panic(err)
530	}
531
532	// If the result doesn't exist, then we set the value to the zero value
533	var schema *Schema
534	if schemaL := addrToSchema(addr, d.schema); len(schemaL) > 0 {
535		schema = schemaL[len(schemaL)-1]
536	}
537
538	if result.Value == nil && schema != nil {
539		result.Value = result.ValueOrZero(schema)
540	}
541
542	// Transform the FieldReadResult into a getResult. It might be worth
543	// merging these two structures one day.
544	return getResult{
545		Value:          result.Value,
546		ValueProcessed: result.ValueProcessed,
547		Computed:       result.Computed,
548		Exists:         result.Exists,
549		Schema:         schema,
550	}
551}
552