github.com/turtlemonvh/terraform@v0.6.9-0.20151204001754-8e40b6b855e8/terraform/transform_resource.go (about) 1 package terraform 2 3 import ( 4 "fmt" 5 "strings" 6 7 "github.com/hashicorp/terraform/config" 8 "github.com/hashicorp/terraform/dag" 9 ) 10 11 // ResourceCountTransformer is a GraphTransformer that expands the count 12 // out for a specific resource. 13 type ResourceCountTransformer struct { 14 Resource *config.Resource 15 Destroy bool 16 Targets []ResourceAddress 17 } 18 19 func (t *ResourceCountTransformer) Transform(g *Graph) error { 20 // Expand the resource count 21 count, err := t.Resource.Count() 22 if err != nil { 23 return err 24 } 25 26 // Don't allow the count to be negative 27 if count < 0 { 28 return fmt.Errorf("negative count: %d", count) 29 } 30 31 // For each count, build and add the node 32 nodes := make([]dag.Vertex, 0, count) 33 for i := 0; i < count; i++ { 34 // Set the index. If our count is 1 we special case it so that 35 // we handle the "resource.0" and "resource" boundary properly. 36 index := i 37 if count == 1 { 38 index = -1 39 } 40 41 // Save the node for later so we can do connections. Make the 42 // proper node depending on if we're just a destroy node or if 43 // were a regular node. 44 var node dag.Vertex = &graphNodeExpandedResource{ 45 Index: index, 46 Resource: t.Resource, 47 Path: g.Path, 48 } 49 if t.Destroy { 50 node = &graphNodeExpandedResourceDestroy{ 51 graphNodeExpandedResource: node.(*graphNodeExpandedResource), 52 } 53 } 54 55 // Skip nodes if targeting excludes them 56 if !t.nodeIsTargeted(node) { 57 continue 58 } 59 60 // Add the node now 61 nodes = append(nodes, node) 62 g.Add(node) 63 } 64 65 // Make the dependency connections 66 for _, n := range nodes { 67 // Connect the dependents. We ignore the return value for missing 68 // dependents since that should've been caught at a higher level. 69 g.ConnectDependent(n) 70 } 71 72 return nil 73 } 74 75 func (t *ResourceCountTransformer) nodeIsTargeted(node dag.Vertex) bool { 76 // no targets specified, everything stays in the graph 77 if len(t.Targets) == 0 { 78 return true 79 } 80 addressable, ok := node.(GraphNodeAddressable) 81 if !ok { 82 return false 83 } 84 85 addr := addressable.ResourceAddress() 86 for _, targetAddr := range t.Targets { 87 if targetAddr.Equals(addr) { 88 return true 89 } 90 } 91 return false 92 } 93 94 type graphNodeExpandedResource struct { 95 Index int 96 Resource *config.Resource 97 Path []string 98 } 99 100 func (n *graphNodeExpandedResource) Name() string { 101 if n.Index == -1 { 102 return n.Resource.Id() 103 } 104 105 return fmt.Sprintf("%s #%d", n.Resource.Id(), n.Index) 106 } 107 108 // GraphNodeAddressable impl. 109 func (n *graphNodeExpandedResource) ResourceAddress() *ResourceAddress { 110 // We want this to report the logical index properly, so we must undo the 111 // special case from the expand 112 index := n.Index 113 if index == -1 { 114 index = 0 115 } 116 return &ResourceAddress{ 117 Path: n.Path[1:], 118 Index: index, 119 InstanceType: TypePrimary, 120 Name: n.Resource.Name, 121 Type: n.Resource.Type, 122 } 123 } 124 125 // graphNodeConfig impl. 126 func (n *graphNodeExpandedResource) ConfigType() GraphNodeConfigType { 127 return GraphNodeConfigTypeResource 128 } 129 130 // GraphNodeDependable impl. 131 func (n *graphNodeExpandedResource) DependableName() []string { 132 return []string{ 133 n.Resource.Id(), 134 n.stateId(), 135 } 136 } 137 138 // GraphNodeDependent impl. 139 func (n *graphNodeExpandedResource) DependentOn() []string { 140 configNode := &GraphNodeConfigResource{Resource: n.Resource} 141 result := configNode.DependentOn() 142 143 // Walk the variables to find any count-specific variables we depend on. 144 configNode.VarWalk(func(v config.InterpolatedVariable) { 145 rv, ok := v.(*config.ResourceVariable) 146 if !ok { 147 return 148 } 149 150 // We only want ourselves 151 if rv.ResourceId() != n.Resource.Id() { 152 return 153 } 154 155 // If this isn't a multi-access (which shouldn't be allowed but 156 // is verified elsewhere), then we depend on the specific count 157 // of this resource, ignoring ourself (which again should be 158 // validated elsewhere). 159 if rv.Index > -1 { 160 id := fmt.Sprintf("%s.%d", rv.ResourceId(), rv.Index) 161 if id != n.stateId() && id != n.stateId()+".0" { 162 result = append(result, id) 163 } 164 } 165 }) 166 167 return result 168 } 169 170 // GraphNodeProviderConsumer 171 func (n *graphNodeExpandedResource) ProvidedBy() []string { 172 return []string{resourceProvider(n.Resource.Type, n.Resource.Provider)} 173 } 174 175 func (n *graphNodeExpandedResource) StateDependencies() []string { 176 depsRaw := n.DependentOn() 177 deps := make([]string, 0, len(depsRaw)) 178 for _, d := range depsRaw { 179 // Ignore any variable dependencies 180 if strings.HasPrefix(d, "var.") { 181 continue 182 } 183 184 // This is sad. The dependencies are currently in the format of 185 // "module.foo.bar" (the full field). This strips the field off. 186 if strings.HasPrefix(d, "module.") { 187 parts := strings.SplitN(d, ".", 3) 188 d = strings.Join(parts[0:2], ".") 189 } 190 deps = append(deps, d) 191 } 192 193 return deps 194 } 195 196 // GraphNodeEvalable impl. 197 func (n *graphNodeExpandedResource) EvalTree() EvalNode { 198 var diff *InstanceDiff 199 var provider ResourceProvider 200 var resourceConfig *ResourceConfig 201 var state *InstanceState 202 203 // Build the resource. If we aren't part of a multi-resource, then 204 // we still consider ourselves as count index zero. 205 index := n.Index 206 if index < 0 { 207 index = 0 208 } 209 resource := &Resource{ 210 Name: n.Resource.Name, 211 Type: n.Resource.Type, 212 CountIndex: index, 213 } 214 215 seq := &EvalSequence{Nodes: make([]EvalNode, 0, 5)} 216 217 // Validate the resource 218 vseq := &EvalSequence{Nodes: make([]EvalNode, 0, 5)} 219 vseq.Nodes = append(vseq.Nodes, &EvalGetProvider{ 220 Name: n.ProvidedBy()[0], 221 Output: &provider, 222 }) 223 vseq.Nodes = append(vseq.Nodes, &EvalInterpolate{ 224 Config: n.Resource.RawConfig.Copy(), 225 Resource: resource, 226 Output: &resourceConfig, 227 }) 228 vseq.Nodes = append(vseq.Nodes, &EvalValidateResource{ 229 Provider: &provider, 230 Config: &resourceConfig, 231 ResourceName: n.Resource.Name, 232 ResourceType: n.Resource.Type, 233 }) 234 235 // Validate all the provisioners 236 for _, p := range n.Resource.Provisioners { 237 var provisioner ResourceProvisioner 238 vseq.Nodes = append(vseq.Nodes, &EvalGetProvisioner{ 239 Name: p.Type, 240 Output: &provisioner, 241 }, &EvalInterpolate{ 242 Config: p.RawConfig.Copy(), 243 Resource: resource, 244 Output: &resourceConfig, 245 }, &EvalValidateProvisioner{ 246 Provisioner: &provisioner, 247 Config: &resourceConfig, 248 }) 249 } 250 251 // Add the validation operations 252 seq.Nodes = append(seq.Nodes, &EvalOpFilter{ 253 Ops: []walkOperation{walkValidate}, 254 Node: vseq, 255 }) 256 257 // Build instance info 258 info := n.instanceInfo() 259 seq.Nodes = append(seq.Nodes, &EvalInstanceInfo{Info: info}) 260 261 // Refresh the resource 262 seq.Nodes = append(seq.Nodes, &EvalOpFilter{ 263 Ops: []walkOperation{walkRefresh}, 264 Node: &EvalSequence{ 265 Nodes: []EvalNode{ 266 &EvalGetProvider{ 267 Name: n.ProvidedBy()[0], 268 Output: &provider, 269 }, 270 &EvalReadState{ 271 Name: n.stateId(), 272 Output: &state, 273 }, 274 &EvalRefresh{ 275 Info: info, 276 Provider: &provider, 277 State: &state, 278 Output: &state, 279 }, 280 &EvalWriteState{ 281 Name: n.stateId(), 282 ResourceType: n.Resource.Type, 283 Provider: n.Resource.Provider, 284 Dependencies: n.StateDependencies(), 285 State: &state, 286 }, 287 }, 288 }, 289 }) 290 291 // Diff the resource 292 seq.Nodes = append(seq.Nodes, &EvalOpFilter{ 293 Ops: []walkOperation{walkPlan}, 294 Node: &EvalSequence{ 295 Nodes: []EvalNode{ 296 &EvalInterpolate{ 297 Config: n.Resource.RawConfig.Copy(), 298 Resource: resource, 299 Output: &resourceConfig, 300 }, 301 &EvalGetProvider{ 302 Name: n.ProvidedBy()[0], 303 Output: &provider, 304 }, 305 &EvalReadState{ 306 Name: n.stateId(), 307 Output: &state, 308 }, 309 &EvalDiff{ 310 Info: info, 311 Config: &resourceConfig, 312 Provider: &provider, 313 State: &state, 314 Output: &diff, 315 OutputState: &state, 316 }, 317 &EvalCheckPreventDestroy{ 318 Resource: n.Resource, 319 Diff: &diff, 320 }, 321 &EvalIgnoreChanges{ 322 Resource: n.Resource, 323 Diff: &diff, 324 }, 325 &EvalWriteState{ 326 Name: n.stateId(), 327 ResourceType: n.Resource.Type, 328 Provider: n.Resource.Provider, 329 Dependencies: n.StateDependencies(), 330 State: &state, 331 }, 332 &EvalDiffTainted{ 333 Diff: &diff, 334 Name: n.stateId(), 335 }, 336 &EvalWriteDiff{ 337 Name: n.stateId(), 338 Diff: &diff, 339 }, 340 }, 341 }, 342 }) 343 344 // Diff the resource for destruction 345 seq.Nodes = append(seq.Nodes, &EvalOpFilter{ 346 Ops: []walkOperation{walkPlanDestroy}, 347 Node: &EvalSequence{ 348 Nodes: []EvalNode{ 349 &EvalReadState{ 350 Name: n.stateId(), 351 Output: &state, 352 }, 353 &EvalDiffDestroy{ 354 Info: info, 355 State: &state, 356 Output: &diff, 357 }, 358 &EvalCheckPreventDestroy{ 359 Resource: n.Resource, 360 Diff: &diff, 361 }, 362 &EvalWriteDiff{ 363 Name: n.stateId(), 364 Diff: &diff, 365 }, 366 }, 367 }, 368 }) 369 370 // Apply 371 var diffApply *InstanceDiff 372 var err error 373 var createNew, tainted bool 374 var createBeforeDestroyEnabled bool 375 seq.Nodes = append(seq.Nodes, &EvalOpFilter{ 376 Ops: []walkOperation{walkApply, walkDestroy}, 377 Node: &EvalSequence{ 378 Nodes: []EvalNode{ 379 // Get the saved diff for apply 380 &EvalReadDiff{ 381 Name: n.stateId(), 382 Diff: &diffApply, 383 }, 384 385 // We don't want to do any destroys 386 &EvalIf{ 387 If: func(ctx EvalContext) (bool, error) { 388 if diffApply == nil { 389 return true, EvalEarlyExitError{} 390 } 391 392 if diffApply.Destroy && len(diffApply.Attributes) == 0 { 393 return true, EvalEarlyExitError{} 394 } 395 396 diffApply.Destroy = false 397 return true, nil 398 }, 399 Then: EvalNoop{}, 400 }, 401 402 &EvalIf{ 403 If: func(ctx EvalContext) (bool, error) { 404 destroy := false 405 if diffApply != nil { 406 destroy = diffApply.Destroy || diffApply.RequiresNew() 407 } 408 409 createBeforeDestroyEnabled = 410 n.Resource.Lifecycle.CreateBeforeDestroy && 411 destroy 412 413 return createBeforeDestroyEnabled, nil 414 }, 415 Then: &EvalDeposeState{ 416 Name: n.stateId(), 417 }, 418 }, 419 420 &EvalInterpolate{ 421 Config: n.Resource.RawConfig.Copy(), 422 Resource: resource, 423 Output: &resourceConfig, 424 }, 425 &EvalGetProvider{ 426 Name: n.ProvidedBy()[0], 427 Output: &provider, 428 }, 429 &EvalReadState{ 430 Name: n.stateId(), 431 Output: &state, 432 }, 433 434 &EvalDiff{ 435 Info: info, 436 Config: &resourceConfig, 437 Provider: &provider, 438 State: &state, 439 Output: &diffApply, 440 }, 441 442 // Get the saved diff 443 &EvalReadDiff{ 444 Name: n.stateId(), 445 Diff: &diff, 446 }, 447 448 // Compare the diffs 449 &EvalCompareDiff{ 450 Info: info, 451 One: &diff, 452 Two: &diffApply, 453 }, 454 455 &EvalGetProvider{ 456 Name: n.ProvidedBy()[0], 457 Output: &provider, 458 }, 459 &EvalReadState{ 460 Name: n.stateId(), 461 Output: &state, 462 }, 463 &EvalApply{ 464 Info: info, 465 State: &state, 466 Diff: &diffApply, 467 Provider: &provider, 468 Output: &state, 469 Error: &err, 470 CreateNew: &createNew, 471 }, 472 &EvalWriteState{ 473 Name: n.stateId(), 474 ResourceType: n.Resource.Type, 475 Provider: n.Resource.Provider, 476 Dependencies: n.StateDependencies(), 477 State: &state, 478 }, 479 &EvalApplyProvisioners{ 480 Info: info, 481 State: &state, 482 Resource: n.Resource, 483 InterpResource: resource, 484 CreateNew: &createNew, 485 Tainted: &tainted, 486 Error: &err, 487 }, 488 &EvalIf{ 489 If: func(ctx EvalContext) (bool, error) { 490 if createBeforeDestroyEnabled { 491 tainted = err != nil 492 } 493 494 failure := tainted || err != nil 495 return createBeforeDestroyEnabled && failure, nil 496 }, 497 Then: &EvalUndeposeState{ 498 Name: n.stateId(), 499 }, 500 }, 501 502 // We clear the diff out here so that future nodes 503 // don't see a diff that is already complete. There 504 // is no longer a diff! 505 &EvalWriteDiff{ 506 Name: n.stateId(), 507 Diff: nil, 508 }, 509 510 &EvalIf{ 511 If: func(ctx EvalContext) (bool, error) { 512 return tainted, nil 513 }, 514 Then: &EvalSequence{ 515 Nodes: []EvalNode{ 516 &EvalWriteStateTainted{ 517 Name: n.stateId(), 518 ResourceType: n.Resource.Type, 519 Provider: n.Resource.Provider, 520 Dependencies: n.StateDependencies(), 521 State: &state, 522 Index: -1, 523 }, 524 &EvalIf{ 525 If: func(ctx EvalContext) (bool, error) { 526 return !n.Resource.Lifecycle.CreateBeforeDestroy, nil 527 }, 528 Then: &EvalClearPrimaryState{ 529 Name: n.stateId(), 530 }, 531 }, 532 }, 533 }, 534 Else: &EvalWriteState{ 535 Name: n.stateId(), 536 ResourceType: n.Resource.Type, 537 Provider: n.Resource.Provider, 538 Dependencies: n.StateDependencies(), 539 State: &state, 540 }, 541 }, 542 &EvalApplyPost{ 543 Info: info, 544 State: &state, 545 Error: &err, 546 }, 547 &EvalUpdateStateHook{}, 548 }, 549 }, 550 }) 551 552 return seq 553 } 554 555 // instanceInfo is used for EvalTree. 556 func (n *graphNodeExpandedResource) instanceInfo() *InstanceInfo { 557 return &InstanceInfo{Id: n.stateId(), Type: n.Resource.Type} 558 } 559 560 // stateId is the name used for the state key 561 func (n *graphNodeExpandedResource) stateId() string { 562 if n.Index == -1 { 563 return n.Resource.Id() 564 } 565 566 return fmt.Sprintf("%s.%d", n.Resource.Id(), n.Index) 567 } 568 569 // GraphNodeStateRepresentative impl. 570 func (n *graphNodeExpandedResource) StateId() []string { 571 return []string{n.stateId()} 572 } 573 574 // graphNodeExpandedResourceDestroy represents an expanded resource that 575 // is to be destroyed. 576 type graphNodeExpandedResourceDestroy struct { 577 *graphNodeExpandedResource 578 } 579 580 func (n *graphNodeExpandedResourceDestroy) Name() string { 581 return fmt.Sprintf("%s (destroy)", n.graphNodeExpandedResource.Name()) 582 } 583 584 // graphNodeConfig impl. 585 func (n *graphNodeExpandedResourceDestroy) ConfigType() GraphNodeConfigType { 586 return GraphNodeConfigTypeResource 587 } 588 589 // GraphNodeEvalable impl. 590 func (n *graphNodeExpandedResourceDestroy) EvalTree() EvalNode { 591 info := n.instanceInfo() 592 593 var diffApply *InstanceDiff 594 var provider ResourceProvider 595 var state *InstanceState 596 var err error 597 return &EvalOpFilter{ 598 Ops: []walkOperation{walkApply, walkDestroy}, 599 Node: &EvalSequence{ 600 Nodes: []EvalNode{ 601 // Get the saved diff for apply 602 &EvalReadDiff{ 603 Name: n.stateId(), 604 Diff: &diffApply, 605 }, 606 607 // Filter the diff so we only get the destroy 608 &EvalFilterDiff{ 609 Diff: &diffApply, 610 Output: &diffApply, 611 Destroy: true, 612 }, 613 614 // If we're not destroying, then compare diffs 615 &EvalIf{ 616 If: func(ctx EvalContext) (bool, error) { 617 if diffApply != nil && diffApply.Destroy { 618 return true, nil 619 } 620 621 return true, EvalEarlyExitError{} 622 }, 623 Then: EvalNoop{}, 624 }, 625 626 &EvalGetProvider{ 627 Name: n.ProvidedBy()[0], 628 Output: &provider, 629 }, 630 &EvalReadState{ 631 Name: n.stateId(), 632 Output: &state, 633 }, 634 &EvalRequireState{ 635 State: &state, 636 }, 637 &EvalApply{ 638 Info: info, 639 State: &state, 640 Diff: &diffApply, 641 Provider: &provider, 642 Output: &state, 643 Error: &err, 644 }, 645 &EvalWriteState{ 646 Name: n.stateId(), 647 ResourceType: n.Resource.Type, 648 Provider: n.Resource.Provider, 649 Dependencies: n.StateDependencies(), 650 State: &state, 651 }, 652 &EvalApplyPost{ 653 Info: info, 654 State: &state, 655 Error: &err, 656 }, 657 }, 658 }, 659 } 660 }