github.com/danrjohnson/terraform@v0.7.0-rc2.0.20160627135212-d0fc1fa086ff/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 OutputDiff: &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 &EvalWriteDiff{ 359 Name: n.stateId(), 360 Diff: &diff, 361 }, 362 }, 363 }, 364 }) 365 366 // Diff the resource for destruction 367 nodes = append(nodes, &EvalOpFilter{ 368 Ops: []walkOperation{walkPlanDestroy}, 369 Node: &EvalSequence{ 370 Nodes: []EvalNode{ 371 &EvalReadState{ 372 Name: n.stateId(), 373 Output: &state, 374 }, 375 &EvalDiffDestroy{ 376 Info: info, 377 State: &state, 378 Output: &diff, 379 }, 380 &EvalCheckPreventDestroy{ 381 Resource: n.Resource, 382 Diff: &diff, 383 }, 384 &EvalWriteDiff{ 385 Name: n.stateId(), 386 Diff: &diff, 387 }, 388 }, 389 }, 390 }) 391 392 // Apply 393 var diffApply *InstanceDiff 394 var err error 395 var createNew bool 396 var createBeforeDestroyEnabled bool 397 var wasChangeType DiffChangeType 398 nodes = append(nodes, &EvalOpFilter{ 399 Ops: []walkOperation{walkApply, walkDestroy}, 400 Node: &EvalSequence{ 401 Nodes: []EvalNode{ 402 // Get the saved diff for apply 403 &EvalReadDiff{ 404 Name: n.stateId(), 405 Diff: &diffApply, 406 }, 407 408 // We don't want to do any destroys 409 &EvalIf{ 410 If: func(ctx EvalContext) (bool, error) { 411 if diffApply == nil { 412 return true, EvalEarlyExitError{} 413 } 414 415 if diffApply.Destroy && len(diffApply.Attributes) == 0 { 416 return true, EvalEarlyExitError{} 417 } 418 419 wasChangeType = diffApply.ChangeType() 420 diffApply.Destroy = false 421 return true, nil 422 }, 423 Then: EvalNoop{}, 424 }, 425 426 &EvalIf{ 427 If: func(ctx EvalContext) (bool, error) { 428 destroy := false 429 if diffApply != nil { 430 destroy = diffApply.Destroy || diffApply.RequiresNew() 431 } 432 433 createBeforeDestroyEnabled = 434 n.Resource.Lifecycle.CreateBeforeDestroy && 435 destroy 436 437 return createBeforeDestroyEnabled, nil 438 }, 439 Then: &EvalDeposeState{ 440 Name: n.stateId(), 441 }, 442 }, 443 444 &EvalInterpolate{ 445 Config: n.Resource.RawConfig.Copy(), 446 Resource: resource, 447 Output: &resourceConfig, 448 }, 449 &EvalGetProvider{ 450 Name: n.ProvidedBy()[0], 451 Output: &provider, 452 }, 453 &EvalReadState{ 454 Name: n.stateId(), 455 Output: &state, 456 }, 457 458 &EvalDiff{ 459 Info: info, 460 Config: &resourceConfig, 461 Provider: &provider, 462 Diff: &diffApply, 463 State: &state, 464 OutputDiff: &diffApply, 465 }, 466 &EvalIgnoreChanges{ 467 Resource: n.Resource, 468 Diff: &diffApply, 469 WasChangeType: &wasChangeType, 470 }, 471 472 // Get the saved diff 473 &EvalReadDiff{ 474 Name: n.stateId(), 475 Diff: &diff, 476 }, 477 478 // Compare the diffs 479 &EvalCompareDiff{ 480 Info: info, 481 One: &diff, 482 Two: &diffApply, 483 }, 484 485 &EvalGetProvider{ 486 Name: n.ProvidedBy()[0], 487 Output: &provider, 488 }, 489 &EvalReadState{ 490 Name: n.stateId(), 491 Output: &state, 492 }, 493 &EvalApply{ 494 Info: info, 495 State: &state, 496 Diff: &diffApply, 497 Provider: &provider, 498 Output: &state, 499 Error: &err, 500 CreateNew: &createNew, 501 }, 502 &EvalWriteState{ 503 Name: n.stateId(), 504 ResourceType: n.Resource.Type, 505 Provider: n.Resource.Provider, 506 Dependencies: n.StateDependencies(), 507 State: &state, 508 }, 509 &EvalApplyProvisioners{ 510 Info: info, 511 State: &state, 512 Resource: n.Resource, 513 InterpResource: resource, 514 CreateNew: &createNew, 515 Error: &err, 516 }, 517 &EvalIf{ 518 If: func(ctx EvalContext) (bool, error) { 519 return createBeforeDestroyEnabled && err != nil, nil 520 }, 521 Then: &EvalUndeposeState{ 522 Name: n.stateId(), 523 State: &state, 524 }, 525 Else: &EvalWriteState{ 526 Name: n.stateId(), 527 ResourceType: n.Resource.Type, 528 Provider: n.Resource.Provider, 529 Dependencies: n.StateDependencies(), 530 State: &state, 531 }, 532 }, 533 534 // We clear the diff out here so that future nodes 535 // don't see a diff that is already complete. There 536 // is no longer a diff! 537 &EvalWriteDiff{ 538 Name: n.stateId(), 539 Diff: nil, 540 }, 541 542 &EvalApplyPost{ 543 Info: info, 544 State: &state, 545 Error: &err, 546 }, 547 &EvalUpdateStateHook{}, 548 }, 549 }, 550 }) 551 552 return nodes 553 } 554 555 func (n *graphNodeExpandedResource) dataResourceEvalNodes(resource *Resource, info *InstanceInfo, resourceConfig *ResourceConfig) []EvalNode { 556 //var diff *InstanceDiff 557 var provider ResourceProvider 558 var config *ResourceConfig 559 var diff *InstanceDiff 560 var state *InstanceState 561 562 nodes := make([]EvalNode, 0, 5) 563 564 // Refresh the resource 565 nodes = append(nodes, &EvalOpFilter{ 566 Ops: []walkOperation{walkRefresh}, 567 Node: &EvalSequence{ 568 Nodes: []EvalNode{ 569 570 // Always destroy the existing state first, since we must 571 // make sure that values from a previous read will not 572 // get interpolated if we end up needing to defer our 573 // loading until apply time. 574 &EvalWriteState{ 575 Name: n.stateId(), 576 ResourceType: n.Resource.Type, 577 Provider: n.Resource.Provider, 578 Dependencies: n.StateDependencies(), 579 State: &state, // state is nil here 580 }, 581 582 &EvalInterpolate{ 583 Config: n.Resource.RawConfig.Copy(), 584 Resource: resource, 585 Output: &config, 586 }, 587 588 // The rest of this pass can proceed only if there are no 589 // computed values in our config. 590 // (If there are, we'll deal with this during the plan and 591 // apply phases.) 592 &EvalIf{ 593 If: func(ctx EvalContext) (bool, error) { 594 if config.ComputedKeys != nil && len(config.ComputedKeys) > 0 { 595 return true, EvalEarlyExitError{} 596 } 597 598 return true, nil 599 }, 600 Then: EvalNoop{}, 601 }, 602 603 // The remainder of this pass is the same as running 604 // a "plan" pass immediately followed by an "apply" pass, 605 // populating the state early so it'll be available to 606 // provider configurations that need this data during 607 // refresh/plan. 608 609 &EvalGetProvider{ 610 Name: n.ProvidedBy()[0], 611 Output: &provider, 612 }, 613 614 &EvalReadDataDiff{ 615 Info: info, 616 Config: &config, 617 Provider: &provider, 618 Output: &diff, 619 OutputState: &state, 620 }, 621 622 &EvalReadDataApply{ 623 Info: info, 624 Diff: &diff, 625 Provider: &provider, 626 Output: &state, 627 }, 628 629 &EvalWriteState{ 630 Name: n.stateId(), 631 ResourceType: n.Resource.Type, 632 Provider: n.Resource.Provider, 633 Dependencies: n.StateDependencies(), 634 State: &state, 635 }, 636 637 &EvalUpdateStateHook{}, 638 }, 639 }, 640 }) 641 642 // Diff the resource 643 nodes = append(nodes, &EvalOpFilter{ 644 Ops: []walkOperation{walkPlan}, 645 Node: &EvalSequence{ 646 Nodes: []EvalNode{ 647 648 &EvalReadState{ 649 Name: n.stateId(), 650 Output: &state, 651 }, 652 653 // We need to re-interpolate the config here because some 654 // of the attributes may have become computed during 655 // earlier planning, due to other resources having 656 // "requires new resource" diffs. 657 &EvalInterpolate{ 658 Config: n.Resource.RawConfig.Copy(), 659 Resource: resource, 660 Output: &config, 661 }, 662 663 &EvalIf{ 664 If: func(ctx EvalContext) (bool, error) { 665 computed := config.ComputedKeys != nil && len(config.ComputedKeys) > 0 666 667 // If the configuration is complete and we 668 // already have a state then we don't need to 669 // do any further work during apply, because we 670 // already populated the state during refresh. 671 if !computed && state != nil { 672 return true, EvalEarlyExitError{} 673 } 674 675 return true, nil 676 }, 677 Then: EvalNoop{}, 678 }, 679 680 &EvalGetProvider{ 681 Name: n.ProvidedBy()[0], 682 Output: &provider, 683 }, 684 685 &EvalReadDataDiff{ 686 Info: info, 687 Config: &config, 688 Provider: &provider, 689 Output: &diff, 690 OutputState: &state, 691 }, 692 693 &EvalWriteState{ 694 Name: n.stateId(), 695 ResourceType: n.Resource.Type, 696 Provider: n.Resource.Provider, 697 Dependencies: n.StateDependencies(), 698 State: &state, 699 }, 700 701 &EvalWriteDiff{ 702 Name: n.stateId(), 703 Diff: &diff, 704 }, 705 }, 706 }, 707 }) 708 709 // Diff the resource for destruction 710 nodes = append(nodes, &EvalOpFilter{ 711 Ops: []walkOperation{walkPlanDestroy}, 712 Node: &EvalSequence{ 713 Nodes: []EvalNode{ 714 715 &EvalReadState{ 716 Name: n.stateId(), 717 Output: &state, 718 }, 719 720 // Since EvalDiffDestroy doesn't interact with the 721 // provider at all, we can safely share the same 722 // implementation for data vs. managed resources. 723 &EvalDiffDestroy{ 724 Info: info, 725 State: &state, 726 Output: &diff, 727 }, 728 729 &EvalWriteDiff{ 730 Name: n.stateId(), 731 Diff: &diff, 732 }, 733 }, 734 }, 735 }) 736 737 // Apply 738 nodes = append(nodes, &EvalOpFilter{ 739 Ops: []walkOperation{walkApply, walkDestroy}, 740 Node: &EvalSequence{ 741 Nodes: []EvalNode{ 742 // Get the saved diff for apply 743 &EvalReadDiff{ 744 Name: n.stateId(), 745 Diff: &diff, 746 }, 747 748 // Stop here if we don't actually have a diff 749 &EvalIf{ 750 If: func(ctx EvalContext) (bool, error) { 751 if diff == nil { 752 return true, EvalEarlyExitError{} 753 } 754 755 if len(diff.Attributes) == 0 { 756 return true, EvalEarlyExitError{} 757 } 758 759 return true, nil 760 }, 761 Then: EvalNoop{}, 762 }, 763 764 // We need to re-interpolate the config here, rather than 765 // just using the diff's values directly, because we've 766 // potentially learned more variable values during the 767 // apply pass that weren't known when the diff was produced. 768 &EvalInterpolate{ 769 Config: n.Resource.RawConfig.Copy(), 770 Resource: resource, 771 Output: &config, 772 }, 773 774 &EvalGetProvider{ 775 Name: n.ProvidedBy()[0], 776 Output: &provider, 777 }, 778 779 // Make a new diff with our newly-interpolated config. 780 &EvalReadDataDiff{ 781 Info: info, 782 Config: &config, 783 Previous: &diff, 784 Provider: &provider, 785 Output: &diff, 786 }, 787 788 &EvalReadDataApply{ 789 Info: info, 790 Diff: &diff, 791 Provider: &provider, 792 Output: &state, 793 }, 794 795 &EvalWriteState{ 796 Name: n.stateId(), 797 ResourceType: n.Resource.Type, 798 Provider: n.Resource.Provider, 799 Dependencies: n.StateDependencies(), 800 State: &state, 801 }, 802 803 // Clear the diff now that we've applied it, so 804 // later nodes won't see a diff that's now a no-op. 805 &EvalWriteDiff{ 806 Name: n.stateId(), 807 Diff: nil, 808 }, 809 810 &EvalUpdateStateHook{}, 811 }, 812 }, 813 }) 814 815 return nodes 816 } 817 818 // instanceInfo is used for EvalTree. 819 func (n *graphNodeExpandedResource) instanceInfo() *InstanceInfo { 820 return &InstanceInfo{Id: n.stateId(), Type: n.Resource.Type} 821 } 822 823 // stateId is the name used for the state key 824 func (n *graphNodeExpandedResource) stateId() string { 825 if n.Index == -1 { 826 return n.Resource.Id() 827 } 828 829 return fmt.Sprintf("%s.%d", n.Resource.Id(), n.Index) 830 } 831 832 // GraphNodeStateRepresentative impl. 833 func (n *graphNodeExpandedResource) StateId() []string { 834 return []string{n.stateId()} 835 } 836 837 // graphNodeExpandedResourceDestroy represents an expanded resource that 838 // is to be destroyed. 839 type graphNodeExpandedResourceDestroy struct { 840 *graphNodeExpandedResource 841 } 842 843 func (n *graphNodeExpandedResourceDestroy) Name() string { 844 return fmt.Sprintf("%s (destroy)", n.graphNodeExpandedResource.Name()) 845 } 846 847 // graphNodeConfig impl. 848 func (n *graphNodeExpandedResourceDestroy) ConfigType() GraphNodeConfigType { 849 return GraphNodeConfigTypeResource 850 } 851 852 // GraphNodeEvalable impl. 853 func (n *graphNodeExpandedResourceDestroy) EvalTree() EvalNode { 854 info := n.instanceInfo() 855 856 var diffApply *InstanceDiff 857 var provider ResourceProvider 858 var state *InstanceState 859 var err error 860 return &EvalOpFilter{ 861 Ops: []walkOperation{walkApply, walkDestroy}, 862 Node: &EvalSequence{ 863 Nodes: []EvalNode{ 864 // Get the saved diff for apply 865 &EvalReadDiff{ 866 Name: n.stateId(), 867 Diff: &diffApply, 868 }, 869 870 // Filter the diff so we only get the destroy 871 &EvalFilterDiff{ 872 Diff: &diffApply, 873 Output: &diffApply, 874 Destroy: true, 875 }, 876 877 // If we're not destroying, then compare diffs 878 &EvalIf{ 879 If: func(ctx EvalContext) (bool, error) { 880 if diffApply != nil && diffApply.Destroy { 881 return true, nil 882 } 883 884 return true, EvalEarlyExitError{} 885 }, 886 Then: EvalNoop{}, 887 }, 888 889 &EvalGetProvider{ 890 Name: n.ProvidedBy()[0], 891 Output: &provider, 892 }, 893 &EvalReadState{ 894 Name: n.stateId(), 895 Output: &state, 896 }, 897 &EvalRequireState{ 898 State: &state, 899 }, 900 // Make sure we handle data sources properly. 901 &EvalIf{ 902 If: func(ctx EvalContext) (bool, error) { 903 if n.Resource.Mode == config.DataResourceMode { 904 return true, nil 905 } 906 907 return false, nil 908 }, 909 910 Then: &EvalReadDataApply{ 911 Info: info, 912 Diff: &diffApply, 913 Provider: &provider, 914 Output: &state, 915 }, 916 Else: &EvalApply{ 917 Info: info, 918 State: &state, 919 Diff: &diffApply, 920 Provider: &provider, 921 Output: &state, 922 Error: &err, 923 }, 924 }, 925 &EvalWriteState{ 926 Name: n.stateId(), 927 ResourceType: n.Resource.Type, 928 Provider: n.Resource.Provider, 929 Dependencies: n.StateDependencies(), 930 State: &state, 931 }, 932 &EvalApplyPost{ 933 Info: info, 934 State: &state, 935 Error: &err, 936 }, 937 }, 938 }, 939 } 940 }