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