github.com/chentex/terraform@v0.11.2-0.20171208003256-252e8145842e/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() { 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 } 265 } 266 } 267 268 for k, v := range attrs { 269 if (v.Empty() || v.NewComputed) && !keep[k] { 270 ignorableAttrKeys[k] = true 271 } 272 } 273 } 274 275 // Here we undo the two reactions to RequireNew in EvalDiff - the "id" 276 // attribute diff and the Destroy boolean field 277 log.Printf("[DEBUG] Removing 'id' diff and setting Destroy to false " + 278 "because after ignore_changes, this diff no longer requires replacement") 279 diff.DelAttribute("id") 280 diff.SetDestroy(false) 281 282 // If we didn't hit any of our early exit conditions, we can filter the diff. 283 for k := range ignorableAttrKeys { 284 log.Printf("[DEBUG] [EvalIgnoreChanges] %s - Ignoring diff attribute: %s", 285 n.Resource.Id(), k) 286 diff.DelAttribute(k) 287 } 288 289 return nil 290 } 291 292 // a group of key-*ResourceAttrDiff pairs from the same flatmapped container 293 type flatAttrDiff map[string]*ResourceAttrDiff 294 295 // we need to keep all keys if any of them have a diff 296 func (f flatAttrDiff) keepDiff() bool { 297 for _, v := range f { 298 if !v.Empty() && !v.NewComputed { 299 return true 300 } 301 } 302 return false 303 } 304 305 // sets, lists and maps need to be compared for diff inclusion as a whole, so 306 // group the flatmapped keys together for easier comparison. 307 func groupContainers(d *InstanceDiff) map[string]flatAttrDiff { 308 isIndex := multiVal.MatchString 309 containers := map[string]flatAttrDiff{} 310 attrs := d.CopyAttributes() 311 // we need to loop once to find the index key 312 for k := range attrs { 313 if isIndex(k) { 314 // add the key, always including the final dot to fully qualify it 315 containers[k[:len(k)-1]] = flatAttrDiff{} 316 } 317 } 318 319 // loop again to find all the sub keys 320 for prefix, values := range containers { 321 for k, attrDiff := range attrs { 322 // we include the index value as well, since it could be part of the diff 323 if strings.HasPrefix(k, prefix) { 324 values[k] = attrDiff 325 } 326 } 327 } 328 329 return containers 330 } 331 332 // EvalDiffDestroy is an EvalNode implementation that returns a plain 333 // destroy diff. 334 type EvalDiffDestroy struct { 335 Info *InstanceInfo 336 State **InstanceState 337 Output **InstanceDiff 338 } 339 340 // TODO: test 341 func (n *EvalDiffDestroy) Eval(ctx EvalContext) (interface{}, error) { 342 state := *n.State 343 344 // If there is no state or we don't have an ID, we're already destroyed 345 if state == nil || state.ID == "" { 346 return nil, nil 347 } 348 349 // Call pre-diff hook 350 err := ctx.Hook(func(h Hook) (HookAction, error) { 351 return h.PreDiff(n.Info, state) 352 }) 353 if err != nil { 354 return nil, err 355 } 356 357 // The diff 358 diff := &InstanceDiff{Destroy: true} 359 360 // Call post-diff hook 361 err = ctx.Hook(func(h Hook) (HookAction, error) { 362 return h.PostDiff(n.Info, diff) 363 }) 364 if err != nil { 365 return nil, err 366 } 367 368 // Update our output 369 *n.Output = diff 370 371 return nil, nil 372 } 373 374 // EvalDiffDestroyModule is an EvalNode implementation that writes the diff to 375 // the full diff. 376 type EvalDiffDestroyModule struct { 377 Path []string 378 } 379 380 // TODO: test 381 func (n *EvalDiffDestroyModule) Eval(ctx EvalContext) (interface{}, error) { 382 diff, lock := ctx.Diff() 383 384 // Acquire the lock so that we can do this safely concurrently 385 lock.Lock() 386 defer lock.Unlock() 387 388 // Write the diff 389 modDiff := diff.ModuleByPath(n.Path) 390 if modDiff == nil { 391 modDiff = diff.AddModule(n.Path) 392 } 393 modDiff.Destroy = true 394 395 return nil, nil 396 } 397 398 // EvalFilterDiff is an EvalNode implementation that filters the diff 399 // according to some filter. 400 type EvalFilterDiff struct { 401 // Input and output 402 Diff **InstanceDiff 403 Output **InstanceDiff 404 405 // Destroy, if true, will only include a destroy diff if it is set. 406 Destroy bool 407 } 408 409 func (n *EvalFilterDiff) Eval(ctx EvalContext) (interface{}, error) { 410 if *n.Diff == nil { 411 return nil, nil 412 } 413 414 input := *n.Diff 415 result := new(InstanceDiff) 416 417 if n.Destroy { 418 if input.GetDestroy() || input.RequiresNew() { 419 result.SetDestroy(true) 420 } 421 } 422 423 if n.Output != nil { 424 *n.Output = result 425 } 426 427 return nil, nil 428 } 429 430 // EvalReadDiff is an EvalNode implementation that writes the diff to 431 // the full diff. 432 type EvalReadDiff struct { 433 Name string 434 Diff **InstanceDiff 435 } 436 437 func (n *EvalReadDiff) Eval(ctx EvalContext) (interface{}, error) { 438 diff, lock := ctx.Diff() 439 440 // Acquire the lock so that we can do this safely concurrently 441 lock.Lock() 442 defer lock.Unlock() 443 444 // Write the diff 445 modDiff := diff.ModuleByPath(ctx.Path()) 446 if modDiff == nil { 447 return nil, nil 448 } 449 450 *n.Diff = modDiff.Resources[n.Name] 451 452 return nil, nil 453 } 454 455 // EvalWriteDiff is an EvalNode implementation that writes the diff to 456 // the full diff. 457 type EvalWriteDiff struct { 458 Name string 459 Diff **InstanceDiff 460 } 461 462 // TODO: test 463 func (n *EvalWriteDiff) Eval(ctx EvalContext) (interface{}, error) { 464 diff, lock := ctx.Diff() 465 466 // The diff to write, if its empty it should write nil 467 var diffVal *InstanceDiff 468 if n.Diff != nil { 469 diffVal = *n.Diff 470 } 471 if diffVal.Empty() { 472 diffVal = nil 473 } 474 475 // Acquire the lock so that we can do this safely concurrently 476 lock.Lock() 477 defer lock.Unlock() 478 479 // Write the diff 480 modDiff := diff.ModuleByPath(ctx.Path()) 481 if modDiff == nil { 482 modDiff = diff.AddModule(ctx.Path()) 483 } 484 if diffVal != nil { 485 modDiff.Resources[n.Name] = diffVal 486 } else { 487 delete(modDiff.Resources, n.Name) 488 } 489 490 return nil, nil 491 }