]> git.immae.eu Git - github/fretlink/terraform-provider-statuscake.git/blame - vendor/github.com/hashicorp/terraform/terraform/eval_diff.go
Merge branch 'master' of /Users/jake/terraform
[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
84}
85
86// TODO: test
87func (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
184func (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
280type flatAttrDiff map[string]*ResourceAttrDiff
281
282// we need to keep all keys if any of them have a diff
283func (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.
294func 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.
321type EvalDiffDestroy struct {
322 Info *InstanceInfo
323 State **InstanceState
324 Output **InstanceDiff
325}
326
327// TODO: test
328func (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.
363type EvalDiffDestroyModule struct {
364 Path []string
365}
366
367// TODO: test
368func (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.
387type 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
396func (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.
419type EvalReadDiff struct {
420 Name string
421 Diff **InstanceDiff
422}
423
424func (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.
444type EvalWriteDiff struct {
445 Name string
446 Diff **InstanceDiff
447}
448
449// TODO: test
450func (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}