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