-
Notifications
You must be signed in to change notification settings - Fork 787
/
set.go
137 lines (120 loc) · 5.13 KB
/
set.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
// 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)
}
}