]>
Commit | Line | Data |
---|---|---|
bae9f6d2 JC |
1 | package terraform |
2 | ||
3 | import ( | |
4 | "fmt" | |
5 | "log" | |
6 | "strings" | |
7 | ||
8 | "github.com/hashicorp/terraform/config" | |
9 | ) | |
10 | ||
11 | // EvalCompareDiff is an EvalNode implementation that compares two diffs | |
12 | // and errors if the diffs are not equal. | |
13 | type EvalCompareDiff struct { | |
14 | Info *InstanceInfo | |
15 | One, Two **InstanceDiff | |
16 | } | |
17 | ||
18 | // TODO: test | |
19 | func (n *EvalCompareDiff) Eval(ctx EvalContext) (interface{}, error) { | |
20 | one, two := *n.One, *n.Two | |
21 | ||
22 | // If either are nil, let them be empty | |
23 | if one == nil { | |
24 | one = new(InstanceDiff) | |
25 | one.init() | |
26 | } | |
27 | if two == nil { | |
28 | two = new(InstanceDiff) | |
29 | two.init() | |
30 | } | |
31 | oneId, _ := one.GetAttribute("id") | |
32 | twoId, _ := two.GetAttribute("id") | |
33 | one.DelAttribute("id") | |
34 | two.DelAttribute("id") | |
35 | defer func() { | |
36 | if oneId != nil { | |
37 | one.SetAttribute("id", oneId) | |
38 | } | |
39 | if twoId != nil { | |
40 | two.SetAttribute("id", twoId) | |
41 | } | |
42 | }() | |
43 | ||
44 | if same, reason := one.Same(two); !same { | |
45 | log.Printf("[ERROR] %s: diffs didn't match", n.Info.Id) | |
46 | log.Printf("[ERROR] %s: reason: %s", n.Info.Id, reason) | |
47 | log.Printf("[ERROR] %s: diff one: %#v", n.Info.Id, one) | |
48 | log.Printf("[ERROR] %s: diff two: %#v", n.Info.Id, two) | |
49 | return nil, fmt.Errorf( | |
50 | "%s: diffs didn't match during apply. This is a bug with "+ | |
51 | "Terraform and should be reported as a GitHub Issue.\n"+ | |
52 | "\n"+ | |
53 | "Please include the following information in your report:\n"+ | |
54 | "\n"+ | |
55 | " Terraform Version: %s\n"+ | |
56 | " Resource ID: %s\n"+ | |
57 | " Mismatch reason: %s\n"+ | |
58 | " Diff One (usually from plan): %#v\n"+ | |
59 | " Diff Two (usually from apply): %#v\n"+ | |
60 | "\n"+ | |
61 | "Also include as much context as you can about your config, state, "+ | |
62 | "and the steps you performed to trigger this error.\n", | |
63 | n.Info.Id, Version, n.Info.Id, reason, one, two) | |
64 | } | |
65 | ||
66 | return nil, nil | |
67 | } | |
68 | ||
69 | // EvalDiff is an EvalNode implementation that does a refresh for | |
70 | // a resource. | |
71 | type EvalDiff struct { | |
72 | Name string | |
73 | Info *InstanceInfo | |
74 | Config **ResourceConfig | |
75 | Provider *ResourceProvider | |
76 | Diff **InstanceDiff | |
77 | State **InstanceState | |
78 | OutputDiff **InstanceDiff | |
79 | OutputState **InstanceState | |
80 | ||
81 | // Resource is needed to fetch the ignore_changes list so we can | |
82 | // filter user-requested ignored attributes from the diff. | |
83 | Resource *config.Resource | |
c680a8e1 RS |
84 | |
85 | // Stub is used to flag the generated InstanceDiff as a stub. This is used to | |
86 | // ensure that the node exists to perform interpolations and generate | |
87 | // computed paths off of, but not as an actual diff where resouces should be | |
88 | // counted, and not as a diff that should be acted on. | |
89 | Stub bool | |
bae9f6d2 JC |
90 | } |
91 | ||
92 | // TODO: test | |
93 | func (n *EvalDiff) Eval(ctx EvalContext) (interface{}, error) { | |
94 | state := *n.State | |
95 | config := *n.Config | |
96 | provider := *n.Provider | |
97 | ||
98 | // Call pre-diff hook | |
c680a8e1 RS |
99 | if !n.Stub { |
100 | err := ctx.Hook(func(h Hook) (HookAction, error) { | |
101 | return h.PreDiff(n.Info, state) | |
102 | }) | |
103 | if err != nil { | |
104 | return nil, err | |
105 | } | |
bae9f6d2 JC |
106 | } |
107 | ||
108 | // The state for the diff must never be nil | |
109 | diffState := state | |
110 | if diffState == nil { | |
111 | diffState = new(InstanceState) | |
112 | } | |
113 | diffState.init() | |
114 | ||
115 | // Diff! | |
116 | diff, err := provider.Diff(n.Info, diffState, config) | |
117 | if err != nil { | |
118 | return nil, err | |
119 | } | |
120 | if diff == nil { | |
121 | diff = new(InstanceDiff) | |
122 | } | |
123 | ||
124 | // Set DestroyDeposed if we have deposed instances | |
125 | _, err = readInstanceFromState(ctx, n.Name, nil, func(rs *ResourceState) (*InstanceState, error) { | |
126 | if len(rs.Deposed) > 0 { | |
127 | diff.DestroyDeposed = true | |
128 | } | |
129 | ||
130 | return nil, nil | |
131 | }) | |
132 | if err != nil { | |
133 | return nil, err | |
134 | } | |
135 | ||
136 | // Preserve the DestroyTainted flag | |
137 | if n.Diff != nil { | |
138 | diff.SetTainted((*n.Diff).GetDestroyTainted()) | |
139 | } | |
140 | ||
141 | // Require a destroy if there is an ID and it requires new. | |
142 | if diff.RequiresNew() && state != nil && state.ID != "" { | |
143 | diff.SetDestroy(true) | |
144 | } | |
145 | ||
146 | // If we're creating a new resource, compute its ID | |
147 | if diff.RequiresNew() || state == nil || state.ID == "" { | |
148 | var oldID string | |
149 | if state != nil { | |
150 | oldID = state.Attributes["id"] | |
151 | } | |
152 | ||
153 | // Add diff to compute new ID | |
154 | diff.init() | |
155 | diff.SetAttribute("id", &ResourceAttrDiff{ | |
156 | Old: oldID, | |
157 | NewComputed: true, | |
158 | RequiresNew: true, | |
159 | Type: DiffAttrOutput, | |
160 | }) | |
161 | } | |
162 | ||
163 | // filter out ignored resources | |
164 | if err := n.processIgnoreChanges(diff); err != nil { | |
165 | return nil, err | |
166 | } | |
167 | ||
168 | // Call post-refresh hook | |
c680a8e1 RS |
169 | if !n.Stub { |
170 | err = ctx.Hook(func(h Hook) (HookAction, error) { | |
171 | return h.PostDiff(n.Info, diff) | |
172 | }) | |
173 | if err != nil { | |
174 | return nil, err | |
175 | } | |
bae9f6d2 JC |
176 | } |
177 | ||
c680a8e1 RS |
178 | // Update our output if we care |
179 | if n.OutputDiff != nil { | |
180 | *n.OutputDiff = diff | |
181 | } | |
bae9f6d2 JC |
182 | |
183 | // Update the state if we care | |
184 | if n.OutputState != nil { | |
185 | *n.OutputState = state | |
186 | ||
187 | // Merge our state so that the state is updated with our plan | |
188 | if !diff.Empty() && n.OutputState != nil { | |
189 | *n.OutputState = state.MergeDiff(diff) | |
190 | } | |
191 | } | |
192 | ||
193 | return nil, nil | |
194 | } | |
195 | ||
196 | func (n *EvalDiff) processIgnoreChanges(diff *InstanceDiff) error { | |
197 | if diff == nil || n.Resource == nil || n.Resource.Id() == "" { | |
198 | return nil | |
199 | } | |
200 | ignoreChanges := n.Resource.Lifecycle.IgnoreChanges | |
201 | ||
202 | if len(ignoreChanges) == 0 { | |
203 | return nil | |
204 | } | |
205 | ||
206 | // If we're just creating the resource, we shouldn't alter the | |
207 | // Diff at all | |
208 | if diff.ChangeType() == DiffCreate { | |
209 | return nil | |
210 | } | |
211 | ||
212 | // If the resource has been tainted then we don't process ignore changes | |
213 | // since we MUST recreate the entire resource. | |
214 | if diff.GetDestroyTainted() { | |
215 | return nil | |
216 | } | |
217 | ||
218 | attrs := diff.CopyAttributes() | |
219 | ||
220 | // get the complete set of keys we want to ignore | |
221 | ignorableAttrKeys := make(map[string]bool) | |
222 | for _, ignoredKey := range ignoreChanges { | |
223 | for k := range attrs { | |
224 | if ignoredKey == "*" || strings.HasPrefix(k, ignoredKey) { | |
225 | ignorableAttrKeys[k] = true | |
226 | } | |
227 | } | |
228 | } | |
229 | ||
230 | // If the resource was being destroyed, check to see if we can ignore the | |
231 | // reason for it being destroyed. | |
232 | if diff.GetDestroy() { | |
233 | for k, v := range attrs { | |
234 | if k == "id" { | |
235 | // id will always be changed if we intended to replace this instance | |
236 | continue | |
237 | } | |
238 | if v.Empty() || v.NewComputed { | |
239 | continue | |
240 | } | |
241 | ||
242 | // If any RequiresNew attribute isn't ignored, we need to keep the diff | |
243 | // as-is to be able to replace the resource. | |
244 | if v.RequiresNew && !ignorableAttrKeys[k] { | |
245 | return nil | |
246 | } | |
247 | } | |
248 | ||
249 | // Now that we know that we aren't replacing the instance, we can filter | |
250 | // out all the empty and computed attributes. There may be a bunch of | |
251 | // extraneous attribute diffs for the other non-requires-new attributes | |
252 | // going from "" -> "configval" or "" -> "<computed>". | |
253 | // We must make sure any flatmapped containers are filterred (or not) as a | |
254 | // whole. | |
255 | containers := groupContainers(diff) | |
256 | keep := map[string]bool{} | |
257 | for _, v := range containers { | |
258 | if v.keepDiff() { | |
259 | // At least one key has changes, so list all the sibling keys | |
260 | // to keep in the diff. | |
261 | for k := range v { | |
262 | keep[k] = true | |
263 | } | |
264 | } | |
265 | } | |
266 | ||
267 | for k, v := range attrs { | |
268 | if (v.Empty() || v.NewComputed) && !keep[k] { | |
269 | ignorableAttrKeys[k] = true | |
270 | } | |
271 | } | |
272 | } | |
273 | ||
274 | // Here we undo the two reactions to RequireNew in EvalDiff - the "id" | |
275 | // attribute diff and the Destroy boolean field | |
276 | log.Printf("[DEBUG] Removing 'id' diff and setting Destroy to false " + | |
277 | "because after ignore_changes, this diff no longer requires replacement") | |
278 | diff.DelAttribute("id") | |
279 | diff.SetDestroy(false) | |
280 | ||
281 | // If we didn't hit any of our early exit conditions, we can filter the diff. | |
282 | for k := range ignorableAttrKeys { | |
283 | log.Printf("[DEBUG] [EvalIgnoreChanges] %s - Ignoring diff attribute: %s", | |
284 | n.Resource.Id(), k) | |
285 | diff.DelAttribute(k) | |
286 | } | |
287 | ||
288 | return nil | |
289 | } | |
290 | ||
291 | // a group of key-*ResourceAttrDiff pairs from the same flatmapped container | |
292 | type flatAttrDiff map[string]*ResourceAttrDiff | |
293 | ||
294 | // we need to keep all keys if any of them have a diff | |
295 | func (f flatAttrDiff) keepDiff() bool { | |
296 | for _, v := range f { | |
297 | if !v.Empty() && !v.NewComputed { | |
298 | return true | |
299 | } | |
300 | } | |
301 | return false | |
302 | } | |
303 | ||
304 | // sets, lists and maps need to be compared for diff inclusion as a whole, so | |
305 | // group the flatmapped keys together for easier comparison. | |
306 | func groupContainers(d *InstanceDiff) map[string]flatAttrDiff { | |
307 | isIndex := multiVal.MatchString | |
308 | containers := map[string]flatAttrDiff{} | |
309 | attrs := d.CopyAttributes() | |
310 | // we need to loop once to find the index key | |
311 | for k := range attrs { | |
312 | if isIndex(k) { | |
313 | // add the key, always including the final dot to fully qualify it | |
314 | containers[k[:len(k)-1]] = flatAttrDiff{} | |
315 | } | |
316 | } | |
317 | ||
318 | // loop again to find all the sub keys | |
319 | for prefix, values := range containers { | |
320 | for k, attrDiff := range attrs { | |
321 | // we include the index value as well, since it could be part of the diff | |
322 | if strings.HasPrefix(k, prefix) { | |
323 | values[k] = attrDiff | |
324 | } | |
325 | } | |
326 | } | |
327 | ||
328 | return containers | |
329 | } | |
330 | ||
331 | // EvalDiffDestroy is an EvalNode implementation that returns a plain | |
332 | // destroy diff. | |
333 | type EvalDiffDestroy struct { | |
334 | Info *InstanceInfo | |
335 | State **InstanceState | |
336 | Output **InstanceDiff | |
337 | } | |
338 | ||
339 | // TODO: test | |
340 | func (n *EvalDiffDestroy) Eval(ctx EvalContext) (interface{}, error) { | |
341 | state := *n.State | |
342 | ||
343 | // If there is no state or we don't have an ID, we're already destroyed | |
344 | if state == nil || state.ID == "" { | |
345 | return nil, nil | |
346 | } | |
347 | ||
348 | // Call pre-diff hook | |
349 | err := ctx.Hook(func(h Hook) (HookAction, error) { | |
350 | return h.PreDiff(n.Info, state) | |
351 | }) | |
352 | if err != nil { | |
353 | return nil, err | |
354 | } | |
355 | ||
356 | // The diff | |
357 | diff := &InstanceDiff{Destroy: true} | |
358 | ||
359 | // Call post-diff hook | |
360 | err = ctx.Hook(func(h Hook) (HookAction, error) { | |
361 | return h.PostDiff(n.Info, diff) | |
362 | }) | |
363 | if err != nil { | |
364 | return nil, err | |
365 | } | |
366 | ||
367 | // Update our output | |
368 | *n.Output = diff | |
369 | ||
370 | return nil, nil | |
371 | } | |
372 | ||
373 | // EvalDiffDestroyModule is an EvalNode implementation that writes the diff to | |
374 | // the full diff. | |
375 | type EvalDiffDestroyModule struct { | |
376 | Path []string | |
377 | } | |
378 | ||
379 | // TODO: test | |
380 | func (n *EvalDiffDestroyModule) Eval(ctx EvalContext) (interface{}, error) { | |
381 | diff, lock := ctx.Diff() | |
382 | ||
383 | // Acquire the lock so that we can do this safely concurrently | |
384 | lock.Lock() | |
385 | defer lock.Unlock() | |
386 | ||
387 | // Write the diff | |
388 | modDiff := diff.ModuleByPath(n.Path) | |
389 | if modDiff == nil { | |
390 | modDiff = diff.AddModule(n.Path) | |
391 | } | |
392 | modDiff.Destroy = true | |
393 | ||
394 | return nil, nil | |
395 | } | |
396 | ||
397 | // EvalFilterDiff is an EvalNode implementation that filters the diff | |
398 | // according to some filter. | |
399 | type EvalFilterDiff struct { | |
400 | // Input and output | |
401 | Diff **InstanceDiff | |
402 | Output **InstanceDiff | |
403 | ||
404 | // Destroy, if true, will only include a destroy diff if it is set. | |
405 | Destroy bool | |
406 | } | |
407 | ||
408 | func (n *EvalFilterDiff) Eval(ctx EvalContext) (interface{}, error) { | |
409 | if *n.Diff == nil { | |
410 | return nil, nil | |
411 | } | |
412 | ||
413 | input := *n.Diff | |
414 | result := new(InstanceDiff) | |
415 | ||
416 | if n.Destroy { | |
417 | if input.GetDestroy() || input.RequiresNew() { | |
418 | result.SetDestroy(true) | |
419 | } | |
420 | } | |
421 | ||
422 | if n.Output != nil { | |
423 | *n.Output = result | |
424 | } | |
425 | ||
426 | return nil, nil | |
427 | } | |
428 | ||
429 | // EvalReadDiff is an EvalNode implementation that writes the diff to | |
430 | // the full diff. | |
431 | type EvalReadDiff struct { | |
432 | Name string | |
433 | Diff **InstanceDiff | |
434 | } | |
435 | ||
436 | func (n *EvalReadDiff) Eval(ctx EvalContext) (interface{}, error) { | |
437 | diff, lock := ctx.Diff() | |
438 | ||
439 | // Acquire the lock so that we can do this safely concurrently | |
440 | lock.Lock() | |
441 | defer lock.Unlock() | |
442 | ||
443 | // Write the diff | |
444 | modDiff := diff.ModuleByPath(ctx.Path()) | |
445 | if modDiff == nil { | |
446 | return nil, nil | |
447 | } | |
448 | ||
449 | *n.Diff = modDiff.Resources[n.Name] | |
450 | ||
451 | return nil, nil | |
452 | } | |
453 | ||
454 | // EvalWriteDiff is an EvalNode implementation that writes the diff to | |
455 | // the full diff. | |
456 | type EvalWriteDiff struct { | |
457 | Name string | |
458 | Diff **InstanceDiff | |
459 | } | |
460 | ||
461 | // TODO: test | |
462 | func (n *EvalWriteDiff) Eval(ctx EvalContext) (interface{}, error) { | |
463 | diff, lock := ctx.Diff() | |
464 | ||
465 | // The diff to write, if its empty it should write nil | |
466 | var diffVal *InstanceDiff | |
467 | if n.Diff != nil { | |
468 | diffVal = *n.Diff | |
469 | } | |
470 | if diffVal.Empty() { | |
471 | diffVal = nil | |
472 | } | |
473 | ||
474 | // Acquire the lock so that we can do this safely concurrently | |
475 | lock.Lock() | |
476 | defer lock.Unlock() | |
477 | ||
478 | // Write the diff | |
479 | modDiff := diff.ModuleByPath(ctx.Path()) | |
480 | if modDiff == nil { | |
481 | modDiff = diff.AddModule(ctx.Path()) | |
482 | } | |
483 | if diffVal != nil { | |
484 | modDiff.Resources[n.Name] = diffVal | |
485 | } else { | |
486 | delete(modDiff.Resources, n.Name) | |
487 | } | |
488 | ||
489 | return nil, nil | |
490 | } |