github.com/tomaszheflik/terraform@v0.7.3-0.20160827060421-32f990b41594/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 Info *InstanceInfo 73 Config **ResourceConfig 74 Provider *ResourceProvider 75 Diff **InstanceDiff 76 State **InstanceState 77 OutputDiff **InstanceDiff 78 OutputState **InstanceState 79 80 // Resource is needed to fetch the ignore_changes list so we can 81 // filter user-requested ignored attributes from the diff. 82 Resource *config.Resource 83 } 84 85 // TODO: test 86 func (n *EvalDiff) Eval(ctx EvalContext) (interface{}, error) { 87 state := *n.State 88 config := *n.Config 89 provider := *n.Provider 90 91 // Call pre-diff hook 92 err := ctx.Hook(func(h Hook) (HookAction, error) { 93 return h.PreDiff(n.Info, state) 94 }) 95 if err != nil { 96 return nil, err 97 } 98 99 // The state for the diff must never be nil 100 diffState := state 101 if diffState == nil { 102 diffState = new(InstanceState) 103 } 104 diffState.init() 105 106 // Diff! 107 diff, err := provider.Diff(n.Info, diffState, config) 108 if err != nil { 109 return nil, err 110 } 111 if diff == nil { 112 diff = new(InstanceDiff) 113 } 114 115 // Preserve the DestroyTainted flag 116 if n.Diff != nil { 117 diff.SetTainted((*n.Diff).GetDestroyTainted()) 118 } 119 120 // Require a destroy if there is an ID and it requires new. 121 if diff.RequiresNew() && state != nil && state.ID != "" { 122 diff.SetDestroy(true) 123 } 124 125 // If we're creating a new resource, compute its ID 126 if diff.RequiresNew() || state == nil || state.ID == "" { 127 var oldID string 128 if state != nil { 129 oldID = state.Attributes["id"] 130 } 131 132 // Add diff to compute new ID 133 diff.init() 134 diff.SetAttribute("id", &ResourceAttrDiff{ 135 Old: oldID, 136 NewComputed: true, 137 RequiresNew: true, 138 Type: DiffAttrOutput, 139 }) 140 } 141 142 if err := n.processIgnoreChanges(diff); err != nil { 143 return nil, err 144 } 145 146 // Call post-refresh hook 147 err = ctx.Hook(func(h Hook) (HookAction, error) { 148 return h.PostDiff(n.Info, diff) 149 }) 150 if err != nil { 151 return nil, err 152 } 153 154 // Update our output 155 *n.OutputDiff = diff 156 157 // Update the state if we care 158 if n.OutputState != nil { 159 *n.OutputState = state 160 161 // Merge our state so that the state is updated with our plan 162 if !diff.Empty() && n.OutputState != nil { 163 *n.OutputState = state.MergeDiff(diff) 164 } 165 } 166 167 return nil, nil 168 } 169 170 func (n *EvalDiff) processIgnoreChanges(diff *InstanceDiff) error { 171 if diff == nil || n.Resource == nil || n.Resource.Id() == "" { 172 return nil 173 } 174 ignoreChanges := n.Resource.Lifecycle.IgnoreChanges 175 176 if len(ignoreChanges) == 0 { 177 return nil 178 } 179 180 changeType := diff.ChangeType() 181 182 // If we're just creating the resource, we shouldn't alter the 183 // Diff at all 184 if changeType == DiffCreate { 185 return nil 186 } 187 188 ignorableAttrKeys := make(map[string]bool) 189 for _, ignoredKey := range ignoreChanges { 190 for k := range diff.CopyAttributes() { 191 if strings.HasPrefix(k, ignoredKey) { 192 ignorableAttrKeys[k] = true 193 } 194 } 195 } 196 197 // If we are replacing the resource, then we expect there to be a bunch of 198 // extraneous attribute diffs we need to filter out for the other 199 // non-requires-new attributes going from "" -> "configval" or "" -> 200 // "<computed>". Filtering these out allows us to see if we might be able to 201 // skip this diff altogether. 202 if changeType == DiffDestroyCreate { 203 for k, v := range diff.CopyAttributes() { 204 if v.Empty() || v.NewComputed { 205 ignorableAttrKeys[k] = true 206 } 207 } 208 209 // Here we emulate the implementation of diff.RequiresNew() with one small 210 // tweak, we ignore the "id" attribute diff that gets added by EvalDiff, 211 // since that was added in reaction to RequiresNew being true. 212 requiresNewAfterIgnores := false 213 for k, v := range diff.CopyAttributes() { 214 if k == "id" { 215 continue 216 } 217 if _, ok := ignorableAttrKeys[k]; ok { 218 continue 219 } 220 if v.RequiresNew == true { 221 requiresNewAfterIgnores = true 222 } 223 } 224 225 // If we still require resource replacement after ignores, we 226 // can't touch the diff, as all of the attributes will be 227 // required to process the replacement. 228 if requiresNewAfterIgnores { 229 return nil 230 } 231 232 // Here we undo the two reactions to RequireNew in EvalDiff - the "id" 233 // attribute diff and the Destroy boolean field 234 log.Printf("[DEBUG] Removing 'id' diff and setting Destroy to false " + 235 "because after ignore_changes, this diff no longer requires replacement") 236 diff.DelAttribute("id") 237 diff.SetDestroy(false) 238 } 239 240 // If we didn't hit any of our early exit conditions, we can filter the diff. 241 for k := range ignorableAttrKeys { 242 log.Printf("[DEBUG] [EvalIgnoreChanges] %s - Ignoring diff attribute: %s", 243 n.Resource.Id(), k) 244 diff.DelAttribute(k) 245 } 246 247 return nil 248 } 249 250 // EvalDiffDestroy is an EvalNode implementation that returns a plain 251 // destroy diff. 252 type EvalDiffDestroy struct { 253 Info *InstanceInfo 254 State **InstanceState 255 Output **InstanceDiff 256 } 257 258 // TODO: test 259 func (n *EvalDiffDestroy) Eval(ctx EvalContext) (interface{}, error) { 260 state := *n.State 261 262 // If there is no state or we don't have an ID, we're already destroyed 263 if state == nil || state.ID == "" { 264 return nil, nil 265 } 266 267 // Call pre-diff hook 268 err := ctx.Hook(func(h Hook) (HookAction, error) { 269 return h.PreDiff(n.Info, state) 270 }) 271 if err != nil { 272 return nil, err 273 } 274 275 // The diff 276 diff := &InstanceDiff{Destroy: true} 277 278 // Call post-diff hook 279 err = ctx.Hook(func(h Hook) (HookAction, error) { 280 return h.PostDiff(n.Info, diff) 281 }) 282 if err != nil { 283 return nil, err 284 } 285 286 // Update our output 287 *n.Output = diff 288 289 return nil, nil 290 } 291 292 // EvalDiffDestroyModule is an EvalNode implementation that writes the diff to 293 // the full diff. 294 type EvalDiffDestroyModule struct { 295 Path []string 296 } 297 298 // TODO: test 299 func (n *EvalDiffDestroyModule) Eval(ctx EvalContext) (interface{}, error) { 300 diff, lock := ctx.Diff() 301 302 // Acquire the lock so that we can do this safely concurrently 303 lock.Lock() 304 defer lock.Unlock() 305 306 // Write the diff 307 modDiff := diff.ModuleByPath(n.Path) 308 if modDiff == nil { 309 modDiff = diff.AddModule(n.Path) 310 } 311 modDiff.Destroy = true 312 313 return nil, nil 314 } 315 316 // EvalFilterDiff is an EvalNode implementation that filters the diff 317 // according to some filter. 318 type EvalFilterDiff struct { 319 // Input and output 320 Diff **InstanceDiff 321 Output **InstanceDiff 322 323 // Destroy, if true, will only include a destroy diff if it is set. 324 Destroy bool 325 } 326 327 func (n *EvalFilterDiff) Eval(ctx EvalContext) (interface{}, error) { 328 if *n.Diff == nil { 329 return nil, nil 330 } 331 332 input := *n.Diff 333 result := new(InstanceDiff) 334 335 if n.Destroy { 336 if input.GetDestroy() || input.RequiresNew() { 337 result.SetDestroy(true) 338 } 339 } 340 341 if n.Output != nil { 342 *n.Output = result 343 } 344 345 return nil, nil 346 } 347 348 // EvalReadDiff is an EvalNode implementation that writes the diff to 349 // the full diff. 350 type EvalReadDiff struct { 351 Name string 352 Diff **InstanceDiff 353 } 354 355 func (n *EvalReadDiff) Eval(ctx EvalContext) (interface{}, error) { 356 diff, lock := ctx.Diff() 357 358 // Acquire the lock so that we can do this safely concurrently 359 lock.Lock() 360 defer lock.Unlock() 361 362 // Write the diff 363 modDiff := diff.ModuleByPath(ctx.Path()) 364 if modDiff == nil { 365 return nil, nil 366 } 367 368 *n.Diff = modDiff.Resources[n.Name] 369 370 return nil, nil 371 } 372 373 // EvalWriteDiff is an EvalNode implementation that writes the diff to 374 // the full diff. 375 type EvalWriteDiff struct { 376 Name string 377 Diff **InstanceDiff 378 } 379 380 // TODO: test 381 func (n *EvalWriteDiff) Eval(ctx EvalContext) (interface{}, error) { 382 diff, lock := ctx.Diff() 383 384 // The diff to write, if its empty it should write nil 385 var diffVal *InstanceDiff 386 if n.Diff != nil { 387 diffVal = *n.Diff 388 } 389 if diffVal.Empty() { 390 diffVal = nil 391 } 392 393 // Acquire the lock so that we can do this safely concurrently 394 lock.Lock() 395 defer lock.Unlock() 396 397 // Write the diff 398 modDiff := diff.ModuleByPath(ctx.Path()) 399 if modDiff == nil { 400 modDiff = diff.AddModule(ctx.Path()) 401 } 402 if diffVal != nil { 403 modDiff.Resources[n.Name] = diffVal 404 } else { 405 delete(modDiff.Resources, n.Name) 406 } 407 408 return nil, nil 409 }