mirror of
https://github.com/opentofu/opentofu.git
synced 2025-02-25 18:45:20 -06:00
138 lines
5.1 KiB
Go
138 lines
5.1 KiB
Go
// Copyright (c) The OpenTofu Authors
|
|
// SPDX-License-Identifier: MPL-2.0
|
|
// Copyright (c) 2023 HashiCorp, Inc.
|
|
// SPDX-License-Identifier: MPL-2.0
|
|
|
|
package differ
|
|
|
|
import (
|
|
"reflect"
|
|
|
|
"github.com/zclconf/go-cty/cty"
|
|
|
|
"github.com/opentofu/opentofu/internal/command/jsonformat/collections"
|
|
"github.com/opentofu/opentofu/internal/command/jsonformat/computed"
|
|
"github.com/opentofu/opentofu/internal/command/jsonformat/computed/renderers"
|
|
"github.com/opentofu/opentofu/internal/command/jsonformat/structured"
|
|
"github.com/opentofu/opentofu/internal/command/jsonformat/structured/attribute_path"
|
|
"github.com/opentofu/opentofu/internal/command/jsonprovider"
|
|
"github.com/opentofu/opentofu/internal/plans"
|
|
)
|
|
|
|
func computeAttributeDiffAsSet(change structured.Change, elementType cty.Type) computed.Diff {
|
|
var elements []computed.Diff
|
|
current := change.GetDefaultActionForIteration()
|
|
processSet(change, func(value structured.Change) {
|
|
element := ComputeDiffForType(value, elementType)
|
|
elements = append(elements, element)
|
|
current = collections.CompareActions(current, element.Action)
|
|
})
|
|
return computed.NewDiff(renderers.Set(elements), current, change.ReplacePaths.Matches())
|
|
}
|
|
|
|
func computeAttributeDiffAsNestedSet(change structured.Change, attributes map[string]*jsonprovider.Attribute) computed.Diff {
|
|
var elements []computed.Diff
|
|
current := change.GetDefaultActionForIteration()
|
|
processSet(change, func(value structured.Change) {
|
|
element := computeDiffForNestedAttribute(value, &jsonprovider.NestedType{
|
|
Attributes: attributes,
|
|
NestingMode: "single",
|
|
})
|
|
elements = append(elements, element)
|
|
current = collections.CompareActions(current, element.Action)
|
|
})
|
|
return computed.NewDiff(renderers.NestedSet(elements), current, change.ReplacePaths.Matches())
|
|
}
|
|
|
|
func computeBlockDiffsAsSet(change structured.Change, block *jsonprovider.Block) ([]computed.Diff, plans.Action) {
|
|
var elements []computed.Diff
|
|
current := change.GetDefaultActionForIteration()
|
|
processSet(change, func(value structured.Change) {
|
|
element := ComputeDiffForBlock(value, block)
|
|
elements = append(elements, element)
|
|
current = collections.CompareActions(current, element.Action)
|
|
})
|
|
return elements, current
|
|
}
|
|
|
|
func processSet(change structured.Change, process func(value structured.Change)) {
|
|
sliceValue := change.AsSlice()
|
|
|
|
foundInBefore := make(map[int]int)
|
|
foundInAfter := make(map[int]int)
|
|
|
|
// O(n^2) operation here to find matching pairs in the set, so we can make
|
|
// the display look pretty. There might be a better way to do this, so look
|
|
// here for potential optimisations.
|
|
|
|
for ix := 0; ix < len(sliceValue.Before); ix++ {
|
|
matched := false
|
|
for jx := 0; jx < len(sliceValue.After); jx++ {
|
|
if _, ok := foundInAfter[jx]; ok {
|
|
// We've already found a match for this after value.
|
|
continue
|
|
}
|
|
|
|
child := sliceValue.GetChild(ix, jx)
|
|
if reflect.DeepEqual(child.Before, child.After) && child.IsBeforeSensitive() == child.IsAfterSensitive() && !child.IsUnknown() {
|
|
matched = true
|
|
foundInBefore[ix] = jx
|
|
foundInAfter[jx] = ix
|
|
}
|
|
}
|
|
|
|
if !matched {
|
|
foundInBefore[ix] = -1
|
|
}
|
|
}
|
|
|
|
clearRelevantStatus := func(change structured.Change) structured.Change {
|
|
// It's actually really difficult to render the diffs when some indices
|
|
// within a slice are relevant and others aren't. To make this simpler
|
|
// we just treat all children of a relevant list or set as also
|
|
// relevant.
|
|
//
|
|
// Interestingly the tofu plan builder also agrees with this, and
|
|
// never sets relevant attributes beneath lists or sets. We're just
|
|
// going to enforce this logic here as well. If the collection is
|
|
// relevant (decided elsewhere), then every element in the collection is
|
|
// also relevant. To be clear, in practice even if we didn't do the
|
|
// following explicitly the effect would be the same. It's just nicer
|
|
// for us to be clear about the behaviour we expect.
|
|
//
|
|
// What makes this difficult is the fact that the beforeIx and afterIx
|
|
// can be different, and it's quite difficult to work out which one is
|
|
// the relevant one. For nested lists, block lists, and tuples it's much
|
|
// easier because we always process the same indices in the before and
|
|
// after.
|
|
change.RelevantAttributes = attribute_path.AlwaysMatcher()
|
|
return change
|
|
}
|
|
|
|
// Now everything in before should be a key in foundInBefore and a value
|
|
// in foundInAfter. If a key is mapped to -1 in foundInBefore it means it
|
|
// does not have an equivalent in foundInAfter and so has been deleted.
|
|
// Everything in foundInAfter has a matching value in foundInBefore, but
|
|
// some values in after may not be in foundInAfter. This means these values
|
|
// are newly created.
|
|
|
|
for ix := 0; ix < len(sliceValue.Before); ix++ {
|
|
if jx := foundInBefore[ix]; jx >= 0 {
|
|
child := clearRelevantStatus(sliceValue.GetChild(ix, jx))
|
|
process(child)
|
|
continue
|
|
}
|
|
child := clearRelevantStatus(sliceValue.GetChild(ix, len(sliceValue.After)))
|
|
process(child)
|
|
}
|
|
|
|
for jx := 0; jx < len(sliceValue.After); jx++ {
|
|
if _, ok := foundInAfter[jx]; ok {
|
|
// Then this value was handled in the previous for loop.
|
|
continue
|
|
}
|
|
child := clearRelevantStatus(sliceValue.GetChild(len(sliceValue.Before), jx))
|
|
process(child)
|
|
}
|
|
}
|