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  }