github.com/opentofu/opentofu@v1.7.1/internal/command/jsonformat/differ/list.go (about) 1 // Copyright (c) The OpenTofu Authors 2 // SPDX-License-Identifier: MPL-2.0 3 // Copyright (c) 2023 HashiCorp, Inc. 4 // SPDX-License-Identifier: MPL-2.0 5 6 package differ 7 8 import ( 9 "github.com/zclconf/go-cty/cty" 10 11 "github.com/opentofu/opentofu/internal/command/jsonformat/collections" 12 "github.com/opentofu/opentofu/internal/command/jsonformat/computed" 13 "github.com/opentofu/opentofu/internal/command/jsonformat/computed/renderers" 14 "github.com/opentofu/opentofu/internal/command/jsonformat/structured" 15 "github.com/opentofu/opentofu/internal/command/jsonformat/structured/attribute_path" 16 "github.com/opentofu/opentofu/internal/command/jsonprovider" 17 "github.com/opentofu/opentofu/internal/plans" 18 ) 19 20 func computeAttributeDiffAsList(change structured.Change, elementType cty.Type) computed.Diff { 21 sliceValue := change.AsSlice() 22 23 processIndices := func(beforeIx, afterIx int) computed.Diff { 24 value := sliceValue.GetChild(beforeIx, afterIx) 25 26 // It's actually really difficult to render the diffs when some indices 27 // within a slice are relevant and others aren't. To make this simpler 28 // we just treat all children of a relevant list or set as also 29 // relevant. 30 // 31 // Interestingly the tofu plan builder also agrees with this, and 32 // never sets relevant attributes beneath lists or sets. We're just 33 // going to enforce this logic here as well. If the collection is 34 // relevant (decided elsewhere), then every element in the collection is 35 // also relevant. To be clear, in practice even if we didn't do the 36 // following explicitly the effect would be the same. It's just nicer 37 // for us to be clear about the behaviour we expect. 38 // 39 // What makes this difficult is the fact that the beforeIx and afterIx 40 // can be different, and it's quite difficult to work out which one is 41 // the relevant one. For nested lists, block lists, and tuples it's much 42 // easier because we always process the same indices in the before and 43 // after. 44 value.RelevantAttributes = attribute_path.AlwaysMatcher() 45 46 return ComputeDiffForType(value, elementType) 47 } 48 49 isObjType := func(_ interface{}) bool { 50 return elementType.IsObjectType() 51 } 52 53 elements, current := collections.TransformSlice(sliceValue.Before, sliceValue.After, processIndices, isObjType) 54 return computed.NewDiff(renderers.List(elements), current, change.ReplacePaths.Matches()) 55 } 56 57 func computeAttributeDiffAsNestedList(change structured.Change, attributes map[string]*jsonprovider.Attribute) computed.Diff { 58 var elements []computed.Diff 59 current := change.GetDefaultActionForIteration() 60 processNestedList(change, func(value structured.Change) { 61 element := computeDiffForNestedAttribute(value, &jsonprovider.NestedType{ 62 Attributes: attributes, 63 NestingMode: "single", 64 }) 65 elements = append(elements, element) 66 current = collections.CompareActions(current, element.Action) 67 }) 68 return computed.NewDiff(renderers.NestedList(elements), current, change.ReplacePaths.Matches()) 69 } 70 71 func computeBlockDiffsAsList(change structured.Change, block *jsonprovider.Block) ([]computed.Diff, plans.Action) { 72 var elements []computed.Diff 73 current := change.GetDefaultActionForIteration() 74 processNestedList(change, func(value structured.Change) { 75 element := ComputeDiffForBlock(value, block) 76 elements = append(elements, element) 77 current = collections.CompareActions(current, element.Action) 78 }) 79 return elements, current 80 } 81 82 func processNestedList(change structured.Change, process func(value structured.Change)) { 83 sliceValue := change.AsSlice() 84 for ix := 0; ix < len(sliceValue.Before) || ix < len(sliceValue.After); ix++ { 85 value := sliceValue.GetChild(ix, ix) 86 if !value.RelevantAttributes.MatchesPartial() { 87 // Mark non-relevant attributes as unchanged. 88 value = value.AsNoOp() 89 } 90 process(value) 91 } 92 }