]>
Commit | Line | Data |
---|---|---|
bae9f6d2 JC |
1 | package terraform |
2 | ||
3 | import ( | |
4 | "fmt" | |
5 | "log" | |
6 | ||
107c1cdb | 7 | "github.com/hashicorp/terraform/configs" |
bae9f6d2 | 8 | "github.com/hashicorp/terraform/dag" |
107c1cdb | 9 | "github.com/hashicorp/terraform/states" |
bae9f6d2 JC |
10 | ) |
11 | ||
12 | // GraphNodeDestroyerCBD must be implemented by nodes that might be | |
107c1cdb ND |
13 | // create-before-destroy destroyers, or might plan a create-before-destroy |
14 | // action. | |
bae9f6d2 | 15 | type GraphNodeDestroyerCBD interface { |
bae9f6d2 JC |
16 | // CreateBeforeDestroy returns true if this node represents a node |
17 | // that is doing a CBD. | |
18 | CreateBeforeDestroy() bool | |
19 | ||
20 | // ModifyCreateBeforeDestroy is called when the CBD state of a node | |
21 | // is changed dynamically. This can return an error if this isn't | |
22 | // allowed. | |
23 | ModifyCreateBeforeDestroy(bool) error | |
24 | } | |
25 | ||
107c1cdb ND |
26 | // GraphNodeAttachDestroyer is implemented by applyable nodes that have a |
27 | // companion destroy node. This allows the creation node to look up the status | |
28 | // of the destroy node and determine if it needs to depose the existing state, | |
29 | // or replace it. | |
30 | // If a node is not marked as create-before-destroy in the configuration, but a | |
31 | // dependency forces that status, only the destroy node will be aware of that | |
32 | // status. | |
33 | type GraphNodeAttachDestroyer interface { | |
34 | // AttachDestroyNode takes a destroy node and saves a reference to that | |
35 | // node in the receiver, so it can later check the status of | |
36 | // CreateBeforeDestroy(). | |
37 | AttachDestroyNode(n GraphNodeDestroyerCBD) | |
38 | } | |
39 | ||
40 | // ForcedCBDTransformer detects when a particular CBD-able graph node has | |
41 | // dependencies with another that has create_before_destroy set that require | |
42 | // it to be forced on, and forces it on. | |
43 | // | |
44 | // This must be used in the plan graph builder to ensure that | |
45 | // create_before_destroy settings are properly propagated before constructing | |
46 | // the planned changes. This requires that the plannable resource nodes | |
47 | // implement GraphNodeDestroyerCBD. | |
48 | type ForcedCBDTransformer struct { | |
49 | } | |
50 | ||
51 | func (t *ForcedCBDTransformer) Transform(g *Graph) error { | |
52 | for _, v := range g.Vertices() { | |
53 | dn, ok := v.(GraphNodeDestroyerCBD) | |
54 | if !ok { | |
55 | continue | |
56 | } | |
57 | ||
58 | if !dn.CreateBeforeDestroy() { | |
59 | // If there are no CBD decendent (dependent nodes), then we | |
60 | // do nothing here. | |
61 | if !t.hasCBDDescendent(g, v) { | |
62 | log.Printf("[TRACE] ForcedCBDTransformer: %q (%T) has no CBD descendent, so skipping", dag.VertexName(v), v) | |
63 | continue | |
64 | } | |
65 | ||
66 | // If this isn't naturally a CBD node, this means that an descendent is | |
67 | // and we need to auto-upgrade this node to CBD. We do this because | |
68 | // a CBD node depending on non-CBD will result in cycles. To avoid this, | |
69 | // we always attempt to upgrade it. | |
70 | log.Printf("[TRACE] ForcedCBDTransformer: forcing create_before_destroy on for %q (%T)", dag.VertexName(v), v) | |
71 | if err := dn.ModifyCreateBeforeDestroy(true); err != nil { | |
72 | return fmt.Errorf( | |
73 | "%s: must have create before destroy enabled because "+ | |
74 | "a dependent resource has CBD enabled. However, when "+ | |
75 | "attempting to automatically do this, an error occurred: %s", | |
76 | dag.VertexName(v), err) | |
77 | } | |
78 | } else { | |
79 | log.Printf("[TRACE] ForcedCBDTransformer: %q (%T) already has create_before_destroy set", dag.VertexName(v), v) | |
80 | } | |
81 | } | |
82 | return nil | |
83 | } | |
84 | ||
85 | // hasCBDDescendent returns true if any descendent (node that depends on this) | |
86 | // has CBD set. | |
87 | func (t *ForcedCBDTransformer) hasCBDDescendent(g *Graph, v dag.Vertex) bool { | |
88 | s, _ := g.Descendents(v) | |
89 | if s == nil { | |
90 | return true | |
91 | } | |
92 | ||
93 | for _, ov := range s.List() { | |
94 | dn, ok := ov.(GraphNodeDestroyerCBD) | |
95 | if !ok { | |
96 | continue | |
97 | } | |
98 | ||
99 | if dn.CreateBeforeDestroy() { | |
100 | // some descendent is CreateBeforeDestroy, so we need to follow suit | |
101 | log.Printf("[TRACE] ForcedCBDTransformer: %q has CBD descendent %q", dag.VertexName(v), dag.VertexName(ov)) | |
102 | return true | |
103 | } | |
104 | } | |
105 | ||
106 | return false | |
107 | } | |
108 | ||
bae9f6d2 JC |
109 | // CBDEdgeTransformer modifies the edges of CBD nodes that went through |
110 | // the DestroyEdgeTransformer to have the right dependencies. There are | |
111 | // two real tasks here: | |
112 | // | |
113 | // 1. With CBD, the destroy edge is inverted: the destroy depends on | |
114 | // the creation. | |
115 | // | |
116 | // 2. A_d must depend on resources that depend on A. This is to enable | |
117 | // the destroy to only happen once nodes that depend on A successfully | |
118 | // update to A. Example: adding a web server updates the load balancer | |
119 | // before deleting the old web server. | |
120 | // | |
107c1cdb ND |
121 | // This transformer requires that a previous transformer has already forced |
122 | // create_before_destroy on for nodes that are depended on by explicit CBD | |
123 | // nodes. This is the logic in ForcedCBDTransformer, though in practice we | |
124 | // will get here by recording the CBD-ness of each change in the plan during | |
125 | // the plan walk and then forcing the nodes into the appropriate setting during | |
126 | // DiffTransformer when building the apply graph. | |
bae9f6d2 JC |
127 | type CBDEdgeTransformer struct { |
128 | // Module and State are only needed to look up dependencies in | |
129 | // any way possible. Either can be nil if not availabile. | |
107c1cdb ND |
130 | Config *configs.Config |
131 | State *states.State | |
132 | ||
133 | // If configuration is present then Schemas is required in order to | |
134 | // obtain schema information from providers and provisioners so we can | |
135 | // properly resolve implicit dependencies. | |
136 | Schemas *Schemas | |
bae9f6d2 JC |
137 | } |
138 | ||
139 | func (t *CBDEdgeTransformer) Transform(g *Graph) error { | |
bae9f6d2 JC |
140 | // Go through and reverse any destroy edges |
141 | destroyMap := make(map[string][]dag.Vertex) | |
142 | for _, v := range g.Vertices() { | |
143 | dn, ok := v.(GraphNodeDestroyerCBD) | |
144 | if !ok { | |
145 | continue | |
146 | } | |
107c1cdb ND |
147 | dern, ok := v.(GraphNodeDestroyer) |
148 | if !ok { | |
149 | continue | |
150 | } | |
bae9f6d2 JC |
151 | |
152 | if !dn.CreateBeforeDestroy() { | |
107c1cdb | 153 | continue |
bae9f6d2 JC |
154 | } |
155 | ||
156 | // Find the destroy edge. There should only be one. | |
157 | for _, e := range g.EdgesTo(v) { | |
158 | // Not a destroy edge, ignore it | |
159 | de, ok := e.(*DestroyEdge) | |
160 | if !ok { | |
161 | continue | |
162 | } | |
163 | ||
164 | log.Printf("[TRACE] CBDEdgeTransformer: inverting edge: %s => %s", | |
165 | dag.VertexName(de.Source()), dag.VertexName(de.Target())) | |
166 | ||
167 | // Found it! Invert. | |
168 | g.RemoveEdge(de) | |
107c1cdb ND |
169 | applyNode := de.Source() |
170 | destroyNode := de.Target() | |
171 | g.Connect(&DestroyEdge{S: destroyNode, T: applyNode}) | |
bae9f6d2 JC |
172 | } |
173 | ||
174 | // If the address has an index, we strip that. Our depMap creation | |
175 | // graph doesn't expand counts so we don't currently get _exact_ | |
176 | // dependencies. One day when we limit dependencies more exactly | |
177 | // this will have to change. We have a test case covering this | |
178 | // (depNonCBDCountBoth) so it'll be caught. | |
107c1cdb ND |
179 | addr := dern.DestroyAddr() |
180 | key := addr.ContainingResource().String() | |
bae9f6d2 JC |
181 | |
182 | // Add this to the list of nodes that we need to fix up | |
183 | // the edges for (step 2 above in the docs). | |
bae9f6d2 JC |
184 | destroyMap[key] = append(destroyMap[key], v) |
185 | } | |
186 | ||
187 | // If we have no CBD nodes, then our work here is done | |
188 | if len(destroyMap) == 0 { | |
189 | return nil | |
190 | } | |
191 | ||
192 | // We have CBD nodes. We now have to move on to the much more difficult | |
193 | // task of connecting dependencies of the creation side of the destroy | |
194 | // to the destruction node. The easiest way to explain this is an example: | |
195 | // | |
196 | // Given a pre-destroy dependence of: A => B | |
197 | // And A has CBD set. | |
198 | // | |
199 | // The resulting graph should be: A => B => A_d | |
200 | // | |
201 | // They key here is that B happens before A is destroyed. This is to | |
202 | // facilitate the primary purpose for CBD: making sure that downstreams | |
203 | // are properly updated to avoid downtime before the resource is destroyed. | |
204 | // | |
205 | // We can't trust that the resource being destroyed or anything that | |
206 | // depends on it is actually in our current graph so we make a new | |
207 | // graph in order to determine those dependencies and add them in. | |
208 | log.Printf("[TRACE] CBDEdgeTransformer: building graph to find dependencies...") | |
209 | depMap, err := t.depMap(destroyMap) | |
210 | if err != nil { | |
211 | return err | |
212 | } | |
213 | ||
214 | // We now have the mapping of resource addresses to the destroy | |
215 | // nodes they need to depend on. We now go through our own vertices to | |
216 | // find any matching these addresses and make the connection. | |
217 | for _, v := range g.Vertices() { | |
218 | // We're looking for creators | |
219 | rn, ok := v.(GraphNodeCreator) | |
220 | if !ok { | |
221 | continue | |
222 | } | |
223 | ||
224 | // Get the address | |
225 | addr := rn.CreateAddr() | |
226 | ||
227 | // If the address has an index, we strip that. Our depMap creation | |
228 | // graph doesn't expand counts so we don't currently get _exact_ | |
229 | // dependencies. One day when we limit dependencies more exactly | |
230 | // this will have to change. We have a test case covering this | |
231 | // (depNonCBDCount) so it'll be caught. | |
107c1cdb | 232 | key := addr.ContainingResource().String() |
bae9f6d2 JC |
233 | |
234 | // If there is nothing this resource should depend on, ignore it | |
bae9f6d2 JC |
235 | dns, ok := depMap[key] |
236 | if !ok { | |
237 | continue | |
238 | } | |
239 | ||
240 | // We have nodes! Make the connection | |
241 | for _, dn := range dns { | |
242 | log.Printf("[TRACE] CBDEdgeTransformer: destroy depends on dependence: %s => %s", | |
243 | dag.VertexName(dn), dag.VertexName(v)) | |
244 | g.Connect(dag.BasicEdge(dn, v)) | |
245 | } | |
246 | } | |
247 | ||
248 | return nil | |
249 | } | |
250 | ||
107c1cdb | 251 | func (t *CBDEdgeTransformer) depMap(destroyMap map[string][]dag.Vertex) (map[string][]dag.Vertex, error) { |
bae9f6d2 JC |
252 | // Build the graph of our config, this ensures that all resources |
253 | // are present in the graph. | |
107c1cdb | 254 | g, diags := (&BasicGraphBuilder{ |
bae9f6d2 | 255 | Steps: []GraphTransformer{ |
107c1cdb ND |
256 | &FlatConfigTransformer{Config: t.Config}, |
257 | &AttachResourceConfigTransformer{Config: t.Config}, | |
bae9f6d2 | 258 | &AttachStateTransformer{State: t.State}, |
107c1cdb | 259 | &AttachSchemaTransformer{Schemas: t.Schemas}, |
bae9f6d2 JC |
260 | &ReferenceTransformer{}, |
261 | }, | |
262 | Name: "CBDEdgeTransformer", | |
263 | }).Build(nil) | |
107c1cdb ND |
264 | if diags.HasErrors() { |
265 | return nil, diags.Err() | |
bae9f6d2 JC |
266 | } |
267 | ||
268 | // Using this graph, build the list of destroy nodes that each resource | |
269 | // address should depend on. For example, when we find B, we map the | |
270 | // address of B to A_d in the "depMap" variable below. | |
271 | depMap := make(map[string][]dag.Vertex) | |
272 | for _, v := range g.Vertices() { | |
273 | // We're looking for resources. | |
274 | rn, ok := v.(GraphNodeResource) | |
275 | if !ok { | |
276 | continue | |
277 | } | |
278 | ||
279 | // Get the address | |
280 | addr := rn.ResourceAddr() | |
281 | key := addr.String() | |
282 | ||
283 | // Get the destroy nodes that are destroying this resource. | |
284 | // If there aren't any, then we don't need to worry about | |
285 | // any connections. | |
286 | dns, ok := destroyMap[key] | |
287 | if !ok { | |
288 | continue | |
289 | } | |
290 | ||
291 | // Get the nodes that depend on this on. In the example above: | |
292 | // finding B in A => B. | |
293 | for _, v := range g.UpEdges(v).List() { | |
294 | // We're looking for resources. | |
295 | rn, ok := v.(GraphNodeResource) | |
296 | if !ok { | |
297 | continue | |
298 | } | |
299 | ||
300 | // Keep track of the destroy nodes that this address | |
301 | // needs to depend on. | |
302 | key := rn.ResourceAddr().String() | |
303 | depMap[key] = append(depMap[key], dns...) | |
304 | } | |
305 | } | |
306 | ||
307 | return depMap, nil | |
308 | } |