]> git.immae.eu Git - github/fretlink/terraform-provider-statuscake.git/blame - vendor/github.com/hashicorp/terraform/terraform/eval_diff.go
vendor: github.com/hashicorp/terraform/...@v0.10.0
[github/fretlink/terraform-provider-statuscake.git] / vendor / github.com / hashicorp / terraform / terraform / eval_diff.go
CommitLineData
bae9f6d2
JC
1package terraform
2
3import (
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.
13type EvalCompareDiff struct {
14 Info *InstanceInfo
15 One, Two **InstanceDiff
16}
17
18// TODO: test
19func (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.
71type 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
93func (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
196func (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
292type flatAttrDiff map[string]*ResourceAttrDiff
293
294// we need to keep all keys if any of them have a diff
295func (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.
306func 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.
333type EvalDiffDestroy struct {
334 Info *InstanceInfo
335 State **InstanceState
336 Output **InstanceDiff
337}
338
339// TODO: test
340func (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.
375type EvalDiffDestroyModule struct {
376 Path []string
377}
378
379// TODO: test
380func (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.
399type 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
408func (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.
431type EvalReadDiff struct {
432 Name string
433 Diff **InstanceDiff
434}
435
436func (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.
456type EvalWriteDiff struct {
457 Name string
458 Diff **InstanceDiff
459}
460
461// TODO: test
462func (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}