github.com/skyscape-cloud-services/terraform@v0.9.2-0.20170609144644-7ece028a1747/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 // 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 184 func (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 280 type flatAttrDiff map[string]*ResourceAttrDiff 281 282 // we need to keep all keys if any of them have a diff 283 func (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. 294 func 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. 321 type EvalDiffDestroy struct { 322 Info *InstanceInfo 323 State **InstanceState 324 Output **InstanceDiff 325 } 326 327 // TODO: test 328 func (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. 363 type EvalDiffDestroyModule struct { 364 Path []string 365 } 366 367 // TODO: test 368 func (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. 387 type 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 396 func (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. 419 type EvalReadDiff struct { 420 Name string 421 Diff **InstanceDiff 422 } 423 424 func (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. 444 type EvalWriteDiff struct { 445 Name string 446 Diff **InstanceDiff 447 } 448 449 // TODO: test 450 func (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 }