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