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