]>
Commit | Line | Data |
---|---|---|
bae9f6d2 JC |
1 | package terraform |
2 | ||
3 | import ( | |
4 | "fmt" | |
5 | ||
6 | "github.com/hashicorp/terraform/config" | |
7 | ) | |
8 | ||
9 | // NodeApplyableResource represents a resource that is "applyable": | |
10 | // it is ready to be applied and is represented by a diff. | |
11 | type NodeApplyableResource struct { | |
12 | *NodeAbstractResource | |
13 | } | |
14 | ||
15 | // GraphNodeCreator | |
16 | func (n *NodeApplyableResource) CreateAddr() *ResourceAddress { | |
17 | return n.NodeAbstractResource.Addr | |
18 | } | |
19 | ||
20 | // GraphNodeReferencer, overriding NodeAbstractResource | |
21 | func (n *NodeApplyableResource) References() []string { | |
22 | result := n.NodeAbstractResource.References() | |
23 | ||
24 | // The "apply" side of a resource generally also depends on the | |
25 | // destruction of its dependencies as well. For example, if a LB | |
26 | // references a set of VMs with ${vm.foo.*.id}, then we must wait for | |
27 | // the destruction so we get the newly updated list of VMs. | |
28 | // | |
29 | // The exception here is CBD. When CBD is set, we don't do this since | |
30 | // it would create a cycle. By not creating a cycle, we require two | |
31 | // applies since the first apply the creation step will use the OLD | |
32 | // values (pre-destroy) and the second step will update. | |
33 | // | |
34 | // This is how Terraform behaved with "legacy" graphs (TF <= 0.7.x). | |
35 | // We mimic that behavior here now and can improve upon it in the future. | |
36 | // | |
37 | // This behavior is tested in graph_build_apply_test.go to test ordering. | |
38 | cbd := n.Config != nil && n.Config.Lifecycle.CreateBeforeDestroy | |
39 | if !cbd { | |
40 | // The "apply" side of a resource always depends on the destruction | |
41 | // of all its dependencies in addition to the creation. | |
42 | for _, v := range result { | |
43 | result = append(result, v+".destroy") | |
44 | } | |
45 | } | |
46 | ||
47 | return result | |
48 | } | |
49 | ||
50 | // GraphNodeEvalable | |
51 | func (n *NodeApplyableResource) EvalTree() EvalNode { | |
52 | addr := n.NodeAbstractResource.Addr | |
53 | ||
54 | // stateId is the ID to put into the state | |
55 | stateId := addr.stateId() | |
56 | ||
57 | // Build the instance info. More of this will be populated during eval | |
58 | info := &InstanceInfo{ | |
59 | Id: stateId, | |
60 | Type: addr.Type, | |
61 | } | |
62 | ||
63 | // Build the resource for eval | |
64 | resource := &Resource{ | |
65 | Name: addr.Name, | |
66 | Type: addr.Type, | |
67 | CountIndex: addr.Index, | |
68 | } | |
69 | if resource.CountIndex < 0 { | |
70 | resource.CountIndex = 0 | |
71 | } | |
72 | ||
73 | // Determine the dependencies for the state. | |
74 | stateDeps := n.StateReferences() | |
75 | ||
76 | // Eval info is different depending on what kind of resource this is | |
77 | switch n.Config.Mode { | |
78 | case config.ManagedResourceMode: | |
79 | return n.evalTreeManagedResource( | |
80 | stateId, info, resource, stateDeps, | |
81 | ) | |
82 | case config.DataResourceMode: | |
83 | return n.evalTreeDataResource( | |
84 | stateId, info, resource, stateDeps) | |
85 | default: | |
86 | panic(fmt.Errorf("unsupported resource mode %s", n.Config.Mode)) | |
87 | } | |
88 | } | |
89 | ||
90 | func (n *NodeApplyableResource) evalTreeDataResource( | |
91 | stateId string, info *InstanceInfo, | |
92 | resource *Resource, stateDeps []string) EvalNode { | |
93 | var provider ResourceProvider | |
94 | var config *ResourceConfig | |
95 | var diff *InstanceDiff | |
96 | var state *InstanceState | |
97 | ||
98 | return &EvalSequence{ | |
99 | Nodes: []EvalNode{ | |
100 | // Build the instance info | |
101 | &EvalInstanceInfo{ | |
102 | Info: info, | |
103 | }, | |
104 | ||
105 | // Get the saved diff for apply | |
106 | &EvalReadDiff{ | |
107 | Name: stateId, | |
108 | Diff: &diff, | |
109 | }, | |
110 | ||
111 | // Stop here if we don't actually have a diff | |
112 | &EvalIf{ | |
113 | If: func(ctx EvalContext) (bool, error) { | |
114 | if diff == nil { | |
115 | return true, EvalEarlyExitError{} | |
116 | } | |
117 | ||
118 | if diff.GetAttributesLen() == 0 { | |
119 | return true, EvalEarlyExitError{} | |
120 | } | |
121 | ||
122 | return true, nil | |
123 | }, | |
124 | Then: EvalNoop{}, | |
125 | }, | |
126 | ||
127 | // We need to re-interpolate the config here, rather than | |
128 | // just using the diff's values directly, because we've | |
129 | // potentially learned more variable values during the | |
130 | // apply pass that weren't known when the diff was produced. | |
131 | &EvalInterpolate{ | |
132 | Config: n.Config.RawConfig.Copy(), | |
133 | Resource: resource, | |
134 | Output: &config, | |
135 | }, | |
136 | ||
137 | &EvalGetProvider{ | |
138 | Name: n.ProvidedBy()[0], | |
139 | Output: &provider, | |
140 | }, | |
141 | ||
142 | // Make a new diff with our newly-interpolated config. | |
143 | &EvalReadDataDiff{ | |
144 | Info: info, | |
145 | Config: &config, | |
146 | Previous: &diff, | |
147 | Provider: &provider, | |
148 | Output: &diff, | |
149 | }, | |
150 | ||
151 | &EvalReadDataApply{ | |
152 | Info: info, | |
153 | Diff: &diff, | |
154 | Provider: &provider, | |
155 | Output: &state, | |
156 | }, | |
157 | ||
158 | &EvalWriteState{ | |
159 | Name: stateId, | |
160 | ResourceType: n.Config.Type, | |
161 | Provider: n.Config.Provider, | |
162 | Dependencies: stateDeps, | |
163 | State: &state, | |
164 | }, | |
165 | ||
166 | // Clear the diff now that we've applied it, so | |
167 | // later nodes won't see a diff that's now a no-op. | |
168 | &EvalWriteDiff{ | |
169 | Name: stateId, | |
170 | Diff: nil, | |
171 | }, | |
172 | ||
173 | &EvalUpdateStateHook{}, | |
174 | }, | |
175 | } | |
176 | } | |
177 | ||
178 | func (n *NodeApplyableResource) evalTreeManagedResource( | |
179 | stateId string, info *InstanceInfo, | |
180 | resource *Resource, stateDeps []string) EvalNode { | |
181 | // Declare a bunch of variables that are used for state during | |
182 | // evaluation. Most of this are written to by-address below. | |
183 | var provider ResourceProvider | |
184 | var diff, diffApply *InstanceDiff | |
185 | var state *InstanceState | |
186 | var resourceConfig *ResourceConfig | |
187 | var err error | |
188 | var createNew bool | |
189 | var createBeforeDestroyEnabled bool | |
190 | ||
191 | return &EvalSequence{ | |
192 | Nodes: []EvalNode{ | |
193 | // Build the instance info | |
194 | &EvalInstanceInfo{ | |
195 | Info: info, | |
196 | }, | |
197 | ||
198 | // Get the saved diff for apply | |
199 | &EvalReadDiff{ | |
200 | Name: stateId, | |
201 | Diff: &diffApply, | |
202 | }, | |
203 | ||
204 | // We don't want to do any destroys | |
205 | &EvalIf{ | |
206 | If: func(ctx EvalContext) (bool, error) { | |
207 | if diffApply == nil { | |
208 | return true, EvalEarlyExitError{} | |
209 | } | |
210 | ||
211 | if diffApply.GetDestroy() && diffApply.GetAttributesLen() == 0 { | |
212 | return true, EvalEarlyExitError{} | |
213 | } | |
214 | ||
215 | diffApply.SetDestroy(false) | |
216 | return true, nil | |
217 | }, | |
218 | Then: EvalNoop{}, | |
219 | }, | |
220 | ||
221 | &EvalIf{ | |
222 | If: func(ctx EvalContext) (bool, error) { | |
223 | destroy := false | |
224 | if diffApply != nil { | |
225 | destroy = diffApply.GetDestroy() || diffApply.RequiresNew() | |
226 | } | |
227 | ||
228 | createBeforeDestroyEnabled = | |
229 | n.Config.Lifecycle.CreateBeforeDestroy && | |
230 | destroy | |
231 | ||
232 | return createBeforeDestroyEnabled, nil | |
233 | }, | |
234 | Then: &EvalDeposeState{ | |
235 | Name: stateId, | |
236 | }, | |
237 | }, | |
238 | ||
239 | &EvalInterpolate{ | |
240 | Config: n.Config.RawConfig.Copy(), | |
241 | Resource: resource, | |
242 | Output: &resourceConfig, | |
243 | }, | |
244 | &EvalGetProvider{ | |
245 | Name: n.ProvidedBy()[0], | |
246 | Output: &provider, | |
247 | }, | |
248 | &EvalReadState{ | |
249 | Name: stateId, | |
250 | Output: &state, | |
251 | }, | |
252 | // Re-run validation to catch any errors we missed, e.g. type | |
253 | // mismatches on computed values. | |
254 | &EvalValidateResource{ | |
255 | Provider: &provider, | |
256 | Config: &resourceConfig, | |
257 | ResourceName: n.Config.Name, | |
258 | ResourceType: n.Config.Type, | |
259 | ResourceMode: n.Config.Mode, | |
260 | IgnoreWarnings: true, | |
261 | }, | |
262 | &EvalDiff{ | |
263 | Info: info, | |
264 | Config: &resourceConfig, | |
265 | Resource: n.Config, | |
266 | Provider: &provider, | |
267 | Diff: &diffApply, | |
268 | State: &state, | |
269 | OutputDiff: &diffApply, | |
270 | }, | |
271 | ||
272 | // Get the saved diff | |
273 | &EvalReadDiff{ | |
274 | Name: stateId, | |
275 | Diff: &diff, | |
276 | }, | |
277 | ||
278 | // Compare the diffs | |
279 | &EvalCompareDiff{ | |
280 | Info: info, | |
281 | One: &diff, | |
282 | Two: &diffApply, | |
283 | }, | |
284 | ||
285 | &EvalGetProvider{ | |
286 | Name: n.ProvidedBy()[0], | |
287 | Output: &provider, | |
288 | }, | |
289 | &EvalReadState{ | |
290 | Name: stateId, | |
291 | Output: &state, | |
292 | }, | |
293 | // Call pre-apply hook | |
294 | &EvalApplyPre{ | |
295 | Info: info, | |
296 | State: &state, | |
297 | Diff: &diffApply, | |
298 | }, | |
299 | &EvalApply{ | |
300 | Info: info, | |
301 | State: &state, | |
302 | Diff: &diffApply, | |
303 | Provider: &provider, | |
304 | Output: &state, | |
305 | Error: &err, | |
306 | CreateNew: &createNew, | |
307 | }, | |
308 | &EvalWriteState{ | |
309 | Name: stateId, | |
310 | ResourceType: n.Config.Type, | |
311 | Provider: n.Config.Provider, | |
312 | Dependencies: stateDeps, | |
313 | State: &state, | |
314 | }, | |
315 | &EvalApplyProvisioners{ | |
316 | Info: info, | |
317 | State: &state, | |
318 | Resource: n.Config, | |
319 | InterpResource: resource, | |
320 | CreateNew: &createNew, | |
321 | Error: &err, | |
322 | When: config.ProvisionerWhenCreate, | |
323 | }, | |
324 | &EvalIf{ | |
325 | If: func(ctx EvalContext) (bool, error) { | |
326 | return createBeforeDestroyEnabled && err != nil, nil | |
327 | }, | |
328 | Then: &EvalUndeposeState{ | |
329 | Name: stateId, | |
330 | State: &state, | |
331 | }, | |
332 | Else: &EvalWriteState{ | |
333 | Name: stateId, | |
334 | ResourceType: n.Config.Type, | |
335 | Provider: n.Config.Provider, | |
336 | Dependencies: stateDeps, | |
337 | State: &state, | |
338 | }, | |
339 | }, | |
340 | ||
341 | // We clear the diff out here so that future nodes | |
342 | // don't see a diff that is already complete. There | |
343 | // is no longer a diff! | |
344 | &EvalWriteDiff{ | |
345 | Name: stateId, | |
346 | Diff: nil, | |
347 | }, | |
348 | ||
349 | &EvalApplyPost{ | |
350 | Info: info, | |
351 | State: &state, | |
352 | Error: &err, | |
353 | }, | |
354 | &EvalUpdateStateHook{}, | |
355 | }, | |
356 | } | |
357 | } |