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