github.com/r3labs/terraform@v0.8.4/terraform/eval_diff.go (about) 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 if err := n.processIgnoreChanges(diff); err != nil { 156 return nil, err 157 } 158 159 // Call post-refresh hook 160 err = ctx.Hook(func(h Hook) (HookAction, error) { 161 return h.PostDiff(n.Info, diff) 162 }) 163 if err != nil { 164 return nil, err 165 } 166 167 // Update our output 168 *n.OutputDiff = diff 169 170 // Update the state if we care 171 if n.OutputState != nil { 172 *n.OutputState = state 173 174 // Merge our state so that the state is updated with our plan 175 if !diff.Empty() && n.OutputState != nil { 176 *n.OutputState = state.MergeDiff(diff) 177 } 178 } 179 180 return nil, nil 181 } 182 183 func (n *EvalDiff) processIgnoreChanges(diff *InstanceDiff) error { 184 if diff == nil || n.Resource == nil || n.Resource.Id() == "" { 185 return nil 186 } 187 ignoreChanges := n.Resource.Lifecycle.IgnoreChanges 188 189 if len(ignoreChanges) == 0 { 190 return nil 191 } 192 193 changeType := diff.ChangeType() 194 195 // If we're just creating the resource, we shouldn't alter the 196 // Diff at all 197 if changeType == DiffCreate { 198 return nil 199 } 200 201 // If the resource has been tainted then we don't process ignore changes 202 // since we MUST recreate the entire resource. 203 if diff.DestroyTainted { 204 return nil 205 } 206 207 ignorableAttrKeys := make(map[string]bool) 208 for _, ignoredKey := range ignoreChanges { 209 for k := range diff.CopyAttributes() { 210 if ignoredKey == "*" || strings.HasPrefix(k, ignoredKey) { 211 ignorableAttrKeys[k] = true 212 } 213 } 214 } 215 216 // If we are replacing the resource, then we expect there to be a bunch of 217 // extraneous attribute diffs we need to filter out for the other 218 // non-requires-new attributes going from "" -> "configval" or "" -> 219 // "<computed>". Filtering these out allows us to see if we might be able to 220 // skip this diff altogether. 221 if changeType == DiffDestroyCreate { 222 for k, v := range diff.CopyAttributes() { 223 if v.Empty() || v.NewComputed { 224 ignorableAttrKeys[k] = true 225 } 226 } 227 228 // Here we emulate the implementation of diff.RequiresNew() with one small 229 // tweak, we ignore the "id" attribute diff that gets added by EvalDiff, 230 // since that was added in reaction to RequiresNew being true. 231 requiresNewAfterIgnores := false 232 for k, v := range diff.CopyAttributes() { 233 if k == "id" { 234 continue 235 } 236 if _, ok := ignorableAttrKeys[k]; ok { 237 continue 238 } 239 if v.RequiresNew == true { 240 requiresNewAfterIgnores = true 241 } 242 } 243 244 // If we still require resource replacement after ignores, we 245 // can't touch the diff, as all of the attributes will be 246 // required to process the replacement. 247 if requiresNewAfterIgnores { 248 return nil 249 } 250 251 // Here we undo the two reactions to RequireNew in EvalDiff - the "id" 252 // attribute diff and the Destroy boolean field 253 log.Printf("[DEBUG] Removing 'id' diff and setting Destroy to false " + 254 "because after ignore_changes, this diff no longer requires replacement") 255 diff.DelAttribute("id") 256 diff.SetDestroy(false) 257 } 258 259 // If we didn't hit any of our early exit conditions, we can filter the diff. 260 for k := range ignorableAttrKeys { 261 log.Printf("[DEBUG] [EvalIgnoreChanges] %s - Ignoring diff attribute: %s", 262 n.Resource.Id(), k) 263 diff.DelAttribute(k) 264 } 265 266 return nil 267 } 268 269 // EvalDiffDestroy is an EvalNode implementation that returns a plain 270 // destroy diff. 271 type EvalDiffDestroy struct { 272 Info *InstanceInfo 273 State **InstanceState 274 Output **InstanceDiff 275 } 276 277 // TODO: test 278 func (n *EvalDiffDestroy) Eval(ctx EvalContext) (interface{}, error) { 279 state := *n.State 280 281 // If there is no state or we don't have an ID, we're already destroyed 282 if state == nil || state.ID == "" { 283 return nil, nil 284 } 285 286 // Call pre-diff hook 287 err := ctx.Hook(func(h Hook) (HookAction, error) { 288 return h.PreDiff(n.Info, state) 289 }) 290 if err != nil { 291 return nil, err 292 } 293 294 // The diff 295 diff := &InstanceDiff{Destroy: true} 296 297 // Call post-diff hook 298 err = ctx.Hook(func(h Hook) (HookAction, error) { 299 return h.PostDiff(n.Info, diff) 300 }) 301 if err != nil { 302 return nil, err 303 } 304 305 // Update our output 306 *n.Output = diff 307 308 return nil, nil 309 } 310 311 // EvalDiffDestroyModule is an EvalNode implementation that writes the diff to 312 // the full diff. 313 type EvalDiffDestroyModule struct { 314 Path []string 315 } 316 317 // TODO: test 318 func (n *EvalDiffDestroyModule) Eval(ctx EvalContext) (interface{}, error) { 319 diff, lock := ctx.Diff() 320 321 // Acquire the lock so that we can do this safely concurrently 322 lock.Lock() 323 defer lock.Unlock() 324 325 // Write the diff 326 modDiff := diff.ModuleByPath(n.Path) 327 if modDiff == nil { 328 modDiff = diff.AddModule(n.Path) 329 } 330 modDiff.Destroy = true 331 332 return nil, nil 333 } 334 335 // EvalFilterDiff is an EvalNode implementation that filters the diff 336 // according to some filter. 337 type EvalFilterDiff struct { 338 // Input and output 339 Diff **InstanceDiff 340 Output **InstanceDiff 341 342 // Destroy, if true, will only include a destroy diff if it is set. 343 Destroy bool 344 } 345 346 func (n *EvalFilterDiff) Eval(ctx EvalContext) (interface{}, error) { 347 if *n.Diff == nil { 348 return nil, nil 349 } 350 351 input := *n.Diff 352 result := new(InstanceDiff) 353 354 if n.Destroy { 355 if input.GetDestroy() || input.RequiresNew() { 356 result.SetDestroy(true) 357 } 358 } 359 360 if n.Output != nil { 361 *n.Output = result 362 } 363 364 return nil, nil 365 } 366 367 // EvalReadDiff is an EvalNode implementation that writes the diff to 368 // the full diff. 369 type EvalReadDiff struct { 370 Name string 371 Diff **InstanceDiff 372 } 373 374 func (n *EvalReadDiff) Eval(ctx EvalContext) (interface{}, error) { 375 diff, lock := ctx.Diff() 376 377 // Acquire the lock so that we can do this safely concurrently 378 lock.Lock() 379 defer lock.Unlock() 380 381 // Write the diff 382 modDiff := diff.ModuleByPath(ctx.Path()) 383 if modDiff == nil { 384 return nil, nil 385 } 386 387 *n.Diff = modDiff.Resources[n.Name] 388 389 return nil, nil 390 } 391 392 // EvalWriteDiff is an EvalNode implementation that writes the diff to 393 // the full diff. 394 type EvalWriteDiff struct { 395 Name string 396 Diff **InstanceDiff 397 } 398 399 // TODO: test 400 func (n *EvalWriteDiff) Eval(ctx EvalContext) (interface{}, error) { 401 diff, lock := ctx.Diff() 402 403 // The diff to write, if its empty it should write nil 404 var diffVal *InstanceDiff 405 if n.Diff != nil { 406 diffVal = *n.Diff 407 } 408 if diffVal.Empty() { 409 diffVal = nil 410 } 411 412 // Acquire the lock so that we can do this safely concurrently 413 lock.Lock() 414 defer lock.Unlock() 415 416 // Write the diff 417 modDiff := diff.ModuleByPath(ctx.Path()) 418 if modDiff == nil { 419 modDiff = diff.AddModule(ctx.Path()) 420 } 421 if diffVal != nil { 422 modDiff.Resources[n.Name] = diffVal 423 } else { 424 delete(modDiff.Resources, n.Name) 425 } 426 427 return nil, nil 428 }