]> git.immae.eu Git - github/fretlink/terraform-provider-statuscake.git/blob - vendor/github.com/hashicorp/terraform/terraform/diff.go
vendor: github.com/hashicorp/terraform/...@v0.10.0
[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 "reflect"
8 "regexp"
9 "sort"
10 "strings"
11 "sync"
12
13 "github.com/mitchellh/copystructure"
14 )
15
16 // DiffChangeType is an enum with the kind of changes a diff has planned.
17 type DiffChangeType byte
18
19 const (
20 DiffInvalid DiffChangeType = iota
21 DiffNone
22 DiffCreate
23 DiffUpdate
24 DiffDestroy
25 DiffDestroyCreate
26 )
27
28 // multiVal matches the index key to a flatmapped set, list or map
29 var multiVal = regexp.MustCompile(`\.(#|%)$`)
30
31 // Diff tracks the changes that are necessary to apply a configuration
32 // to an existing infrastructure.
33 type Diff struct {
34 // Modules contains all the modules that have a diff
35 Modules []*ModuleDiff
36 }
37
38 // Prune cleans out unused structures in the diff without affecting
39 // the behavior of the diff at all.
40 //
41 // This is not safe to call concurrently. This is safe to call on a
42 // nil Diff.
43 func (d *Diff) Prune() {
44 if d == nil {
45 return
46 }
47
48 // Prune all empty modules
49 newModules := make([]*ModuleDiff, 0, len(d.Modules))
50 for _, m := range d.Modules {
51 // If the module isn't empty, we keep it
52 if !m.Empty() {
53 newModules = append(newModules, m)
54 }
55 }
56 if len(newModules) == 0 {
57 newModules = nil
58 }
59 d.Modules = newModules
60 }
61
62 // AddModule adds the module with the given path to the diff.
63 //
64 // This should be the preferred method to add module diffs since it
65 // allows us to optimize lookups later as well as control sorting.
66 func (d *Diff) AddModule(path []string) *ModuleDiff {
67 m := &ModuleDiff{Path: path}
68 m.init()
69 d.Modules = append(d.Modules, m)
70 return m
71 }
72
73 // ModuleByPath is used to lookup the module diff for the given path.
74 // This should be the preferred lookup mechanism as it allows for future
75 // lookup optimizations.
76 func (d *Diff) ModuleByPath(path []string) *ModuleDiff {
77 if d == nil {
78 return nil
79 }
80 for _, mod := range d.Modules {
81 if mod.Path == nil {
82 panic("missing module path")
83 }
84 if reflect.DeepEqual(mod.Path, path) {
85 return mod
86 }
87 }
88 return nil
89 }
90
91 // RootModule returns the ModuleState for the root module
92 func (d *Diff) RootModule() *ModuleDiff {
93 root := d.ModuleByPath(rootModulePath)
94 if root == nil {
95 panic("missing root module")
96 }
97 return root
98 }
99
100 // Empty returns true if the diff has no changes.
101 func (d *Diff) Empty() bool {
102 if d == nil {
103 return true
104 }
105
106 for _, m := range d.Modules {
107 if !m.Empty() {
108 return false
109 }
110 }
111
112 return true
113 }
114
115 // Equal compares two diffs for exact equality.
116 //
117 // This is different from the Same comparison that is supported which
118 // checks for operation equality taking into account computed values. Equal
119 // instead checks for exact equality.
120 func (d *Diff) Equal(d2 *Diff) bool {
121 // If one is nil, they must both be nil
122 if d == nil || d2 == nil {
123 return d == d2
124 }
125
126 // Sort the modules
127 sort.Sort(moduleDiffSort(d.Modules))
128 sort.Sort(moduleDiffSort(d2.Modules))
129
130 // Copy since we have to modify the module destroy flag to false so
131 // we don't compare that. TODO: delete this when we get rid of the
132 // destroy flag on modules.
133 dCopy := d.DeepCopy()
134 d2Copy := d2.DeepCopy()
135 for _, m := range dCopy.Modules {
136 m.Destroy = false
137 }
138 for _, m := range d2Copy.Modules {
139 m.Destroy = false
140 }
141
142 // Use DeepEqual
143 return reflect.DeepEqual(dCopy, d2Copy)
144 }
145
146 // DeepCopy performs a deep copy of all parts of the Diff, making the
147 // resulting Diff safe to use without modifying this one.
148 func (d *Diff) DeepCopy() *Diff {
149 copy, err := copystructure.Config{Lock: true}.Copy(d)
150 if err != nil {
151 panic(err)
152 }
153
154 return copy.(*Diff)
155 }
156
157 func (d *Diff) String() string {
158 var buf bytes.Buffer
159
160 keys := make([]string, 0, len(d.Modules))
161 lookup := make(map[string]*ModuleDiff)
162 for _, m := range d.Modules {
163 key := fmt.Sprintf("module.%s", strings.Join(m.Path[1:], "."))
164 keys = append(keys, key)
165 lookup[key] = m
166 }
167 sort.Strings(keys)
168
169 for _, key := range keys {
170 m := lookup[key]
171 mStr := m.String()
172
173 // If we're the root module, we just write the output directly.
174 if reflect.DeepEqual(m.Path, rootModulePath) {
175 buf.WriteString(mStr + "\n")
176 continue
177 }
178
179 buf.WriteString(fmt.Sprintf("%s:\n", key))
180
181 s := bufio.NewScanner(strings.NewReader(mStr))
182 for s.Scan() {
183 buf.WriteString(fmt.Sprintf(" %s\n", s.Text()))
184 }
185 }
186
187 return strings.TrimSpace(buf.String())
188 }
189
190 func (d *Diff) init() {
191 if d.Modules == nil {
192 rootDiff := &ModuleDiff{Path: rootModulePath}
193 d.Modules = []*ModuleDiff{rootDiff}
194 }
195 for _, m := range d.Modules {
196 m.init()
197 }
198 }
199
200 // ModuleDiff tracks the differences between resources to apply within
201 // a single module.
202 type ModuleDiff struct {
203 Path []string
204 Resources map[string]*InstanceDiff
205 Destroy bool // Set only by the destroy plan
206 }
207
208 func (d *ModuleDiff) init() {
209 if d.Resources == nil {
210 d.Resources = make(map[string]*InstanceDiff)
211 }
212 for _, r := range d.Resources {
213 r.init()
214 }
215 }
216
217 // ChangeType returns the type of changes that the diff for this
218 // module includes.
219 //
220 // At a module level, this will only be DiffNone, DiffUpdate, DiffDestroy, or
221 // DiffCreate. If an instance within the module has a DiffDestroyCreate
222 // then this will register as a DiffCreate for a module.
223 func (d *ModuleDiff) ChangeType() DiffChangeType {
224 result := DiffNone
225 for _, r := range d.Resources {
226 change := r.ChangeType()
227 switch change {
228 case DiffCreate, DiffDestroy:
229 if result == DiffNone {
230 result = change
231 }
232 case DiffDestroyCreate, DiffUpdate:
233 result = DiffUpdate
234 }
235 }
236
237 return result
238 }
239
240 // Empty returns true if the diff has no changes within this module.
241 func (d *ModuleDiff) Empty() bool {
242 if d.Destroy {
243 return false
244 }
245
246 if len(d.Resources) == 0 {
247 return true
248 }
249
250 for _, rd := range d.Resources {
251 if !rd.Empty() {
252 return false
253 }
254 }
255
256 return true
257 }
258
259 // Instances returns the instance diffs for the id given. This can return
260 // multiple instance diffs if there are counts within the resource.
261 func (d *ModuleDiff) Instances(id string) []*InstanceDiff {
262 var result []*InstanceDiff
263 for k, diff := range d.Resources {
264 if k == id || strings.HasPrefix(k, id+".") {
265 if !diff.Empty() {
266 result = append(result, diff)
267 }
268 }
269 }
270
271 return result
272 }
273
274 // IsRoot says whether or not this module diff is for the root module.
275 func (d *ModuleDiff) IsRoot() bool {
276 return reflect.DeepEqual(d.Path, rootModulePath)
277 }
278
279 // String outputs the diff in a long but command-line friendly output
280 // format that users can read to quickly inspect a diff.
281 func (d *ModuleDiff) String() string {
282 var buf bytes.Buffer
283
284 names := make([]string, 0, len(d.Resources))
285 for name, _ := range d.Resources {
286 names = append(names, name)
287 }
288 sort.Strings(names)
289
290 for _, name := range names {
291 rdiff := d.Resources[name]
292
293 crud := "UPDATE"
294 switch {
295 case rdiff.RequiresNew() && (rdiff.GetDestroy() || rdiff.GetDestroyTainted()):
296 crud = "DESTROY/CREATE"
297 case rdiff.GetDestroy() || rdiff.GetDestroyDeposed():
298 crud = "DESTROY"
299 case rdiff.RequiresNew():
300 crud = "CREATE"
301 }
302
303 extra := ""
304 if !rdiff.GetDestroy() && rdiff.GetDestroyDeposed() {
305 extra = " (deposed only)"
306 }
307
308 buf.WriteString(fmt.Sprintf(
309 "%s: %s%s\n",
310 crud,
311 name,
312 extra))
313
314 keyLen := 0
315 rdiffAttrs := rdiff.CopyAttributes()
316 keys := make([]string, 0, len(rdiffAttrs))
317 for key, _ := range rdiffAttrs {
318 if key == "id" {
319 continue
320 }
321
322 keys = append(keys, key)
323 if len(key) > keyLen {
324 keyLen = len(key)
325 }
326 }
327 sort.Strings(keys)
328
329 for _, attrK := range keys {
330 attrDiff, _ := rdiff.GetAttribute(attrK)
331
332 v := attrDiff.New
333 u := attrDiff.Old
334 if attrDiff.NewComputed {
335 v = "<computed>"
336 }
337
338 if attrDiff.Sensitive {
339 u = "<sensitive>"
340 v = "<sensitive>"
341 }
342
343 updateMsg := ""
344 if attrDiff.RequiresNew {
345 updateMsg = " (forces new resource)"
346 } else if attrDiff.Sensitive {
347 updateMsg = " (attribute changed)"
348 }
349
350 buf.WriteString(fmt.Sprintf(
351 " %s:%s %#v => %#v%s\n",
352 attrK,
353 strings.Repeat(" ", keyLen-len(attrK)),
354 u,
355 v,
356 updateMsg))
357 }
358 }
359
360 return buf.String()
361 }
362
363 // InstanceDiff is the diff of a resource from some state to another.
364 type InstanceDiff struct {
365 mu sync.Mutex
366 Attributes map[string]*ResourceAttrDiff
367 Destroy bool
368 DestroyDeposed bool
369 DestroyTainted bool
370
371 // Meta is a simple K/V map that is stored in a diff and persisted to
372 // plans but otherwise is completely ignored by Terraform core. It is
373 // meant to be used for additional data a resource may want to pass through.
374 // The value here must only contain Go primitives and collections.
375 Meta map[string]interface{}
376 }
377
378 func (d *InstanceDiff) Lock() { d.mu.Lock() }
379 func (d *InstanceDiff) Unlock() { d.mu.Unlock() }
380
381 // ResourceAttrDiff is the diff of a single attribute of a resource.
382 type ResourceAttrDiff struct {
383 Old string // Old Value
384 New string // New Value
385 NewComputed bool // True if new value is computed (unknown currently)
386 NewRemoved bool // True if this attribute is being removed
387 NewExtra interface{} // Extra information for the provider
388 RequiresNew bool // True if change requires new resource
389 Sensitive bool // True if the data should not be displayed in UI output
390 Type DiffAttrType
391 }
392
393 // Empty returns true if the diff for this attr is neutral
394 func (d *ResourceAttrDiff) Empty() bool {
395 return d.Old == d.New && !d.NewComputed && !d.NewRemoved
396 }
397
398 func (d *ResourceAttrDiff) GoString() string {
399 return fmt.Sprintf("*%#v", *d)
400 }
401
402 // DiffAttrType is an enum type that says whether a resource attribute
403 // diff is an input attribute (comes from the configuration) or an
404 // output attribute (comes as a result of applying the configuration). An
405 // example input would be "ami" for AWS and an example output would be
406 // "private_ip".
407 type DiffAttrType byte
408
409 const (
410 DiffAttrUnknown DiffAttrType = iota
411 DiffAttrInput
412 DiffAttrOutput
413 )
414
415 func (d *InstanceDiff) init() {
416 if d.Attributes == nil {
417 d.Attributes = make(map[string]*ResourceAttrDiff)
418 }
419 }
420
421 func NewInstanceDiff() *InstanceDiff {
422 return &InstanceDiff{Attributes: make(map[string]*ResourceAttrDiff)}
423 }
424
425 func (d *InstanceDiff) Copy() (*InstanceDiff, error) {
426 if d == nil {
427 return nil, nil
428 }
429
430 dCopy, err := copystructure.Config{Lock: true}.Copy(d)
431 if err != nil {
432 return nil, err
433 }
434
435 return dCopy.(*InstanceDiff), nil
436 }
437
438 // ChangeType returns the DiffChangeType represented by the diff
439 // for this single instance.
440 func (d *InstanceDiff) ChangeType() DiffChangeType {
441 if d.Empty() {
442 return DiffNone
443 }
444
445 if d.RequiresNew() && (d.GetDestroy() || d.GetDestroyTainted()) {
446 return DiffDestroyCreate
447 }
448
449 if d.GetDestroy() || d.GetDestroyDeposed() {
450 return DiffDestroy
451 }
452
453 if d.RequiresNew() {
454 return DiffCreate
455 }
456
457 return DiffUpdate
458 }
459
460 // Empty returns true if this diff encapsulates no changes.
461 func (d *InstanceDiff) Empty() bool {
462 if d == nil {
463 return true
464 }
465
466 d.mu.Lock()
467 defer d.mu.Unlock()
468 return !d.Destroy &&
469 !d.DestroyTainted &&
470 !d.DestroyDeposed &&
471 len(d.Attributes) == 0
472 }
473
474 // Equal compares two diffs for exact equality.
475 //
476 // This is different from the Same comparison that is supported which
477 // checks for operation equality taking into account computed values. Equal
478 // instead checks for exact equality.
479 func (d *InstanceDiff) Equal(d2 *InstanceDiff) bool {
480 // If one is nil, they must both be nil
481 if d == nil || d2 == nil {
482 return d == d2
483 }
484
485 // Use DeepEqual
486 return reflect.DeepEqual(d, d2)
487 }
488
489 // DeepCopy performs a deep copy of all parts of the InstanceDiff
490 func (d *InstanceDiff) DeepCopy() *InstanceDiff {
491 copy, err := copystructure.Config{Lock: true}.Copy(d)
492 if err != nil {
493 panic(err)
494 }
495
496 return copy.(*InstanceDiff)
497 }
498
499 func (d *InstanceDiff) GoString() string {
500 return fmt.Sprintf("*%#v", InstanceDiff{
501 Attributes: d.Attributes,
502 Destroy: d.Destroy,
503 DestroyTainted: d.DestroyTainted,
504 DestroyDeposed: d.DestroyDeposed,
505 })
506 }
507
508 // RequiresNew returns true if the diff requires the creation of a new
509 // resource (implying the destruction of the old).
510 func (d *InstanceDiff) RequiresNew() bool {
511 if d == nil {
512 return false
513 }
514
515 d.mu.Lock()
516 defer d.mu.Unlock()
517
518 return d.requiresNew()
519 }
520
521 func (d *InstanceDiff) requiresNew() bool {
522 if d == nil {
523 return false
524 }
525
526 if d.DestroyTainted {
527 return true
528 }
529
530 for _, rd := range d.Attributes {
531 if rd != nil && rd.RequiresNew {
532 return true
533 }
534 }
535
536 return false
537 }
538
539 func (d *InstanceDiff) GetDestroyDeposed() bool {
540 d.mu.Lock()
541 defer d.mu.Unlock()
542
543 return d.DestroyDeposed
544 }
545
546 func (d *InstanceDiff) SetDestroyDeposed(b bool) {
547 d.mu.Lock()
548 defer d.mu.Unlock()
549
550 d.DestroyDeposed = b
551 }
552
553 // These methods are properly locked, for use outside other InstanceDiff
554 // methods but everywhere else within the terraform package.
555 // TODO refactor the locking scheme
556 func (d *InstanceDiff) SetTainted(b bool) {
557 d.mu.Lock()
558 defer d.mu.Unlock()
559
560 d.DestroyTainted = b
561 }
562
563 func (d *InstanceDiff) GetDestroyTainted() bool {
564 d.mu.Lock()
565 defer d.mu.Unlock()
566
567 return d.DestroyTainted
568 }
569
570 func (d *InstanceDiff) SetDestroy(b bool) {
571 d.mu.Lock()
572 defer d.mu.Unlock()
573
574 d.Destroy = b
575 }
576
577 func (d *InstanceDiff) GetDestroy() bool {
578 d.mu.Lock()
579 defer d.mu.Unlock()
580
581 return d.Destroy
582 }
583
584 func (d *InstanceDiff) SetAttribute(key string, attr *ResourceAttrDiff) {
585 d.mu.Lock()
586 defer d.mu.Unlock()
587
588 d.Attributes[key] = attr
589 }
590
591 func (d *InstanceDiff) DelAttribute(key string) {
592 d.mu.Lock()
593 defer d.mu.Unlock()
594
595 delete(d.Attributes, key)
596 }
597
598 func (d *InstanceDiff) GetAttribute(key string) (*ResourceAttrDiff, bool) {
599 d.mu.Lock()
600 defer d.mu.Unlock()
601
602 attr, ok := d.Attributes[key]
603 return attr, ok
604 }
605 func (d *InstanceDiff) GetAttributesLen() int {
606 d.mu.Lock()
607 defer d.mu.Unlock()
608
609 return len(d.Attributes)
610 }
611
612 // Safely copies the Attributes map
613 func (d *InstanceDiff) CopyAttributes() map[string]*ResourceAttrDiff {
614 d.mu.Lock()
615 defer d.mu.Unlock()
616
617 attrs := make(map[string]*ResourceAttrDiff)
618 for k, v := range d.Attributes {
619 attrs[k] = v
620 }
621
622 return attrs
623 }
624
625 // Same checks whether or not two InstanceDiff's are the "same". When
626 // we say "same", it is not necessarily exactly equal. Instead, it is
627 // just checking that the same attributes are changing, a destroy
628 // isn't suddenly happening, etc.
629 func (d *InstanceDiff) Same(d2 *InstanceDiff) (bool, string) {
630 // we can safely compare the pointers without a lock
631 switch {
632 case d == nil && d2 == nil:
633 return true, ""
634 case d == nil || d2 == nil:
635 return false, "one nil"
636 case d == d2:
637 return true, ""
638 }
639
640 d.mu.Lock()
641 defer d.mu.Unlock()
642
643 // If we're going from requiring new to NOT requiring new, then we have
644 // to see if all required news were computed. If so, it is allowed since
645 // computed may also mean "same value and therefore not new".
646 oldNew := d.requiresNew()
647 newNew := d2.RequiresNew()
648 if oldNew && !newNew {
649 oldNew = false
650
651 // This section builds a list of ignorable attributes for requiresNew
652 // by removing off any elements of collections going to zero elements.
653 // For collections going to zero, they may not exist at all in the
654 // new diff (and hence RequiresNew == false).
655 ignoreAttrs := make(map[string]struct{})
656 for k, diffOld := range d.Attributes {
657 if !strings.HasSuffix(k, ".%") && !strings.HasSuffix(k, ".#") {
658 continue
659 }
660
661 // This case is in here as a protection measure. The bug that this
662 // code originally fixed (GH-11349) didn't have to deal with computed
663 // so I'm not 100% sure what the correct behavior is. Best to leave
664 // the old behavior.
665 if diffOld.NewComputed {
666 continue
667 }
668
669 // We're looking for the case a map goes to exactly 0.
670 if diffOld.New != "0" {
671 continue
672 }
673
674 // Found it! Ignore all of these. The prefix here is stripping
675 // off the "%" so it is just "k."
676 prefix := k[:len(k)-1]
677 for k2, _ := range d.Attributes {
678 if strings.HasPrefix(k2, prefix) {
679 ignoreAttrs[k2] = struct{}{}
680 }
681 }
682 }
683
684 for k, rd := range d.Attributes {
685 if _, ok := ignoreAttrs[k]; ok {
686 continue
687 }
688
689 // If the field is requires new and NOT computed, then what
690 // we have is a diff mismatch for sure. We set that the old
691 // diff does REQUIRE a ForceNew.
692 if rd != nil && rd.RequiresNew && !rd.NewComputed {
693 oldNew = true
694 break
695 }
696 }
697 }
698
699 if oldNew != newNew {
700 return false, fmt.Sprintf(
701 "diff RequiresNew; old: %t, new: %t", oldNew, newNew)
702 }
703
704 // Verify that destroy matches. The second boolean here allows us to
705 // have mismatching Destroy if we're moving from RequiresNew true
706 // to false above. Therefore, the second boolean will only pass if
707 // we're moving from Destroy: true to false as well.
708 if d.Destroy != d2.GetDestroy() && d.requiresNew() == oldNew {
709 return false, fmt.Sprintf(
710 "diff: Destroy; old: %t, new: %t", d.Destroy, d2.GetDestroy())
711 }
712
713 // Go through the old diff and make sure the new diff has all the
714 // same attributes. To start, build up the check map to be all the keys.
715 checkOld := make(map[string]struct{})
716 checkNew := make(map[string]struct{})
717 for k, _ := range d.Attributes {
718 checkOld[k] = struct{}{}
719 }
720 for k, _ := range d2.CopyAttributes() {
721 checkNew[k] = struct{}{}
722 }
723
724 // Make an ordered list so we are sure the approximated hashes are left
725 // to process at the end of the loop
726 keys := make([]string, 0, len(d.Attributes))
727 for k, _ := range d.Attributes {
728 keys = append(keys, k)
729 }
730 sort.StringSlice(keys).Sort()
731
732 for _, k := range keys {
733 diffOld := d.Attributes[k]
734
735 if _, ok := checkOld[k]; !ok {
736 // We're not checking this key for whatever reason (see where
737 // check is modified).
738 continue
739 }
740
741 // Remove this key since we'll never hit it again
742 delete(checkOld, k)
743 delete(checkNew, k)
744
745 _, ok := d2.GetAttribute(k)
746 if !ok {
747 // If there's no new attribute, and the old diff expected the attribute
748 // to be removed, that's just fine.
749 if diffOld.NewRemoved {
750 continue
751 }
752
753 // If the last diff was a computed value then the absense of
754 // that value is allowed since it may mean the value ended up
755 // being the same.
756 if diffOld.NewComputed {
757 ok = true
758 }
759
760 // No exact match, but maybe this is a set containing computed
761 // values. So check if there is an approximate hash in the key
762 // and if so, try to match the key.
763 if strings.Contains(k, "~") {
764 parts := strings.Split(k, ".")
765 parts2 := append([]string(nil), parts...)
766
767 re := regexp.MustCompile(`^~\d+$`)
768 for i, part := range parts {
769 if re.MatchString(part) {
770 // we're going to consider this the base of a
771 // computed hash, and remove all longer matching fields
772 ok = true
773
774 parts2[i] = `\d+`
775 parts2 = parts2[:i+1]
776 break
777 }
778 }
779
780 re, err := regexp.Compile("^" + strings.Join(parts2, `\.`))
781 if err != nil {
782 return false, fmt.Sprintf("regexp failed to compile; err: %#v", err)
783 }
784
785 for k2, _ := range checkNew {
786 if re.MatchString(k2) {
787 delete(checkNew, k2)
788 }
789 }
790 }
791
792 // This is a little tricky, but when a diff contains a computed
793 // list, set, or map that can only be interpolated after the apply
794 // command has created the dependent resources, it could turn out
795 // that the result is actually the same as the existing state which
796 // would remove the key from the diff.
797 if diffOld.NewComputed && (strings.HasSuffix(k, ".#") || strings.HasSuffix(k, ".%")) {
798 ok = true
799 }
800
801 // Similarly, in a RequiresNew scenario, a list that shows up in the plan
802 // diff can disappear from the apply diff, which is calculated from an
803 // empty state.
804 if d.requiresNew() && (strings.HasSuffix(k, ".#") || strings.HasSuffix(k, ".%")) {
805 ok = true
806 }
807
808 if !ok {
809 return false, fmt.Sprintf("attribute mismatch: %s", k)
810 }
811 }
812
813 // search for the suffix of the base of a [computed] map, list or set.
814 match := multiVal.FindStringSubmatch(k)
815
816 if diffOld.NewComputed && len(match) == 2 {
817 matchLen := len(match[1])
818
819 // This is a computed list, set, or map, so remove any keys with
820 // this prefix from the check list.
821 kprefix := k[:len(k)-matchLen]
822 for k2, _ := range checkOld {
823 if strings.HasPrefix(k2, kprefix) {
824 delete(checkOld, k2)
825 }
826 }
827 for k2, _ := range checkNew {
828 if strings.HasPrefix(k2, kprefix) {
829 delete(checkNew, k2)
830 }
831 }
832 }
833
834 // TODO: check for the same value if not computed
835 }
836
837 // Check for leftover attributes
838 if len(checkNew) > 0 {
839 extras := make([]string, 0, len(checkNew))
840 for attr, _ := range checkNew {
841 extras = append(extras, attr)
842 }
843 return false,
844 fmt.Sprintf("extra attributes: %s", strings.Join(extras, ", "))
845 }
846
847 return true, ""
848 }
849
850 // moduleDiffSort implements sort.Interface to sort module diffs by path.
851 type moduleDiffSort []*ModuleDiff
852
853 func (s moduleDiffSort) Len() int { return len(s) }
854 func (s moduleDiffSort) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
855 func (s moduleDiffSort) Less(i, j int) bool {
856 a := s[i]
857 b := s[j]
858
859 // If the lengths are different, then the shorter one always wins
860 if len(a.Path) != len(b.Path) {
861 return len(a.Path) < len(b.Path)
862 }
863
864 // Otherwise, compare lexically
865 return strings.Join(a.Path, ".") < strings.Join(b.Path, ".")
866 }