diff options
Diffstat (limited to 'vendor/github.com/hashicorp/terraform/terraform/diff.go')
-rw-r--r-- | vendor/github.com/hashicorp/terraform/terraform/diff.go | 573 |
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. |
72 | func (d *Diff) AddModule(path []string) *ModuleDiff { | 80 | func (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. |
82 | func (d *Diff) ModuleByPath(path []string) *ModuleDiff { | 106 | func (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 |
98 | func (d *Diff) RootModule() *ModuleDiff { | 123 | func (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 { | |||
384 | func (d *InstanceDiff) Lock() { d.mu.Lock() } | 410 | func (d *InstanceDiff) Lock() { d.mu.Lock() } |
385 | func (d *InstanceDiff) Unlock() { d.mu.Unlock() } | 411 | func (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. | ||
419 | func (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. | ||
441 | func (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 | |||
461 | func (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 | |||
646 | func (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 | |||
658 | func (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 | |||
735 | func (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 | |||
878 | func (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 ".%". | ||
921 | func 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. |
388 | type ResourceAttrDiff struct { | 949 | type ResourceAttrDiff struct { |
389 | Old string // Old Value | 950 | Old string // Old Value |