forked from hashicorp/terraform
/
transform_destroy.go
284 lines (242 loc) · 7.92 KB
/
transform_destroy.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
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
package terraform
import (
"github.com/hashicorp/terraform/config"
"github.com/hashicorp/terraform/dag"
)
// GraphNodeDestroyable is the interface that nodes that can be destroyed
// must implement. This is used to automatically handle the creation of
// destroy nodes in the graph and the dependency ordering of those destroys.
type GraphNodeDestroyable interface {
// DestroyNode returns the node used for the destroy with the given
// mode. If this returns nil, then a destroy node for that mode
// will not be added.
DestroyNode() GraphNodeDestroy
}
// GraphNodeDestroy is the interface that must implemented by
// nodes that destroy.
type GraphNodeDestroy interface {
dag.Vertex
// CreateBeforeDestroy is called to check whether this node
// should be created before it is destroyed. The CreateBeforeDestroy
// transformer uses this information to setup the graph.
CreateBeforeDestroy() bool
// CreateNode returns the node used for the create side of this
// destroy. This must already exist within the graph.
CreateNode() dag.Vertex
}
// GraphNodeDestroyPrunable is the interface that can be implemented to
// signal that this node can be pruned depending on state.
type GraphNodeDestroyPrunable interface {
// DestroyInclude is called to check if this node should be included
// with the given state. The state and diff must NOT be modified.
DestroyInclude(*ModuleDiff, *ModuleState) bool
}
// GraphNodeEdgeInclude can be implemented to not include something
// as an edge within the destroy graph. This is usually done because it
// might cause unnecessary cycles.
type GraphNodeDestroyEdgeInclude interface {
DestroyEdgeInclude(dag.Vertex) bool
}
// DestroyTransformer is a GraphTransformer that creates the destruction
// nodes for things that _might_ be destroyed.
type DestroyTransformer struct {
FullDestroy bool
}
func (t *DestroyTransformer) Transform(g *Graph) error {
var connect, remove []dag.Edge
nodeToCn := make(map[dag.Vertex]dag.Vertex, len(g.Vertices()))
nodeToDn := make(map[dag.Vertex]dag.Vertex, len(g.Vertices()))
for _, v := range g.Vertices() {
// If it is not a destroyable, we don't care
cn, ok := v.(GraphNodeDestroyable)
if !ok {
continue
}
// Grab the destroy side of the node and connect it through
n := cn.DestroyNode()
if n == nil {
continue
}
// Store it
nodeToCn[n] = cn
nodeToDn[cn] = n
// If the creation node is equal to the destroy node, then
// don't do any of the edge jump rope below.
if n.(interface{}) == cn.(interface{}) {
continue
}
// Add it to the graph
g.Add(n)
// Inherit all the edges from the old node
downEdges := g.DownEdges(v).List()
for _, edgeRaw := range downEdges {
// If this thing specifically requests to not be depended on
// by destroy nodes, then don't.
if i, ok := edgeRaw.(GraphNodeDestroyEdgeInclude); ok &&
!i.DestroyEdgeInclude(v) {
continue
}
g.Connect(dag.BasicEdge(n, edgeRaw.(dag.Vertex)))
}
// Add a new edge to connect the node to be created to
// the destroy node.
connect = append(connect, dag.BasicEdge(v, n))
}
// Go through the nodes we added and determine if they depend
// on any nodes with a destroy node. If so, depend on that instead.
for n, _ := range nodeToCn {
for _, downRaw := range g.DownEdges(n).List() {
target := downRaw.(dag.Vertex)
cn2, ok := target.(GraphNodeDestroyable)
if !ok {
continue
}
newTarget := nodeToDn[cn2]
if newTarget == nil {
continue
}
// Make the new edge and transpose
connect = append(connect, dag.BasicEdge(newTarget, n))
// Remove the old edge
remove = append(remove, dag.BasicEdge(n, target))
}
}
// Atomatically add/remove the edges
for _, e := range connect {
g.Connect(e)
}
for _, e := range remove {
g.RemoveEdge(e)
}
return nil
}
// CreateBeforeDestroyTransformer is a GraphTransformer that modifies
// the destroys of some nodes so that the creation happens before the
// destroy.
type CreateBeforeDestroyTransformer struct{}
func (t *CreateBeforeDestroyTransformer) Transform(g *Graph) error {
// We "stage" the edge connections/destroys in these slices so that
// while we're doing the edge transformations (transpositions) in
// the graph, we're not affecting future edge transpositions. These
// slices let us stage ALL the changes that WILL happen so that all
// of the transformations happen atomically.
var connect, destroy []dag.Edge
for _, v := range g.Vertices() {
// We only care to use the destroy nodes
dn, ok := v.(GraphNodeDestroy)
if !ok {
continue
}
// If the node doesn't need to create before destroy, then continue
if !dn.CreateBeforeDestroy() {
if noCreateBeforeDestroyAncestors(g, dn) {
continue
}
// PURPOSELY HACKY FIX SINCE THIS TRANSFORM IS DEPRECATED.
// This is a hacky way to fix GH-10439. For a detailed description
// of the fix, see CBDEdgeTransformer, which is the equivalent
// transform used by the new graphs.
//
// This transform is deprecated because it is only used by the
// old graphs which are going to be removed.
var update *config.Resource
if dn, ok := v.(*graphNodeResourceDestroy); ok {
update = dn.Original.Resource
}
if dn, ok := v.(*graphNodeResourceDestroyFlat); ok {
update = dn.Original.Resource
}
if update != nil {
update.Lifecycle.CreateBeforeDestroy = true
}
}
// Get the creation side of this node
cn := dn.CreateNode()
// Take all the things which depend on the creation node and
// make them dependencies on the destruction. Clarifying this
// with an example: if you have a web server and a load balancer
// and the load balancer depends on the web server, then when we
// do a create before destroy, we want to make sure the steps are:
//
// 1.) Create new web server
// 2.) Update load balancer
// 3.) Delete old web server
//
// This ensures that.
for _, sourceRaw := range g.UpEdges(cn).List() {
source := sourceRaw.(dag.Vertex)
// If the graph has a "root" node (one added by a RootTransformer and not
// just a resource that happens to have no ancestors), we don't want to
// add any edges to it, because then it ceases to be a root.
if _, ok := source.(graphNodeRoot); ok {
continue
}
connect = append(connect, dag.BasicEdge(dn, source))
}
// Swap the edge so that the destroy depends on the creation
// happening...
connect = append(connect, dag.BasicEdge(dn, cn))
destroy = append(destroy, dag.BasicEdge(cn, dn))
}
for _, edge := range connect {
g.Connect(edge)
}
for _, edge := range destroy {
g.RemoveEdge(edge)
}
return nil
}
// noCreateBeforeDestroyAncestors verifies that a vertex has no ancestors that
// are CreateBeforeDestroy.
// If this vertex has an ancestor with CreateBeforeDestroy, we will need to
// inherit that behavior and re-order the edges even if this node type doesn't
// directly implement CreateBeforeDestroy.
func noCreateBeforeDestroyAncestors(g *Graph, v dag.Vertex) bool {
s, _ := g.Ancestors(v)
if s == nil {
return true
}
for _, v := range s.List() {
dn, ok := v.(GraphNodeDestroy)
if !ok {
continue
}
if dn.CreateBeforeDestroy() {
// some ancestor is CreateBeforeDestroy, so we need to follow suit
return false
}
}
return true
}
// PruneDestroyTransformer is a GraphTransformer that removes the destroy
// nodes that aren't in the diff.
type PruneDestroyTransformer struct {
Diff *Diff
State *State
}
func (t *PruneDestroyTransformer) Transform(g *Graph) error {
for _, v := range g.Vertices() {
// If it is not a destroyer, we don't care
dn, ok := v.(GraphNodeDestroyPrunable)
if !ok {
continue
}
path := g.Path
if pn, ok := v.(GraphNodeSubPath); ok {
path = pn.Path()
}
var modDiff *ModuleDiff
var modState *ModuleState
if t.Diff != nil {
modDiff = t.Diff.ModuleByPath(path)
}
if t.State != nil {
modState = t.State.ModuleByPath(path)
}
// Remove it if we should
if !dn.DestroyInclude(modDiff, modState) {
g.Remove(v)
}
}
return nil
}