]> git.immae.eu Git - github/fretlink/terraform-provider-statuscake.git/blame - vendor/github.com/hashicorp/terraform/helper/schema/resource_diff.go
Upgrade to 0.12
[github/fretlink/terraform-provider-statuscake.git] / vendor / github.com / hashicorp / terraform / helper / schema / resource_diff.go
CommitLineData
15c0b25d
AP
1package schema
2
3import (
4 "errors"
5 "fmt"
6 "reflect"
7 "strings"
8 "sync"
9
10 "github.com/hashicorp/terraform/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
107c1cdb 370// if an overridden diff exists.
15c0b25d
AP
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}