github.com/hs0210/hashicorp-terraform@v0.11.12-beta1/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 "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 }