]> git.immae.eu Git - github/fretlink/terraform-provider-statuscake.git/blame - vendor/github.com/hashicorp/terraform/terraform/diff.go
Upgrade to 0.12
[github/fretlink/terraform-provider-statuscake.git] / vendor / github.com / hashicorp / terraform / terraform / diff.go
CommitLineData
bae9f6d2
JC
1package terraform
2
3import (
4 "bufio"
5 "bytes"
6 "fmt"
107c1cdb 7 "log"
bae9f6d2
JC
8 "reflect"
9 "regexp"
10 "sort"
107c1cdb 11 "strconv"
bae9f6d2
JC
12 "strings"
13 "sync"
14
107c1cdb
ND
15 "github.com/hashicorp/terraform/addrs"
16 "github.com/hashicorp/terraform/config"
17 "github.com/hashicorp/terraform/config/hcl2shim"
18 "github.com/hashicorp/terraform/configs/configschema"
19 "github.com/zclconf/go-cty/cty"
20
bae9f6d2
JC
21 "github.com/mitchellh/copystructure"
22)
23
24// DiffChangeType is an enum with the kind of changes a diff has planned.
25type DiffChangeType byte
26
27const (
28 DiffInvalid DiffChangeType = iota
29 DiffNone
30 DiffCreate
31 DiffUpdate
32 DiffDestroy
33 DiffDestroyCreate
15c0b25d
AP
34
35 // DiffRefresh is only used in the UI for displaying diffs.
36 // Managed resource reads never appear in plan, and when data source
37 // reads appear they are represented as DiffCreate in core before
38 // transforming to DiffRefresh in the UI layer.
39 DiffRefresh // TODO: Actually use DiffRefresh in core too, for less confusion
bae9f6d2
JC
40)
41
42// multiVal matches the index key to a flatmapped set, list or map
43var multiVal = regexp.MustCompile(`\.(#|%)$`)
44
c680a8e1 45// Diff tracks the changes that are necessary to apply a configuration
bae9f6d2
JC
46// to an existing infrastructure.
47type Diff struct {
48 // Modules contains all the modules that have a diff
49 Modules []*ModuleDiff
50}
51
52// Prune cleans out unused structures in the diff without affecting
53// the behavior of the diff at all.
54//
55// This is not safe to call concurrently. This is safe to call on a
56// nil Diff.
57func (d *Diff) Prune() {
58 if d == nil {
59 return
60 }
61
62 // Prune all empty modules
63 newModules := make([]*ModuleDiff, 0, len(d.Modules))
64 for _, m := range d.Modules {
65 // If the module isn't empty, we keep it
66 if !m.Empty() {
67 newModules = append(newModules, m)
68 }
69 }
70 if len(newModules) == 0 {
71 newModules = nil
72 }
73 d.Modules = newModules
74}
75
76// AddModule adds the module with the given path to the diff.
77//
78// This should be the preferred method to add module diffs since it
79// allows us to optimize lookups later as well as control sorting.
107c1cdb
ND
80func (d *Diff) AddModule(path addrs.ModuleInstance) *ModuleDiff {
81 // Lower the new-style address into a legacy-style address.
82 // This requires that none of the steps have instance keys, which is
83 // true for all addresses at the time of implementing this because
84 // "count" and "for_each" are not yet implemented for modules.
85 legacyPath := make([]string, len(path))
86 for i, step := range path {
87 if step.InstanceKey != addrs.NoKey {
88 // FIXME: Once the rest of Terraform is ready to use count and
89 // for_each, remove all of this and just write the addrs.ModuleInstance
90 // value itself into the ModuleState.
91 panic("diff cannot represent modules with count or for_each keys")
92 }
93
94 legacyPath[i] = step.Name
95 }
96
97 m := &ModuleDiff{Path: legacyPath}
bae9f6d2
JC
98 m.init()
99 d.Modules = append(d.Modules, m)
100 return m
101}
102
103// ModuleByPath is used to lookup the module diff for the given path.
104// This should be the preferred lookup mechanism as it allows for future
105// lookup optimizations.
107c1cdb 106func (d *Diff) ModuleByPath(path addrs.ModuleInstance) *ModuleDiff {
bae9f6d2
JC
107 if d == nil {
108 return nil
109 }
110 for _, mod := range d.Modules {
111 if mod.Path == nil {
112 panic("missing module path")
113 }
107c1cdb
ND
114 modPath := normalizeModulePath(mod.Path)
115 if modPath.String() == path.String() {
bae9f6d2
JC
116 return mod
117 }
118 }
119 return nil
120}
121
122// RootModule returns the ModuleState for the root module
123func (d *Diff) RootModule() *ModuleDiff {
107c1cdb 124 root := d.ModuleByPath(addrs.RootModuleInstance)
bae9f6d2
JC
125 if root == nil {
126 panic("missing root module")
127 }
128 return root
129}
130
131// Empty returns true if the diff has no changes.
132func (d *Diff) Empty() bool {
133 if d == nil {
134 return true
135 }
136
137 for _, m := range d.Modules {
138 if !m.Empty() {
139 return false
140 }
141 }
142
143 return true
144}
145
146// Equal compares two diffs for exact equality.
147//
148// This is different from the Same comparison that is supported which
149// checks for operation equality taking into account computed values. Equal
150// instead checks for exact equality.
151func (d *Diff) Equal(d2 *Diff) bool {
152 // If one is nil, they must both be nil
153 if d == nil || d2 == nil {
154 return d == d2
155 }
156
157 // Sort the modules
158 sort.Sort(moduleDiffSort(d.Modules))
159 sort.Sort(moduleDiffSort(d2.Modules))
160
161 // Copy since we have to modify the module destroy flag to false so
162 // we don't compare that. TODO: delete this when we get rid of the
163 // destroy flag on modules.
164 dCopy := d.DeepCopy()
165 d2Copy := d2.DeepCopy()
166 for _, m := range dCopy.Modules {
167 m.Destroy = false
168 }
169 for _, m := range d2Copy.Modules {
170 m.Destroy = false
171 }
172
173 // Use DeepEqual
174 return reflect.DeepEqual(dCopy, d2Copy)
175}
176
177// DeepCopy performs a deep copy of all parts of the Diff, making the
178// resulting Diff safe to use without modifying this one.
179func (d *Diff) DeepCopy() *Diff {
180 copy, err := copystructure.Config{Lock: true}.Copy(d)
181 if err != nil {
182 panic(err)
183 }
184
185 return copy.(*Diff)
186}
187
188func (d *Diff) String() string {
189 var buf bytes.Buffer
190
191 keys := make([]string, 0, len(d.Modules))
192 lookup := make(map[string]*ModuleDiff)
193 for _, m := range d.Modules {
107c1cdb
ND
194 addr := normalizeModulePath(m.Path)
195 key := addr.String()
bae9f6d2
JC
196 keys = append(keys, key)
197 lookup[key] = m
198 }
199 sort.Strings(keys)
200
201 for _, key := range keys {
202 m := lookup[key]
203 mStr := m.String()
204
205 // If we're the root module, we just write the output directly.
206 if reflect.DeepEqual(m.Path, rootModulePath) {
207 buf.WriteString(mStr + "\n")
208 continue
209 }
210
211 buf.WriteString(fmt.Sprintf("%s:\n", key))
212
213 s := bufio.NewScanner(strings.NewReader(mStr))
214 for s.Scan() {
215 buf.WriteString(fmt.Sprintf(" %s\n", s.Text()))
216 }
217 }
218
219 return strings.TrimSpace(buf.String())
220}
221
222func (d *Diff) init() {
223 if d.Modules == nil {
224 rootDiff := &ModuleDiff{Path: rootModulePath}
225 d.Modules = []*ModuleDiff{rootDiff}
226 }
227 for _, m := range d.Modules {
228 m.init()
229 }
230}
231
232// ModuleDiff tracks the differences between resources to apply within
233// a single module.
234type ModuleDiff struct {
235 Path []string
236 Resources map[string]*InstanceDiff
237 Destroy bool // Set only by the destroy plan
238}
239
240func (d *ModuleDiff) init() {
241 if d.Resources == nil {
242 d.Resources = make(map[string]*InstanceDiff)
243 }
244 for _, r := range d.Resources {
245 r.init()
246 }
247}
248
249// ChangeType returns the type of changes that the diff for this
250// module includes.
251//
252// At a module level, this will only be DiffNone, DiffUpdate, DiffDestroy, or
253// DiffCreate. If an instance within the module has a DiffDestroyCreate
254// then this will register as a DiffCreate for a module.
255func (d *ModuleDiff) ChangeType() DiffChangeType {
256 result := DiffNone
257 for _, r := range d.Resources {
258 change := r.ChangeType()
259 switch change {
260 case DiffCreate, DiffDestroy:
261 if result == DiffNone {
262 result = change
263 }
264 case DiffDestroyCreate, DiffUpdate:
265 result = DiffUpdate
266 }
267 }
268
269 return result
270}
271
272// Empty returns true if the diff has no changes within this module.
273func (d *ModuleDiff) Empty() bool {
274 if d.Destroy {
275 return false
276 }
277
278 if len(d.Resources) == 0 {
279 return true
280 }
281
282 for _, rd := range d.Resources {
283 if !rd.Empty() {
284 return false
285 }
286 }
287
288 return true
289}
290
291// Instances returns the instance diffs for the id given. This can return
292// multiple instance diffs if there are counts within the resource.
293func (d *ModuleDiff) Instances(id string) []*InstanceDiff {
294 var result []*InstanceDiff
295 for k, diff := range d.Resources {
296 if k == id || strings.HasPrefix(k, id+".") {
297 if !diff.Empty() {
298 result = append(result, diff)
299 }
300 }
301 }
302
303 return result
304}
305
306// IsRoot says whether or not this module diff is for the root module.
307func (d *ModuleDiff) IsRoot() bool {
308 return reflect.DeepEqual(d.Path, rootModulePath)
309}
310
311// String outputs the diff in a long but command-line friendly output
312// format that users can read to quickly inspect a diff.
313func (d *ModuleDiff) String() string {
314 var buf bytes.Buffer
315
316 names := make([]string, 0, len(d.Resources))
317 for name, _ := range d.Resources {
318 names = append(names, name)
319 }
320 sort.Strings(names)
321
322 for _, name := range names {
323 rdiff := d.Resources[name]
324
325 crud := "UPDATE"
326 switch {
327 case rdiff.RequiresNew() && (rdiff.GetDestroy() || rdiff.GetDestroyTainted()):
328 crud = "DESTROY/CREATE"
329 case rdiff.GetDestroy() || rdiff.GetDestroyDeposed():
330 crud = "DESTROY"
331 case rdiff.RequiresNew():
332 crud = "CREATE"
333 }
334
335 extra := ""
336 if !rdiff.GetDestroy() && rdiff.GetDestroyDeposed() {
337 extra = " (deposed only)"
338 }
339
340 buf.WriteString(fmt.Sprintf(
341 "%s: %s%s\n",
342 crud,
343 name,
344 extra))
345
346 keyLen := 0
347 rdiffAttrs := rdiff.CopyAttributes()
348 keys := make([]string, 0, len(rdiffAttrs))
349 for key, _ := range rdiffAttrs {
350 if key == "id" {
351 continue
352 }
353
354 keys = append(keys, key)
355 if len(key) > keyLen {
356 keyLen = len(key)
357 }
358 }
359 sort.Strings(keys)
360
361 for _, attrK := range keys {
362 attrDiff, _ := rdiff.GetAttribute(attrK)
363
364 v := attrDiff.New
365 u := attrDiff.Old
366 if attrDiff.NewComputed {
367 v = "<computed>"
368 }
369
370 if attrDiff.Sensitive {
371 u = "<sensitive>"
372 v = "<sensitive>"
373 }
374
375 updateMsg := ""
376 if attrDiff.RequiresNew {
377 updateMsg = " (forces new resource)"
378 } else if attrDiff.Sensitive {
379 updateMsg = " (attribute changed)"
380 }
381
382 buf.WriteString(fmt.Sprintf(
383 " %s:%s %#v => %#v%s\n",
384 attrK,
385 strings.Repeat(" ", keyLen-len(attrK)),
386 u,
387 v,
388 updateMsg))
389 }
390 }
391
392 return buf.String()
393}
394
395// InstanceDiff is the diff of a resource from some state to another.
396type InstanceDiff struct {
397 mu sync.Mutex
398 Attributes map[string]*ResourceAttrDiff
399 Destroy bool
400 DestroyDeposed bool
401 DestroyTainted bool
402
403 // Meta is a simple K/V map that is stored in a diff and persisted to
404 // plans but otherwise is completely ignored by Terraform core. It is
c680a8e1 405 // meant to be used for additional data a resource may want to pass through.
bae9f6d2
JC
406 // The value here must only contain Go primitives and collections.
407 Meta map[string]interface{}
408}
409
410func (d *InstanceDiff) Lock() { d.mu.Lock() }
411func (d *InstanceDiff) Unlock() { d.mu.Unlock() }
412
107c1cdb
ND
413// ApplyToValue merges the receiver into the given base value, returning a
414// new value that incorporates the planned changes. The given value must
415// conform to the given schema, or this method will panic.
416//
417// This method is intended for shimming old subsystems that still use this
418// legacy diff type to work with the new-style types.
419func (d *InstanceDiff) ApplyToValue(base cty.Value, schema *configschema.Block) (cty.Value, error) {
420 // Create an InstanceState attributes from our existing state.
421 // We can use this to more easily apply the diff changes.
422 attrs := hcl2shim.FlatmapValueFromHCL2(base)
423 applied, err := d.Apply(attrs, schema)
424 if err != nil {
425 return base, err
426 }
427
428 val, err := hcl2shim.HCL2ValueFromFlatmap(applied, schema.ImpliedType())
429 if err != nil {
430 return base, err
431 }
432
433 return schema.CoerceValue(val)
434}
435
436// Apply applies the diff to the provided flatmapped attributes,
437// returning the new instance attributes.
438//
439// This method is intended for shimming old subsystems that still use this
440// legacy diff type to work with the new-style types.
441func (d *InstanceDiff) Apply(attrs map[string]string, schema *configschema.Block) (map[string]string, error) {
442 // We always build a new value here, even if the given diff is "empty",
443 // because we might be planning to create a new instance that happens
444 // to have no attributes set, and so we want to produce an empty object
445 // rather than just echoing back the null old value.
446 if attrs == nil {
447 attrs = map[string]string{}
448 }
449
450 // Rather applying the diff to mutate the attrs, we'll copy new values into
451 // here to avoid the possibility of leaving stale values.
452 result := map[string]string{}
453
454 if d.Destroy || d.DestroyDeposed || d.DestroyTainted {
455 return result, nil
456 }
457
458 return d.applyBlockDiff(nil, attrs, schema)
459}
460
461func (d *InstanceDiff) applyBlockDiff(path []string, attrs map[string]string, schema *configschema.Block) (map[string]string, error) {
462 result := map[string]string{}
463 name := ""
464 if len(path) > 0 {
465 name = path[len(path)-1]
466 }
467
468 // localPrefix is used to build the local result map
469 localPrefix := ""
470 if name != "" {
471 localPrefix = name + "."
472 }
473
474 // iterate over the schema rather than the attributes, so we can handle
475 // different block types separately from plain attributes
476 for n, attrSchema := range schema.Attributes {
477 var err error
478 newAttrs, err := d.applyAttrDiff(append(path, n), attrs, attrSchema)
479
480 if err != nil {
481 return result, err
482 }
483
484 for k, v := range newAttrs {
485 result[localPrefix+k] = v
486 }
487 }
488
489 blockPrefix := strings.Join(path, ".")
490 if blockPrefix != "" {
491 blockPrefix += "."
492 }
493 for n, block := range schema.BlockTypes {
494 // we need to find the set of all keys that traverse this block
495 candidateKeys := map[string]bool{}
496 blockKey := blockPrefix + n + "."
497 localBlockPrefix := localPrefix + n + "."
498
499 // we can only trust the diff for sets, since the path changes, so don't
500 // count existing values as candidate keys. If it turns out we're
501 // keeping the attributes, we will catch it down below with "keepBlock"
502 // after we check the set count.
503 if block.Nesting != configschema.NestingSet {
504 for k := range attrs {
505 if strings.HasPrefix(k, blockKey) {
506 nextDot := strings.Index(k[len(blockKey):], ".")
507 if nextDot < 0 {
508 continue
509 }
510 nextDot += len(blockKey)
511 candidateKeys[k[len(blockKey):nextDot]] = true
512 }
513 }
514 }
515
516 for k, diff := range d.Attributes {
517 if strings.HasPrefix(k, blockKey) {
518 nextDot := strings.Index(k[len(blockKey):], ".")
519 if nextDot < 0 {
520 continue
521 }
522
523 if diff.NewRemoved {
524 continue
525 }
526
527 nextDot += len(blockKey)
528 candidateKeys[k[len(blockKey):nextDot]] = true
529 }
530 }
531
532 // check each set candidate to see if it was removed.
533 // we need to do this, because when entire sets are removed, they may
534 // have the wrong key, and ony show diffs going to ""
535 if block.Nesting == configschema.NestingSet {
536 for k := range candidateKeys {
537 indexPrefix := strings.Join(append(path, n, k), ".") + "."
538 keep := false
539 // now check each set element to see if it's a new diff, or one
540 // that we're dropping. Since we're only applying the "New"
541 // portion of the set, we can ignore diffs that only contain "Old"
542 for attr, diff := range d.Attributes {
543 if !strings.HasPrefix(attr, indexPrefix) {
544 continue
545 }
546
547 // check for empty "count" keys
548 if (strings.HasSuffix(attr, ".#") || strings.HasSuffix(attr, ".%")) && diff.New == "0" {
549 continue
550 }
551
552 // removed items don't count either
553 if diff.NewRemoved {
554 continue
555 }
556
557 // this must be a diff to keep
558 keep = true
559 break
560 }
561 if !keep {
562 delete(candidateKeys, k)
563 }
564 }
565 }
566
567 for k := range candidateKeys {
568 newAttrs, err := d.applyBlockDiff(append(path, n, k), attrs, &block.Block)
569 if err != nil {
570 return result, err
571 }
572
573 for attr, v := range newAttrs {
574 result[localBlockPrefix+attr] = v
575 }
576 }
577
578 keepBlock := true
579 // check this block's count diff directly first, since we may not
580 // have candidates because it was removed and only set to "0"
581 if diff, ok := d.Attributes[blockKey+"#"]; ok {
582 if diff.New == "0" || diff.NewRemoved {
583 keepBlock = false
584 }
585 }
586
587 // if there was no diff at all, then we need to keep the block attributes
588 if len(candidateKeys) == 0 && keepBlock {
589 for k, v := range attrs {
590 if strings.HasPrefix(k, blockKey) {
591 // we need the key relative to this block, so remove the
592 // entire prefix, then re-insert the block name.
593 localKey := localBlockPrefix + k[len(blockKey):]
594 result[localKey] = v
595 }
596 }
597 }
598
599 countAddr := strings.Join(append(path, n, "#"), ".")
600 if countDiff, ok := d.Attributes[countAddr]; ok {
601 if countDiff.NewComputed {
602 result[localBlockPrefix+"#"] = hcl2shim.UnknownVariableValue
603 } else {
604 result[localBlockPrefix+"#"] = countDiff.New
605
606 // While sets are complete, list are not, and we may not have all the
607 // information to track removals. If the list was truncated, we need to
608 // remove the extra items from the result.
609 if block.Nesting == configschema.NestingList &&
610 countDiff.New != "" && countDiff.New != hcl2shim.UnknownVariableValue {
611 length, _ := strconv.Atoi(countDiff.New)
612 for k := range result {
613 if !strings.HasPrefix(k, localBlockPrefix) {
614 continue
615 }
616
617 index := k[len(localBlockPrefix):]
618 nextDot := strings.Index(index, ".")
619 if nextDot < 1 {
620 continue
621 }
622 index = index[:nextDot]
623 i, err := strconv.Atoi(index)
624 if err != nil {
625 // this shouldn't happen since we added these
626 // ourself, but make note of it just in case.
627 log.Printf("[ERROR] bad list index in %q: %s", k, err)
628 continue
629 }
630 if i >= length {
631 delete(result, k)
632 }
633 }
634 }
635 }
636 } else if origCount, ok := attrs[countAddr]; ok && keepBlock {
637 result[localBlockPrefix+"#"] = origCount
638 } else {
639 result[localBlockPrefix+"#"] = countFlatmapContainerValues(localBlockPrefix+"#", result)
640 }
641 }
642
643 return result, nil
644}
645
646func (d *InstanceDiff) applyAttrDiff(path []string, attrs map[string]string, attrSchema *configschema.Attribute) (map[string]string, error) {
647 ty := attrSchema.Type
648 switch {
649 case ty.IsListType(), ty.IsTupleType(), ty.IsMapType():
650 return d.applyCollectionDiff(path, attrs, attrSchema)
651 case ty.IsSetType():
652 return d.applySetDiff(path, attrs, attrSchema)
653 default:
654 return d.applySingleAttrDiff(path, attrs, attrSchema)
655 }
656}
657
658func (d *InstanceDiff) applySingleAttrDiff(path []string, attrs map[string]string, attrSchema *configschema.Attribute) (map[string]string, error) {
659 currentKey := strings.Join(path, ".")
660
661 attr := path[len(path)-1]
662
663 result := map[string]string{}
664 diff := d.Attributes[currentKey]
665 old, exists := attrs[currentKey]
666
667 if diff != nil && diff.NewComputed {
668 result[attr] = config.UnknownVariableValue
669 return result, nil
670 }
671
672 // "id" must exist and not be an empty string, or it must be unknown.
673 // This only applied to top-level "id" fields.
674 if attr == "id" && len(path) == 1 {
675 if old == "" {
676 result[attr] = config.UnknownVariableValue
677 } else {
678 result[attr] = old
679 }
680 return result, nil
681 }
682
683 // attribute diffs are sometimes missed, so assume no diff means keep the
684 // old value
685 if diff == nil {
686 if exists {
687 result[attr] = old
688 } else {
689 // We need required values, so set those with an empty value. It
690 // must be set in the config, since if it were missing it would have
691 // failed validation.
692 if attrSchema.Required {
693 // we only set a missing string here, since bool or number types
694 // would have distinct zero value which shouldn't have been
695 // lost.
696 if attrSchema.Type == cty.String {
697 result[attr] = ""
698 }
699 }
700 }
701 return result, nil
702 }
703
704 // check for missmatched diff values
705 if exists &&
706 old != diff.Old &&
707 old != config.UnknownVariableValue &&
708 diff.Old != config.UnknownVariableValue {
709 return result, fmt.Errorf("diff apply conflict for %s: diff expects %q, but prior value has %q", attr, diff.Old, old)
710 }
711
712 if diff.NewRemoved {
713 // don't set anything in the new value
714 return map[string]string{}, nil
715 }
716
717 if diff.Old == diff.New && diff.New == "" {
718 // this can only be a valid empty string
719 if attrSchema.Type == cty.String {
720 result[attr] = ""
721 }
722 return result, nil
723 }
724
725 if attrSchema.Computed && diff.NewComputed {
726 result[attr] = config.UnknownVariableValue
727 return result, nil
728 }
729
730 result[attr] = diff.New
731
732 return result, nil
733}
734
735func (d *InstanceDiff) applyCollectionDiff(path []string, attrs map[string]string, attrSchema *configschema.Attribute) (map[string]string, error) {
736 result := map[string]string{}
737
738 prefix := ""
739 if len(path) > 1 {
740 prefix = strings.Join(path[:len(path)-1], ".") + "."
741 }
742
743 name := ""
744 if len(path) > 0 {
745 name = path[len(path)-1]
746 }
747
748 currentKey := prefix + name
749
750 // check the index first for special handling
751 for k, diff := range d.Attributes {
752 // check the index value, which can be set, and 0
753 if k == currentKey+".#" || k == currentKey+".%" || k == currentKey {
754 if diff.NewRemoved {
755 return result, nil
756 }
757
758 if diff.NewComputed {
759 result[k[len(prefix):]] = config.UnknownVariableValue
760 return result, nil
761 }
762
763 // do what the diff tells us to here, so that it's consistent with applies
764 if diff.New == "0" {
765 result[k[len(prefix):]] = "0"
766 return result, nil
767 }
768 }
769 }
770
771 // collect all the keys from the diff and the old state
772 noDiff := true
773 keys := map[string]bool{}
774 for k := range d.Attributes {
775 if !strings.HasPrefix(k, currentKey+".") {
776 continue
777 }
778 noDiff = false
779 keys[k] = true
780 }
781
782 noAttrs := true
783 for k := range attrs {
784 if !strings.HasPrefix(k, currentKey+".") {
785 continue
786 }
787 noAttrs = false
788 keys[k] = true
789 }
790
791 // If there's no diff and no attrs, then there's no value at all.
792 // This prevents an unexpected zero-count attribute in the attributes.
793 if noDiff && noAttrs {
794 return result, nil
795 }
796
797 idx := "#"
798 if attrSchema.Type.IsMapType() {
799 idx = "%"
800 }
801
802 for k := range keys {
803 // generate an schema placeholder for the values
804 elSchema := &configschema.Attribute{
805 Type: attrSchema.Type.ElementType(),
806 }
807
808 res, err := d.applySingleAttrDiff(append(path, k[len(currentKey)+1:]), attrs, elSchema)
809 if err != nil {
810 return result, err
811 }
812
813 for k, v := range res {
814 result[name+"."+k] = v
815 }
816 }
817
818 // Just like in nested list blocks, for simple lists we may need to fill in
819 // missing empty strings.
820 countKey := name + "." + idx
821 count := result[countKey]
822 length, _ := strconv.Atoi(count)
823
824 if count != "" && count != hcl2shim.UnknownVariableValue &&
825 attrSchema.Type.Equals(cty.List(cty.String)) {
826 // insert empty strings into missing indexes
827 for i := 0; i < length; i++ {
828 key := fmt.Sprintf("%s.%d", name, i)
829 if _, ok := result[key]; !ok {
830 result[key] = ""
831 }
832 }
833 }
834
835 // now check for truncation in any type of list
836 if attrSchema.Type.IsListType() {
837 for key := range result {
838 if key == countKey {
839 continue
840 }
841
842 if len(key) <= len(name)+1 {
843 // not sure what this is, but don't panic
844 continue
845 }
846
847 index := key[len(name)+1:]
848
849 // It is possible to have nested sets or maps, so look for another dot
850 dot := strings.Index(index, ".")
851 if dot > 0 {
852 index = index[:dot]
853 }
854
855 // This shouldn't have any more dots, since the element type is only string.
856 num, err := strconv.Atoi(index)
857 if err != nil {
858 log.Printf("[ERROR] bad list index in %q: %s", currentKey, err)
859 continue
860 }
861
862 if num >= length {
863 delete(result, key)
864 }
865 }
866 }
867
868 // Fill in the count value if it wasn't present in the diff for some reason,
869 // or if there is no count at all.
870 _, countDiff := d.Attributes[countKey]
871 if result[countKey] == "" || (!countDiff && len(keys) != len(result)) {
872 result[countKey] = countFlatmapContainerValues(countKey, result)
873 }
874
875 return result, nil
876}
877
878func (d *InstanceDiff) applySetDiff(path []string, attrs map[string]string, attrSchema *configschema.Attribute) (map[string]string, error) {
879 // We only need this special behavior for sets of object.
880 if !attrSchema.Type.ElementType().IsObjectType() {
881 // The normal collection apply behavior will work okay for this one, then.
882 return d.applyCollectionDiff(path, attrs, attrSchema)
883 }
884
885 // When we're dealing with a set of an object type we actually want to
886 // use our normal _block type_ apply behaviors, so we'll construct ourselves
887 // a synthetic schema that treats the object type as a block type and
888 // then delegate to our block apply method.
889 synthSchema := &configschema.Block{
890 Attributes: make(map[string]*configschema.Attribute),
891 }
892
893 for name, ty := range attrSchema.Type.ElementType().AttributeTypes() {
894 // We can safely make everything into an attribute here because in the
895 // event that there are nested set attributes we'll end up back in
896 // here again recursively and can then deal with the next level of
897 // expansion.
898 synthSchema.Attributes[name] = &configschema.Attribute{
899 Type: ty,
900 Optional: true,
901 }
902 }
903
904 parentPath := path[:len(path)-1]
905 childName := path[len(path)-1]
906 containerSchema := &configschema.Block{
907 BlockTypes: map[string]*configschema.NestedBlock{
908 childName: {
909 Nesting: configschema.NestingSet,
910 Block: *synthSchema,
911 },
912 },
913 }
914
915 return d.applyBlockDiff(parentPath, attrs, containerSchema)
916}
917
918// countFlatmapContainerValues returns the number of values in the flatmapped container
919// (set, map, list) indexed by key. The key argument is expected to include the
920// trailing ".#", or ".%".
921func countFlatmapContainerValues(key string, attrs map[string]string) string {
922 if len(key) < 3 || !(strings.HasSuffix(key, ".#") || strings.HasSuffix(key, ".%")) {
923 panic(fmt.Sprintf("invalid index value %q", key))
924 }
925
926 prefix := key[:len(key)-1]
927 items := map[string]int{}
928
929 for k := range attrs {
930 if k == key {
931 continue
932 }
933 if !strings.HasPrefix(k, prefix) {
934 continue
935 }
936
937 suffix := k[len(prefix):]
938 dot := strings.Index(suffix, ".")
939 if dot > 0 {
940 suffix = suffix[:dot]
941 }
942
943 items[suffix]++
944 }
945 return strconv.Itoa(len(items))
946}
947
bae9f6d2
JC
948// ResourceAttrDiff is the diff of a single attribute of a resource.
949type ResourceAttrDiff struct {
950 Old string // Old Value
951 New string // New Value
952 NewComputed bool // True if new value is computed (unknown currently)
953 NewRemoved bool // True if this attribute is being removed
954 NewExtra interface{} // Extra information for the provider
955 RequiresNew bool // True if change requires new resource
956 Sensitive bool // True if the data should not be displayed in UI output
957 Type DiffAttrType
958}
959
960// Empty returns true if the diff for this attr is neutral
961func (d *ResourceAttrDiff) Empty() bool {
962 return d.Old == d.New && !d.NewComputed && !d.NewRemoved
963}
964
965func (d *ResourceAttrDiff) GoString() string {
966 return fmt.Sprintf("*%#v", *d)
967}
968
969// DiffAttrType is an enum type that says whether a resource attribute
970// diff is an input attribute (comes from the configuration) or an
971// output attribute (comes as a result of applying the configuration). An
972// example input would be "ami" for AWS and an example output would be
973// "private_ip".
974type DiffAttrType byte
975
976const (
977 DiffAttrUnknown DiffAttrType = iota
978 DiffAttrInput
979 DiffAttrOutput
980)
981
982func (d *InstanceDiff) init() {
983 if d.Attributes == nil {
984 d.Attributes = make(map[string]*ResourceAttrDiff)
985 }
986}
987
988func NewInstanceDiff() *InstanceDiff {
989 return &InstanceDiff{Attributes: make(map[string]*ResourceAttrDiff)}
990}
991
992func (d *InstanceDiff) Copy() (*InstanceDiff, error) {
993 if d == nil {
994 return nil, nil
995 }
996
997 dCopy, err := copystructure.Config{Lock: true}.Copy(d)
998 if err != nil {
999 return nil, err
1000 }
1001
1002 return dCopy.(*InstanceDiff), nil
1003}
1004
1005// ChangeType returns the DiffChangeType represented by the diff
1006// for this single instance.
1007func (d *InstanceDiff) ChangeType() DiffChangeType {
1008 if d.Empty() {
1009 return DiffNone
1010 }
1011
1012 if d.RequiresNew() && (d.GetDestroy() || d.GetDestroyTainted()) {
1013 return DiffDestroyCreate
1014 }
1015
1016 if d.GetDestroy() || d.GetDestroyDeposed() {
1017 return DiffDestroy
1018 }
1019
1020 if d.RequiresNew() {
1021 return DiffCreate
1022 }
1023
1024 return DiffUpdate
1025}
1026
1027// Empty returns true if this diff encapsulates no changes.
1028func (d *InstanceDiff) Empty() bool {
1029 if d == nil {
1030 return true
1031 }
1032
1033 d.mu.Lock()
1034 defer d.mu.Unlock()
1035 return !d.Destroy &&
1036 !d.DestroyTainted &&
1037 !d.DestroyDeposed &&
1038 len(d.Attributes) == 0
1039}
1040
1041// Equal compares two diffs for exact equality.
1042//
1043// This is different from the Same comparison that is supported which
1044// checks for operation equality taking into account computed values. Equal
1045// instead checks for exact equality.
1046func (d *InstanceDiff) Equal(d2 *InstanceDiff) bool {
1047 // If one is nil, they must both be nil
1048 if d == nil || d2 == nil {
1049 return d == d2
1050 }
1051
1052 // Use DeepEqual
1053 return reflect.DeepEqual(d, d2)
1054}
1055
1056// DeepCopy performs a deep copy of all parts of the InstanceDiff
1057func (d *InstanceDiff) DeepCopy() *InstanceDiff {
1058 copy, err := copystructure.Config{Lock: true}.Copy(d)
1059 if err != nil {
1060 panic(err)
1061 }
1062
1063 return copy.(*InstanceDiff)
1064}
1065
1066func (d *InstanceDiff) GoString() string {
1067 return fmt.Sprintf("*%#v", InstanceDiff{
1068 Attributes: d.Attributes,
1069 Destroy: d.Destroy,
1070 DestroyTainted: d.DestroyTainted,
1071 DestroyDeposed: d.DestroyDeposed,
1072 })
1073}
1074
1075// RequiresNew returns true if the diff requires the creation of a new
1076// resource (implying the destruction of the old).
1077func (d *InstanceDiff) RequiresNew() bool {
1078 if d == nil {
1079 return false
1080 }
1081
1082 d.mu.Lock()
1083 defer d.mu.Unlock()
1084
1085 return d.requiresNew()
1086}
1087
1088func (d *InstanceDiff) requiresNew() bool {
1089 if d == nil {
1090 return false
1091 }
1092
1093 if d.DestroyTainted {
1094 return true
1095 }
1096
1097 for _, rd := range d.Attributes {
1098 if rd != nil && rd.RequiresNew {
1099 return true
1100 }
1101 }
1102
1103 return false
1104}
1105
1106func (d *InstanceDiff) GetDestroyDeposed() bool {
1107 d.mu.Lock()
1108 defer d.mu.Unlock()
1109
1110 return d.DestroyDeposed
1111}
1112
1113func (d *InstanceDiff) SetDestroyDeposed(b bool) {
1114 d.mu.Lock()
1115 defer d.mu.Unlock()
1116
1117 d.DestroyDeposed = b
1118}
1119
1120// These methods are properly locked, for use outside other InstanceDiff
c680a8e1 1121// methods but everywhere else within the terraform package.
bae9f6d2
JC
1122// TODO refactor the locking scheme
1123func (d *InstanceDiff) SetTainted(b bool) {
1124 d.mu.Lock()
1125 defer d.mu.Unlock()
1126
1127 d.DestroyTainted = b
1128}
1129
1130func (d *InstanceDiff) GetDestroyTainted() bool {
1131 d.mu.Lock()
1132 defer d.mu.Unlock()
1133
1134 return d.DestroyTainted
1135}
1136
1137func (d *InstanceDiff) SetDestroy(b bool) {
1138 d.mu.Lock()
1139 defer d.mu.Unlock()
1140
1141 d.Destroy = b
1142}
1143
1144func (d *InstanceDiff) GetDestroy() bool {
1145 d.mu.Lock()
1146 defer d.mu.Unlock()
1147
1148 return d.Destroy
1149}
1150
1151func (d *InstanceDiff) SetAttribute(key string, attr *ResourceAttrDiff) {
1152 d.mu.Lock()
1153 defer d.mu.Unlock()
1154
1155 d.Attributes[key] = attr
1156}
1157
1158func (d *InstanceDiff) DelAttribute(key string) {
1159 d.mu.Lock()
1160 defer d.mu.Unlock()
1161
1162 delete(d.Attributes, key)
1163}
1164
1165func (d *InstanceDiff) GetAttribute(key string) (*ResourceAttrDiff, bool) {
1166 d.mu.Lock()
1167 defer d.mu.Unlock()
1168
1169 attr, ok := d.Attributes[key]
1170 return attr, ok
1171}
1172func (d *InstanceDiff) GetAttributesLen() int {
1173 d.mu.Lock()
1174 defer d.mu.Unlock()
1175
1176 return len(d.Attributes)
1177}
1178
1179// Safely copies the Attributes map
1180func (d *InstanceDiff) CopyAttributes() map[string]*ResourceAttrDiff {
1181 d.mu.Lock()
1182 defer d.mu.Unlock()
1183
1184 attrs := make(map[string]*ResourceAttrDiff)
1185 for k, v := range d.Attributes {
1186 attrs[k] = v
1187 }
1188
1189 return attrs
1190}
1191
1192// Same checks whether or not two InstanceDiff's are the "same". When
1193// we say "same", it is not necessarily exactly equal. Instead, it is
1194// just checking that the same attributes are changing, a destroy
1195// isn't suddenly happening, etc.
1196func (d *InstanceDiff) Same(d2 *InstanceDiff) (bool, string) {
1197 // we can safely compare the pointers without a lock
1198 switch {
1199 case d == nil && d2 == nil:
1200 return true, ""
1201 case d == nil || d2 == nil:
1202 return false, "one nil"
1203 case d == d2:
1204 return true, ""
1205 }
1206
1207 d.mu.Lock()
1208 defer d.mu.Unlock()
1209
1210 // If we're going from requiring new to NOT requiring new, then we have
1211 // to see if all required news were computed. If so, it is allowed since
1212 // computed may also mean "same value and therefore not new".
1213 oldNew := d.requiresNew()
1214 newNew := d2.RequiresNew()
1215 if oldNew && !newNew {
1216 oldNew = false
1217
1218 // This section builds a list of ignorable attributes for requiresNew
1219 // by removing off any elements of collections going to zero elements.
1220 // For collections going to zero, they may not exist at all in the
1221 // new diff (and hence RequiresNew == false).
1222 ignoreAttrs := make(map[string]struct{})
1223 for k, diffOld := range d.Attributes {
1224 if !strings.HasSuffix(k, ".%") && !strings.HasSuffix(k, ".#") {
1225 continue
1226 }
1227
1228 // This case is in here as a protection measure. The bug that this
1229 // code originally fixed (GH-11349) didn't have to deal with computed
1230 // so I'm not 100% sure what the correct behavior is. Best to leave
1231 // the old behavior.
1232 if diffOld.NewComputed {
1233 continue
1234 }
1235
1236 // We're looking for the case a map goes to exactly 0.
1237 if diffOld.New != "0" {
1238 continue
1239 }
1240
1241 // Found it! Ignore all of these. The prefix here is stripping
1242 // off the "%" so it is just "k."
1243 prefix := k[:len(k)-1]
1244 for k2, _ := range d.Attributes {
1245 if strings.HasPrefix(k2, prefix) {
1246 ignoreAttrs[k2] = struct{}{}
1247 }
1248 }
1249 }
1250
1251 for k, rd := range d.Attributes {
1252 if _, ok := ignoreAttrs[k]; ok {
1253 continue
1254 }
1255
1256 // If the field is requires new and NOT computed, then what
1257 // we have is a diff mismatch for sure. We set that the old
1258 // diff does REQUIRE a ForceNew.
1259 if rd != nil && rd.RequiresNew && !rd.NewComputed {
1260 oldNew = true
1261 break
1262 }
1263 }
1264 }
1265
1266 if oldNew != newNew {
1267 return false, fmt.Sprintf(
1268 "diff RequiresNew; old: %t, new: %t", oldNew, newNew)
1269 }
1270
1271 // Verify that destroy matches. The second boolean here allows us to
1272 // have mismatching Destroy if we're moving from RequiresNew true
1273 // to false above. Therefore, the second boolean will only pass if
1274 // we're moving from Destroy: true to false as well.
1275 if d.Destroy != d2.GetDestroy() && d.requiresNew() == oldNew {
1276 return false, fmt.Sprintf(
1277 "diff: Destroy; old: %t, new: %t", d.Destroy, d2.GetDestroy())
1278 }
1279
1280 // Go through the old diff and make sure the new diff has all the
1281 // same attributes. To start, build up the check map to be all the keys.
1282 checkOld := make(map[string]struct{})
1283 checkNew := make(map[string]struct{})
1284 for k, _ := range d.Attributes {
1285 checkOld[k] = struct{}{}
1286 }
1287 for k, _ := range d2.CopyAttributes() {
1288 checkNew[k] = struct{}{}
1289 }
1290
1291 // Make an ordered list so we are sure the approximated hashes are left
1292 // to process at the end of the loop
1293 keys := make([]string, 0, len(d.Attributes))
1294 for k, _ := range d.Attributes {
1295 keys = append(keys, k)
1296 }
1297 sort.StringSlice(keys).Sort()
1298
1299 for _, k := range keys {
1300 diffOld := d.Attributes[k]
1301
1302 if _, ok := checkOld[k]; !ok {
1303 // We're not checking this key for whatever reason (see where
1304 // check is modified).
1305 continue
1306 }
1307
1308 // Remove this key since we'll never hit it again
1309 delete(checkOld, k)
1310 delete(checkNew, k)
1311
1312 _, ok := d2.GetAttribute(k)
1313 if !ok {
1314 // If there's no new attribute, and the old diff expected the attribute
1315 // to be removed, that's just fine.
1316 if diffOld.NewRemoved {
1317 continue
1318 }
1319
1320 // If the last diff was a computed value then the absense of
1321 // that value is allowed since it may mean the value ended up
1322 // being the same.
1323 if diffOld.NewComputed {
1324 ok = true
1325 }
1326
1327 // No exact match, but maybe this is a set containing computed
1328 // values. So check if there is an approximate hash in the key
1329 // and if so, try to match the key.
1330 if strings.Contains(k, "~") {
1331 parts := strings.Split(k, ".")
1332 parts2 := append([]string(nil), parts...)
1333
1334 re := regexp.MustCompile(`^~\d+$`)
1335 for i, part := range parts {
1336 if re.MatchString(part) {
1337 // we're going to consider this the base of a
1338 // computed hash, and remove all longer matching fields
1339 ok = true
1340
1341 parts2[i] = `\d+`
1342 parts2 = parts2[:i+1]
1343 break
1344 }
1345 }
1346
1347 re, err := regexp.Compile("^" + strings.Join(parts2, `\.`))
1348 if err != nil {
1349 return false, fmt.Sprintf("regexp failed to compile; err: %#v", err)
1350 }
1351
1352 for k2, _ := range checkNew {
1353 if re.MatchString(k2) {
1354 delete(checkNew, k2)
1355 }
1356 }
1357 }
1358
1359 // This is a little tricky, but when a diff contains a computed
1360 // list, set, or map that can only be interpolated after the apply
1361 // command has created the dependent resources, it could turn out
1362 // that the result is actually the same as the existing state which
1363 // would remove the key from the diff.
1364 if diffOld.NewComputed && (strings.HasSuffix(k, ".#") || strings.HasSuffix(k, ".%")) {
1365 ok = true
1366 }
1367
1368 // Similarly, in a RequiresNew scenario, a list that shows up in the plan
1369 // diff can disappear from the apply diff, which is calculated from an
1370 // empty state.
1371 if d.requiresNew() && (strings.HasSuffix(k, ".#") || strings.HasSuffix(k, ".%")) {
1372 ok = true
1373 }
1374
1375 if !ok {
1376 return false, fmt.Sprintf("attribute mismatch: %s", k)
1377 }
1378 }
1379
1380 // search for the suffix of the base of a [computed] map, list or set.
1381 match := multiVal.FindStringSubmatch(k)
1382
1383 if diffOld.NewComputed && len(match) == 2 {
1384 matchLen := len(match[1])
1385
1386 // This is a computed list, set, or map, so remove any keys with
1387 // this prefix from the check list.
1388 kprefix := k[:len(k)-matchLen]
1389 for k2, _ := range checkOld {
1390 if strings.HasPrefix(k2, kprefix) {
1391 delete(checkOld, k2)
1392 }
1393 }
1394 for k2, _ := range checkNew {
1395 if strings.HasPrefix(k2, kprefix) {
1396 delete(checkNew, k2)
1397 }
1398 }
1399 }
1400
15c0b25d
AP
1401 // We don't compare the values because we can't currently actually
1402 // guarantee to generate the same value two two diffs created from
1403 // the same state+config: we have some pesky interpolation functions
1404 // that do not behave as pure functions (uuid, timestamp) and so they
1405 // can be different each time a diff is produced.
1406 // FIXME: Re-organize our config handling so that we don't re-evaluate
1407 // expressions when we produce a second comparison diff during
1408 // apply (for EvalCompareDiff).
bae9f6d2
JC
1409 }
1410
1411 // Check for leftover attributes
1412 if len(checkNew) > 0 {
1413 extras := make([]string, 0, len(checkNew))
1414 for attr, _ := range checkNew {
1415 extras = append(extras, attr)
1416 }
1417 return false,
1418 fmt.Sprintf("extra attributes: %s", strings.Join(extras, ", "))
1419 }
1420
1421 return true, ""
1422}
1423
1424// moduleDiffSort implements sort.Interface to sort module diffs by path.
1425type moduleDiffSort []*ModuleDiff
1426
1427func (s moduleDiffSort) Len() int { return len(s) }
1428func (s moduleDiffSort) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
1429func (s moduleDiffSort) Less(i, j int) bool {
1430 a := s[i]
1431 b := s[j]
1432
1433 // If the lengths are different, then the shorter one always wins
1434 if len(a.Path) != len(b.Path) {
1435 return len(a.Path) < len(b.Path)
1436 }
1437
1438 // Otherwise, compare lexically
1439 return strings.Join(a.Path, ".") < strings.Join(b.Path, ".")
1440}