aboutsummaryrefslogtreecommitdiffhomepage
path: root/vendor/github.com/hashicorp/terraform/terraform/diff.go
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/github.com/hashicorp/terraform/terraform/diff.go')
-rw-r--r--vendor/github.com/hashicorp/terraform/terraform/diff.go573
1 files changed, 567 insertions, 6 deletions
diff --git a/vendor/github.com/hashicorp/terraform/terraform/diff.go b/vendor/github.com/hashicorp/terraform/terraform/diff.go
index d6dc550..7a6ef3d 100644
--- a/vendor/github.com/hashicorp/terraform/terraform/diff.go
+++ b/vendor/github.com/hashicorp/terraform/terraform/diff.go
@@ -4,12 +4,20 @@ import (
4 "bufio" 4 "bufio"
5 "bytes" 5 "bytes"
6 "fmt" 6 "fmt"
7 "log"
7 "reflect" 8 "reflect"
8 "regexp" 9 "regexp"
9 "sort" 10 "sort"
11 "strconv"
10 "strings" 12 "strings"
11 "sync" 13 "sync"
12 14
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
13 "github.com/mitchellh/copystructure" 21 "github.com/mitchellh/copystructure"
14) 22)
15 23
@@ -69,8 +77,24 @@ func (d *Diff) Prune() {
69// 77//
70// This should be the preferred method to add module diffs since it 78// This should be the preferred method to add module diffs since it
71// allows us to optimize lookups later as well as control sorting. 79// allows us to optimize lookups later as well as control sorting.
72func (d *Diff) AddModule(path []string) *ModuleDiff { 80func (d *Diff) AddModule(path addrs.ModuleInstance) *ModuleDiff {
73 m := &ModuleDiff{Path: path} 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}
74 m.init() 98 m.init()
75 d.Modules = append(d.Modules, m) 99 d.Modules = append(d.Modules, m)
76 return m 100 return m
@@ -79,7 +103,7 @@ func (d *Diff) AddModule(path []string) *ModuleDiff {
79// ModuleByPath is used to lookup the module diff for the given path. 103// ModuleByPath is used to lookup the module diff for the given path.
80// This should be the preferred lookup mechanism as it allows for future 104// This should be the preferred lookup mechanism as it allows for future
81// lookup optimizations. 105// lookup optimizations.
82func (d *Diff) ModuleByPath(path []string) *ModuleDiff { 106func (d *Diff) ModuleByPath(path addrs.ModuleInstance) *ModuleDiff {
83 if d == nil { 107 if d == nil {
84 return nil 108 return nil
85 } 109 }
@@ -87,7 +111,8 @@ func (d *Diff) ModuleByPath(path []string) *ModuleDiff {
87 if mod.Path == nil { 111 if mod.Path == nil {
88 panic("missing module path") 112 panic("missing module path")
89 } 113 }
90 if reflect.DeepEqual(mod.Path, path) { 114 modPath := normalizeModulePath(mod.Path)
115 if modPath.String() == path.String() {
91 return mod 116 return mod
92 } 117 }
93 } 118 }
@@ -96,7 +121,7 @@ func (d *Diff) ModuleByPath(path []string) *ModuleDiff {
96 121
97// RootModule returns the ModuleState for the root module 122// RootModule returns the ModuleState for the root module
98func (d *Diff) RootModule() *ModuleDiff { 123func (d *Diff) RootModule() *ModuleDiff {
99 root := d.ModuleByPath(rootModulePath) 124 root := d.ModuleByPath(addrs.RootModuleInstance)
100 if root == nil { 125 if root == nil {
101 panic("missing root module") 126 panic("missing root module")
102 } 127 }
@@ -166,7 +191,8 @@ func (d *Diff) String() string {
166 keys := make([]string, 0, len(d.Modules)) 191 keys := make([]string, 0, len(d.Modules))
167 lookup := make(map[string]*ModuleDiff) 192 lookup := make(map[string]*ModuleDiff)
168 for _, m := range d.Modules { 193 for _, m := range d.Modules {
169 key := fmt.Sprintf("module.%s", strings.Join(m.Path[1:], ".")) 194 addr := normalizeModulePath(m.Path)
195 key := addr.String()
170 keys = append(keys, key) 196 keys = append(keys, key)
171 lookup[key] = m 197 lookup[key] = m
172 } 198 }
@@ -384,6 +410,541 @@ type InstanceDiff struct {
384func (d *InstanceDiff) Lock() { d.mu.Lock() } 410func (d *InstanceDiff) Lock() { d.mu.Lock() }
385func (d *InstanceDiff) Unlock() { d.mu.Unlock() } 411func (d *InstanceDiff) Unlock() { d.mu.Unlock() }
386 412
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
387// ResourceAttrDiff is the diff of a single attribute of a resource. 948// ResourceAttrDiff is the diff of a single attribute of a resource.
388type ResourceAttrDiff struct { 949type ResourceAttrDiff struct {
389 Old string // Old Value 950 Old string // Old Value