diff options
Diffstat (limited to 'vendor/github.com/hashicorp/terraform/terraform/eval_diff.go')
-rw-r--r-- | vendor/github.com/hashicorp/terraform/terraform/eval_diff.go | 478 |
1 files changed, 478 insertions, 0 deletions
diff --git a/vendor/github.com/hashicorp/terraform/terraform/eval_diff.go b/vendor/github.com/hashicorp/terraform/terraform/eval_diff.go new file mode 100644 index 0000000..6f09526 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform/terraform/eval_diff.go | |||
@@ -0,0 +1,478 @@ | |||
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 | } | ||