github.com/ticketmaster/terraform@v0.10.0-beta2.0.20170711045249-a12daf5aba4f/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 // 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 90 } 91 92 // TODO: test 93 func (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 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 } 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 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 } 176 } 177 178 // Update our output if we care 179 if n.OutputDiff != nil { 180 *n.OutputDiff = diff 181 } 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 196 func (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 292 type flatAttrDiff map[string]*ResourceAttrDiff 293 294 // we need to keep all keys if any of them have a diff 295 func (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. 306 func 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. 333 type EvalDiffDestroy struct { 334 Info *InstanceInfo 335 State **InstanceState 336 Output **InstanceDiff 337 } 338 339 // TODO: test 340 func (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. 375 type EvalDiffDestroyModule struct { 376 Path []string 377 } 378 379 // TODO: test 380 func (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. 399 type 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 408 func (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. 431 type EvalReadDiff struct { 432 Name string 433 Diff **InstanceDiff 434 } 435 436 func (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. 456 type EvalWriteDiff struct { 457 Name string 458 Diff **InstanceDiff 459 } 460 461 // TODO: test 462 func (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 }