github.com/ricardclau/terraform@v0.6.17-0.20160519222547-283e3ae6b5a9/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 Mode: n.Resource.Mode, 123 } 124 } 125 126 // graphNodeConfig impl. 127 func (n *graphNodeExpandedResource) ConfigType() GraphNodeConfigType { 128 return GraphNodeConfigTypeResource 129 } 130 131 // GraphNodeDependable impl. 132 func (n *graphNodeExpandedResource) DependableName() []string { 133 return []string{ 134 n.Resource.Id(), 135 n.stateId(), 136 } 137 } 138 139 // GraphNodeDependent impl. 140 func (n *graphNodeExpandedResource) DependentOn() []string { 141 configNode := &GraphNodeConfigResource{Resource: n.Resource} 142 result := configNode.DependentOn() 143 144 // Walk the variables to find any count-specific variables we depend on. 145 configNode.VarWalk(func(v config.InterpolatedVariable) { 146 rv, ok := v.(*config.ResourceVariable) 147 if !ok { 148 return 149 } 150 151 // We only want ourselves 152 if rv.ResourceId() != n.Resource.Id() { 153 return 154 } 155 156 // If this isn't a multi-access (which shouldn't be allowed but 157 // is verified elsewhere), then we depend on the specific count 158 // of this resource, ignoring ourself (which again should be 159 // validated elsewhere). 160 if rv.Index > -1 { 161 id := fmt.Sprintf("%s.%d", rv.ResourceId(), rv.Index) 162 if id != n.stateId() && id != n.stateId()+".0" { 163 result = append(result, id) 164 } 165 } 166 }) 167 168 return result 169 } 170 171 // GraphNodeProviderConsumer 172 func (n *graphNodeExpandedResource) ProvidedBy() []string { 173 return []string{resourceProvider(n.Resource.Type, n.Resource.Provider)} 174 } 175 176 func (n *graphNodeExpandedResource) StateDependencies() []string { 177 depsRaw := n.DependentOn() 178 deps := make([]string, 0, len(depsRaw)) 179 for _, d := range depsRaw { 180 // Ignore any variable dependencies 181 if strings.HasPrefix(d, "var.") { 182 continue 183 } 184 185 // This is sad. The dependencies are currently in the format of 186 // "module.foo.bar" (the full field). This strips the field off. 187 if strings.HasPrefix(d, "module.") { 188 parts := strings.SplitN(d, ".", 3) 189 d = strings.Join(parts[0:2], ".") 190 } 191 deps = append(deps, d) 192 } 193 194 return deps 195 } 196 197 // GraphNodeEvalable impl. 198 func (n *graphNodeExpandedResource) EvalTree() EvalNode { 199 var provider ResourceProvider 200 var resourceConfig *ResourceConfig 201 202 // Build the resource. If we aren't part of a multi-resource, then 203 // we still consider ourselves as count index zero. 204 index := n.Index 205 if index < 0 { 206 index = 0 207 } 208 resource := &Resource{ 209 Name: n.Resource.Name, 210 Type: n.Resource.Type, 211 CountIndex: index, 212 } 213 214 seq := &EvalSequence{Nodes: make([]EvalNode, 0, 5)} 215 216 // Validate the resource 217 vseq := &EvalSequence{Nodes: make([]EvalNode, 0, 5)} 218 vseq.Nodes = append(vseq.Nodes, &EvalGetProvider{ 219 Name: n.ProvidedBy()[0], 220 Output: &provider, 221 }) 222 vseq.Nodes = append(vseq.Nodes, &EvalInterpolate{ 223 Config: n.Resource.RawConfig.Copy(), 224 Resource: resource, 225 Output: &resourceConfig, 226 }) 227 vseq.Nodes = append(vseq.Nodes, &EvalValidateResource{ 228 Provider: &provider, 229 Config: &resourceConfig, 230 ResourceName: n.Resource.Name, 231 ResourceType: n.Resource.Type, 232 ResourceMode: n.Resource.Mode, 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 // Each resource mode has its own lifecycle 262 switch n.Resource.Mode { 263 case config.ManagedResourceMode: 264 seq.Nodes = append( 265 seq.Nodes, 266 n.managedResourceEvalNodes(resource, info, resourceConfig)..., 267 ) 268 case config.DataResourceMode: 269 seq.Nodes = append( 270 seq.Nodes, 271 n.dataResourceEvalNodes(resource, info, resourceConfig)..., 272 ) 273 default: 274 panic(fmt.Errorf("unsupported resource mode %s", n.Resource.Mode)) 275 } 276 277 return seq 278 } 279 280 func (n *graphNodeExpandedResource) managedResourceEvalNodes(resource *Resource, info *InstanceInfo, resourceConfig *ResourceConfig) []EvalNode { 281 var diff *InstanceDiff 282 var provider ResourceProvider 283 var state *InstanceState 284 285 nodes := make([]EvalNode, 0, 5) 286 287 // Refresh the resource 288 nodes = append(nodes, &EvalOpFilter{ 289 Ops: []walkOperation{walkRefresh}, 290 Node: &EvalSequence{ 291 Nodes: []EvalNode{ 292 &EvalGetProvider{ 293 Name: n.ProvidedBy()[0], 294 Output: &provider, 295 }, 296 &EvalReadState{ 297 Name: n.stateId(), 298 Output: &state, 299 }, 300 &EvalRefresh{ 301 Info: info, 302 Provider: &provider, 303 State: &state, 304 Output: &state, 305 }, 306 &EvalWriteState{ 307 Name: n.stateId(), 308 ResourceType: n.Resource.Type, 309 Provider: n.Resource.Provider, 310 Dependencies: n.StateDependencies(), 311 State: &state, 312 }, 313 }, 314 }, 315 }) 316 317 // Diff the resource 318 nodes = append(nodes, &EvalOpFilter{ 319 Ops: []walkOperation{walkPlan}, 320 Node: &EvalSequence{ 321 Nodes: []EvalNode{ 322 &EvalInterpolate{ 323 Config: n.Resource.RawConfig.Copy(), 324 Resource: resource, 325 Output: &resourceConfig, 326 }, 327 &EvalGetProvider{ 328 Name: n.ProvidedBy()[0], 329 Output: &provider, 330 }, 331 &EvalReadState{ 332 Name: n.stateId(), 333 Output: &state, 334 }, 335 &EvalDiff{ 336 Info: info, 337 Config: &resourceConfig, 338 Provider: &provider, 339 State: &state, 340 Output: &diff, 341 OutputState: &state, 342 }, 343 &EvalCheckPreventDestroy{ 344 Resource: n.Resource, 345 Diff: &diff, 346 }, 347 &EvalIgnoreChanges{ 348 Resource: n.Resource, 349 Diff: &diff, 350 }, 351 &EvalWriteState{ 352 Name: n.stateId(), 353 ResourceType: n.Resource.Type, 354 Provider: n.Resource.Provider, 355 Dependencies: n.StateDependencies(), 356 State: &state, 357 }, 358 &EvalDiffTainted{ 359 Diff: &diff, 360 Name: n.stateId(), 361 }, 362 &EvalWriteDiff{ 363 Name: n.stateId(), 364 Diff: &diff, 365 }, 366 }, 367 }, 368 }) 369 370 // Diff the resource for destruction 371 nodes = append(nodes, &EvalOpFilter{ 372 Ops: []walkOperation{walkPlanDestroy}, 373 Node: &EvalSequence{ 374 Nodes: []EvalNode{ 375 &EvalReadState{ 376 Name: n.stateId(), 377 Output: &state, 378 }, 379 &EvalDiffDestroy{ 380 Info: info, 381 State: &state, 382 Output: &diff, 383 }, 384 &EvalCheckPreventDestroy{ 385 Resource: n.Resource, 386 Diff: &diff, 387 }, 388 &EvalWriteDiff{ 389 Name: n.stateId(), 390 Diff: &diff, 391 }, 392 }, 393 }, 394 }) 395 396 // Apply 397 var diffApply *InstanceDiff 398 var err error 399 var createNew, tainted bool 400 var createBeforeDestroyEnabled bool 401 var wasChangeType DiffChangeType 402 nodes = append(nodes, &EvalOpFilter{ 403 Ops: []walkOperation{walkApply, walkDestroy}, 404 Node: &EvalSequence{ 405 Nodes: []EvalNode{ 406 // Get the saved diff for apply 407 &EvalReadDiff{ 408 Name: n.stateId(), 409 Diff: &diffApply, 410 }, 411 412 // We don't want to do any destroys 413 &EvalIf{ 414 If: func(ctx EvalContext) (bool, error) { 415 if diffApply == nil { 416 return true, EvalEarlyExitError{} 417 } 418 419 if diffApply.Destroy && len(diffApply.Attributes) == 0 { 420 return true, EvalEarlyExitError{} 421 } 422 423 wasChangeType = diffApply.ChangeType() 424 diffApply.Destroy = false 425 return true, nil 426 }, 427 Then: EvalNoop{}, 428 }, 429 430 &EvalIf{ 431 If: func(ctx EvalContext) (bool, error) { 432 destroy := false 433 if diffApply != nil { 434 destroy = diffApply.Destroy || diffApply.RequiresNew() 435 } 436 437 createBeforeDestroyEnabled = 438 n.Resource.Lifecycle.CreateBeforeDestroy && 439 destroy 440 441 return createBeforeDestroyEnabled, nil 442 }, 443 Then: &EvalDeposeState{ 444 Name: n.stateId(), 445 }, 446 }, 447 448 &EvalInterpolate{ 449 Config: n.Resource.RawConfig.Copy(), 450 Resource: resource, 451 Output: &resourceConfig, 452 }, 453 &EvalGetProvider{ 454 Name: n.ProvidedBy()[0], 455 Output: &provider, 456 }, 457 &EvalReadState{ 458 Name: n.stateId(), 459 Output: &state, 460 }, 461 462 &EvalDiff{ 463 Info: info, 464 Config: &resourceConfig, 465 Provider: &provider, 466 State: &state, 467 Output: &diffApply, 468 }, 469 &EvalIgnoreChanges{ 470 Resource: n.Resource, 471 Diff: &diffApply, 472 WasChangeType: &wasChangeType, 473 }, 474 475 // Get the saved diff 476 &EvalReadDiff{ 477 Name: n.stateId(), 478 Diff: &diff, 479 }, 480 481 // Compare the diffs 482 &EvalCompareDiff{ 483 Info: info, 484 One: &diff, 485 Two: &diffApply, 486 }, 487 488 &EvalGetProvider{ 489 Name: n.ProvidedBy()[0], 490 Output: &provider, 491 }, 492 &EvalReadState{ 493 Name: n.stateId(), 494 Output: &state, 495 }, 496 &EvalApply{ 497 Info: info, 498 State: &state, 499 Diff: &diffApply, 500 Provider: &provider, 501 Output: &state, 502 Error: &err, 503 CreateNew: &createNew, 504 }, 505 &EvalWriteState{ 506 Name: n.stateId(), 507 ResourceType: n.Resource.Type, 508 Provider: n.Resource.Provider, 509 Dependencies: n.StateDependencies(), 510 State: &state, 511 }, 512 &EvalApplyProvisioners{ 513 Info: info, 514 State: &state, 515 Resource: n.Resource, 516 InterpResource: resource, 517 CreateNew: &createNew, 518 Tainted: &tainted, 519 Error: &err, 520 }, 521 &EvalIf{ 522 If: func(ctx EvalContext) (bool, error) { 523 if createBeforeDestroyEnabled { 524 tainted = err != nil 525 } 526 527 failure := tainted || err != nil 528 return createBeforeDestroyEnabled && failure, nil 529 }, 530 Then: &EvalUndeposeState{ 531 Name: n.stateId(), 532 }, 533 }, 534 535 // We clear the diff out here so that future nodes 536 // don't see a diff that is already complete. There 537 // is no longer a diff! 538 &EvalWriteDiff{ 539 Name: n.stateId(), 540 Diff: nil, 541 }, 542 543 &EvalIf{ 544 If: func(ctx EvalContext) (bool, error) { 545 return tainted, nil 546 }, 547 Then: &EvalSequence{ 548 Nodes: []EvalNode{ 549 &EvalWriteStateTainted{ 550 Name: n.stateId(), 551 ResourceType: n.Resource.Type, 552 Provider: n.Resource.Provider, 553 Dependencies: n.StateDependencies(), 554 State: &state, 555 Index: -1, 556 }, 557 &EvalIf{ 558 If: func(ctx EvalContext) (bool, error) { 559 return !n.Resource.Lifecycle.CreateBeforeDestroy, nil 560 }, 561 Then: &EvalClearPrimaryState{ 562 Name: n.stateId(), 563 }, 564 }, 565 }, 566 }, 567 Else: &EvalWriteState{ 568 Name: n.stateId(), 569 ResourceType: n.Resource.Type, 570 Provider: n.Resource.Provider, 571 Dependencies: n.StateDependencies(), 572 State: &state, 573 }, 574 }, 575 &EvalApplyPost{ 576 Info: info, 577 State: &state, 578 Error: &err, 579 }, 580 &EvalUpdateStateHook{}, 581 }, 582 }, 583 }) 584 585 return nodes 586 } 587 588 func (n *graphNodeExpandedResource) dataResourceEvalNodes(resource *Resource, info *InstanceInfo, resourceConfig *ResourceConfig) []EvalNode { 589 //var diff *InstanceDiff 590 var provider ResourceProvider 591 var config *ResourceConfig 592 var diff *InstanceDiff 593 var state *InstanceState 594 595 nodes := make([]EvalNode, 0, 5) 596 597 // Refresh the resource 598 // TODO: Interpolate and then check if the config has any computed stuff. 599 // If it doesn't, then do the diff/apply/writestate steps here so we 600 // can get this data resource populated early enough for its values to 601 // be visible during plan. 602 nodes = append(nodes, &EvalOpFilter{ 603 Ops: []walkOperation{walkRefresh}, 604 Node: &EvalSequence{ 605 Nodes: []EvalNode{ 606 607 // Always destroy the existing state first, since we must 608 // make sure that values from a previous read will not 609 // get interpolated if we end up needing to defer our 610 // loading until apply time. 611 &EvalWriteState{ 612 Name: n.stateId(), 613 ResourceType: n.Resource.Type, 614 Provider: n.Resource.Provider, 615 Dependencies: n.StateDependencies(), 616 State: &state, // state is nil here 617 }, 618 619 &EvalInterpolate{ 620 Config: n.Resource.RawConfig.Copy(), 621 Resource: resource, 622 Output: &config, 623 }, 624 625 // The rest of this pass can proceed only if there are no 626 // computed values in our config. 627 // (If there are, we'll deal with this during the plan and 628 // apply phases.) 629 &EvalIf{ 630 If: func(ctx EvalContext) (bool, error) { 631 if config.ComputedKeys != nil && len(config.ComputedKeys) > 0 { 632 return true, EvalEarlyExitError{} 633 } 634 635 return true, nil 636 }, 637 Then: EvalNoop{}, 638 }, 639 640 // The remainder of this pass is the same as running 641 // a "plan" pass immediately followed by an "apply" pass, 642 // populating the state early so it'll be available to 643 // provider configurations that need this data during 644 // refresh/plan. 645 646 &EvalGetProvider{ 647 Name: n.ProvidedBy()[0], 648 Output: &provider, 649 }, 650 651 &EvalReadDataDiff{ 652 Info: info, 653 Config: &config, 654 Provider: &provider, 655 Output: &diff, 656 OutputState: &state, 657 }, 658 659 &EvalReadDataApply{ 660 Info: info, 661 Diff: &diff, 662 Provider: &provider, 663 Output: &state, 664 }, 665 666 &EvalWriteState{ 667 Name: n.stateId(), 668 ResourceType: n.Resource.Type, 669 Provider: n.Resource.Provider, 670 Dependencies: n.StateDependencies(), 671 State: &state, 672 }, 673 674 &EvalUpdateStateHook{}, 675 }, 676 }, 677 }) 678 679 // Diff the resource 680 nodes = append(nodes, &EvalOpFilter{ 681 Ops: []walkOperation{walkPlan}, 682 Node: &EvalSequence{ 683 Nodes: []EvalNode{ 684 685 &EvalReadState{ 686 Name: n.stateId(), 687 Output: &state, 688 }, 689 690 // If we already have a state (created either during refresh 691 // or on a previous apply) then we don't need to do any 692 // more work on it during apply. 693 &EvalIf{ 694 If: func(ctx EvalContext) (bool, error) { 695 if state != nil { 696 return true, EvalEarlyExitError{} 697 } 698 699 return true, nil 700 }, 701 Then: EvalNoop{}, 702 }, 703 704 &EvalInterpolate{ 705 Config: n.Resource.RawConfig.Copy(), 706 Resource: resource, 707 Output: &config, 708 }, 709 710 &EvalGetProvider{ 711 Name: n.ProvidedBy()[0], 712 Output: &provider, 713 }, 714 715 &EvalReadDataDiff{ 716 Info: info, 717 Config: &config, 718 Provider: &provider, 719 Output: &diff, 720 OutputState: &state, 721 }, 722 723 &EvalWriteState{ 724 Name: n.stateId(), 725 ResourceType: n.Resource.Type, 726 Provider: n.Resource.Provider, 727 Dependencies: n.StateDependencies(), 728 State: &state, 729 }, 730 731 &EvalWriteDiff{ 732 Name: n.stateId(), 733 Diff: &diff, 734 }, 735 }, 736 }, 737 }) 738 739 // Diff the resource for destruction 740 nodes = append(nodes, &EvalOpFilter{ 741 Ops: []walkOperation{walkPlanDestroy}, 742 Node: &EvalSequence{ 743 Nodes: []EvalNode{ 744 745 &EvalReadState{ 746 Name: n.stateId(), 747 Output: &state, 748 }, 749 750 // Since EvalDiffDestroy doesn't interact with the 751 // provider at all, we can safely share the same 752 // implementation for data vs. managed resources. 753 &EvalDiffDestroy{ 754 Info: info, 755 State: &state, 756 Output: &diff, 757 }, 758 759 &EvalWriteDiff{ 760 Name: n.stateId(), 761 Diff: &diff, 762 }, 763 }, 764 }, 765 }) 766 767 // Apply 768 nodes = append(nodes, &EvalOpFilter{ 769 Ops: []walkOperation{walkApply, walkDestroy}, 770 Node: &EvalSequence{ 771 Nodes: []EvalNode{ 772 // Get the saved diff for apply 773 &EvalReadDiff{ 774 Name: n.stateId(), 775 Diff: &diff, 776 }, 777 778 // Stop here if we don't actually have a diff 779 &EvalIf{ 780 If: func(ctx EvalContext) (bool, error) { 781 if diff == nil { 782 return true, EvalEarlyExitError{} 783 } 784 785 if len(diff.Attributes) == 0 { 786 return true, EvalEarlyExitError{} 787 } 788 789 return true, nil 790 }, 791 Then: EvalNoop{}, 792 }, 793 794 // We need to re-interpolate the config here, rather than 795 // just using the diff's values directly, because we've 796 // potentially learned more variable values during the 797 // apply pass that weren't known when the diff was produced. 798 &EvalInterpolate{ 799 Config: n.Resource.RawConfig.Copy(), 800 Resource: resource, 801 Output: &config, 802 }, 803 804 &EvalGetProvider{ 805 Name: n.ProvidedBy()[0], 806 Output: &provider, 807 }, 808 809 // Make a new diff with our newly-interpolated config. 810 &EvalReadDataDiff{ 811 Info: info, 812 Config: &config, 813 Provider: &provider, 814 Output: &diff, 815 }, 816 817 &EvalReadDataApply{ 818 Info: info, 819 Diff: &diff, 820 Provider: &provider, 821 Output: &state, 822 }, 823 824 &EvalWriteState{ 825 Name: n.stateId(), 826 ResourceType: n.Resource.Type, 827 Provider: n.Resource.Provider, 828 Dependencies: n.StateDependencies(), 829 State: &state, 830 }, 831 832 // Clear the diff now that we've applied it, so 833 // later nodes won't see a diff that's now a no-op. 834 &EvalWriteDiff{ 835 Name: n.stateId(), 836 Diff: nil, 837 }, 838 839 &EvalUpdateStateHook{}, 840 }, 841 }, 842 }) 843 844 return nodes 845 } 846 847 // instanceInfo is used for EvalTree. 848 func (n *graphNodeExpandedResource) instanceInfo() *InstanceInfo { 849 return &InstanceInfo{Id: n.stateId(), Type: n.Resource.Type} 850 } 851 852 // stateId is the name used for the state key 853 func (n *graphNodeExpandedResource) stateId() string { 854 if n.Index == -1 { 855 return n.Resource.Id() 856 } 857 858 return fmt.Sprintf("%s.%d", n.Resource.Id(), n.Index) 859 } 860 861 // GraphNodeStateRepresentative impl. 862 func (n *graphNodeExpandedResource) StateId() []string { 863 return []string{n.stateId()} 864 } 865 866 // graphNodeExpandedResourceDestroy represents an expanded resource that 867 // is to be destroyed. 868 type graphNodeExpandedResourceDestroy struct { 869 *graphNodeExpandedResource 870 } 871 872 func (n *graphNodeExpandedResourceDestroy) Name() string { 873 return fmt.Sprintf("%s (destroy)", n.graphNodeExpandedResource.Name()) 874 } 875 876 // graphNodeConfig impl. 877 func (n *graphNodeExpandedResourceDestroy) ConfigType() GraphNodeConfigType { 878 return GraphNodeConfigTypeResource 879 } 880 881 // GraphNodeEvalable impl. 882 func (n *graphNodeExpandedResourceDestroy) EvalTree() EvalNode { 883 info := n.instanceInfo() 884 885 var diffApply *InstanceDiff 886 var provider ResourceProvider 887 var state *InstanceState 888 var err error 889 return &EvalOpFilter{ 890 Ops: []walkOperation{walkApply, walkDestroy}, 891 Node: &EvalSequence{ 892 Nodes: []EvalNode{ 893 // Get the saved diff for apply 894 &EvalReadDiff{ 895 Name: n.stateId(), 896 Diff: &diffApply, 897 }, 898 899 // Filter the diff so we only get the destroy 900 &EvalFilterDiff{ 901 Diff: &diffApply, 902 Output: &diffApply, 903 Destroy: true, 904 }, 905 906 // If we're not destroying, then compare diffs 907 &EvalIf{ 908 If: func(ctx EvalContext) (bool, error) { 909 if diffApply != nil && diffApply.Destroy { 910 return true, nil 911 } 912 913 return true, EvalEarlyExitError{} 914 }, 915 Then: EvalNoop{}, 916 }, 917 918 &EvalGetProvider{ 919 Name: n.ProvidedBy()[0], 920 Output: &provider, 921 }, 922 &EvalReadState{ 923 Name: n.stateId(), 924 Output: &state, 925 }, 926 &EvalRequireState{ 927 State: &state, 928 }, 929 &EvalApply{ 930 Info: info, 931 State: &state, 932 Diff: &diffApply, 933 Provider: &provider, 934 Output: &state, 935 Error: &err, 936 }, 937 &EvalWriteState{ 938 Name: n.stateId(), 939 ResourceType: n.Resource.Type, 940 Provider: n.Resource.Provider, 941 Dependencies: n.StateDependencies(), 942 State: &state, 943 }, 944 &EvalApplyPost{ 945 Info: info, 946 State: &state, 947 Error: &err, 948 }, 949 }, 950 }, 951 } 952 }