github.com/nbering/terraform@v0.8.5-0.20170113232247-453f670684b5/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 // ResourceCountTransformerOld is a GraphTransformer that expands the count 12 // out for a specific resource. 13 type ResourceCountTransformerOld struct { 14 Resource *config.Resource 15 Destroy bool 16 Targets []ResourceAddress 17 } 18 19 func (t *ResourceCountTransformerOld) 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 *ResourceCountTransformerOld) 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 605 if config.ComputedKeys != nil && len(config.ComputedKeys) > 0 { 606 return true, EvalEarlyExitError{} 607 } 608 609 // If the config explicitly has a depends_on for this 610 // data source, assume the intention is to prevent 611 // refreshing ahead of that dependency. 612 if len(n.Resource.DependsOn) > 0 { 613 return true, EvalEarlyExitError{} 614 } 615 616 return true, nil 617 }, 618 Then: EvalNoop{}, 619 }, 620 621 // The remainder of this pass is the same as running 622 // a "plan" pass immediately followed by an "apply" pass, 623 // populating the state early so it'll be available to 624 // provider configurations that need this data during 625 // refresh/plan. 626 627 &EvalGetProvider{ 628 Name: n.ProvidedBy()[0], 629 Output: &provider, 630 }, 631 632 &EvalReadDataDiff{ 633 Info: info, 634 Config: &config, 635 Provider: &provider, 636 Output: &diff, 637 OutputState: &state, 638 }, 639 640 &EvalReadDataApply{ 641 Info: info, 642 Diff: &diff, 643 Provider: &provider, 644 Output: &state, 645 }, 646 647 &EvalWriteState{ 648 Name: n.stateId(), 649 ResourceType: n.Resource.Type, 650 Provider: n.Resource.Provider, 651 Dependencies: n.StateDependencies(), 652 State: &state, 653 }, 654 655 &EvalUpdateStateHook{}, 656 }, 657 }, 658 }) 659 660 // Diff the resource 661 nodes = append(nodes, &EvalOpFilter{ 662 Ops: []walkOperation{walkPlan}, 663 Node: &EvalSequence{ 664 Nodes: []EvalNode{ 665 666 &EvalReadState{ 667 Name: n.stateId(), 668 Output: &state, 669 }, 670 671 // We need to re-interpolate the config here because some 672 // of the attributes may have become computed during 673 // earlier planning, due to other resources having 674 // "requires new resource" diffs. 675 &EvalInterpolate{ 676 Config: n.Resource.RawConfig.Copy(), 677 Resource: resource, 678 Output: &config, 679 }, 680 681 &EvalIf{ 682 If: func(ctx EvalContext) (bool, error) { 683 computed := config.ComputedKeys != nil && len(config.ComputedKeys) > 0 684 685 // If the configuration is complete and we 686 // already have a state then we don't need to 687 // do any further work during apply, because we 688 // already populated the state during refresh. 689 if !computed && state != nil { 690 return true, EvalEarlyExitError{} 691 } 692 693 return true, nil 694 }, 695 Then: EvalNoop{}, 696 }, 697 698 &EvalGetProvider{ 699 Name: n.ProvidedBy()[0], 700 Output: &provider, 701 }, 702 703 &EvalReadDataDiff{ 704 Info: info, 705 Config: &config, 706 Provider: &provider, 707 Output: &diff, 708 OutputState: &state, 709 }, 710 711 &EvalWriteState{ 712 Name: n.stateId(), 713 ResourceType: n.Resource.Type, 714 Provider: n.Resource.Provider, 715 Dependencies: n.StateDependencies(), 716 State: &state, 717 }, 718 719 &EvalWriteDiff{ 720 Name: n.stateId(), 721 Diff: &diff, 722 }, 723 }, 724 }, 725 }) 726 727 // Diff the resource for destruction 728 nodes = append(nodes, &EvalOpFilter{ 729 Ops: []walkOperation{walkPlanDestroy}, 730 Node: &EvalSequence{ 731 Nodes: []EvalNode{ 732 733 &EvalReadState{ 734 Name: n.stateId(), 735 Output: &state, 736 }, 737 738 // Since EvalDiffDestroy doesn't interact with the 739 // provider at all, we can safely share the same 740 // implementation for data vs. managed resources. 741 &EvalDiffDestroy{ 742 Info: info, 743 State: &state, 744 Output: &diff, 745 }, 746 747 &EvalWriteDiff{ 748 Name: n.stateId(), 749 Diff: &diff, 750 }, 751 }, 752 }, 753 }) 754 755 // Apply 756 nodes = append(nodes, &EvalOpFilter{ 757 Ops: []walkOperation{walkApply, walkDestroy}, 758 Node: &EvalSequence{ 759 Nodes: []EvalNode{ 760 // Get the saved diff for apply 761 &EvalReadDiff{ 762 Name: n.stateId(), 763 Diff: &diff, 764 }, 765 766 // Stop here if we don't actually have a diff 767 &EvalIf{ 768 If: func(ctx EvalContext) (bool, error) { 769 if diff == nil { 770 return true, EvalEarlyExitError{} 771 } 772 773 if diff.GetAttributesLen() == 0 { 774 return true, EvalEarlyExitError{} 775 } 776 777 return true, nil 778 }, 779 Then: EvalNoop{}, 780 }, 781 782 // We need to re-interpolate the config here, rather than 783 // just using the diff's values directly, because we've 784 // potentially learned more variable values during the 785 // apply pass that weren't known when the diff was produced. 786 &EvalInterpolate{ 787 Config: n.Resource.RawConfig.Copy(), 788 Resource: resource, 789 Output: &config, 790 }, 791 792 &EvalGetProvider{ 793 Name: n.ProvidedBy()[0], 794 Output: &provider, 795 }, 796 797 // Make a new diff with our newly-interpolated config. 798 &EvalReadDataDiff{ 799 Info: info, 800 Config: &config, 801 Previous: &diff, 802 Provider: &provider, 803 Output: &diff, 804 }, 805 806 &EvalReadDataApply{ 807 Info: info, 808 Diff: &diff, 809 Provider: &provider, 810 Output: &state, 811 }, 812 813 &EvalWriteState{ 814 Name: n.stateId(), 815 ResourceType: n.Resource.Type, 816 Provider: n.Resource.Provider, 817 Dependencies: n.StateDependencies(), 818 State: &state, 819 }, 820 821 // Clear the diff now that we've applied it, so 822 // later nodes won't see a diff that's now a no-op. 823 &EvalWriteDiff{ 824 Name: n.stateId(), 825 Diff: nil, 826 }, 827 828 &EvalUpdateStateHook{}, 829 }, 830 }, 831 }) 832 833 return nodes 834 } 835 836 // instanceInfo is used for EvalTree. 837 func (n *graphNodeExpandedResource) instanceInfo() *InstanceInfo { 838 return &InstanceInfo{Id: n.stateId(), Type: n.Resource.Type} 839 } 840 841 // stateId is the name used for the state key 842 func (n *graphNodeExpandedResource) stateId() string { 843 if n.Index == -1 { 844 return n.Resource.Id() 845 } 846 847 return fmt.Sprintf("%s.%d", n.Resource.Id(), n.Index) 848 } 849 850 // GraphNodeStateRepresentative impl. 851 func (n *graphNodeExpandedResource) StateId() []string { 852 return []string{n.stateId()} 853 } 854 855 // graphNodeExpandedResourceDestroy represents an expanded resource that 856 // is to be destroyed. 857 type graphNodeExpandedResourceDestroy struct { 858 *graphNodeExpandedResource 859 } 860 861 func (n *graphNodeExpandedResourceDestroy) Name() string { 862 return fmt.Sprintf("%s (destroy)", n.graphNodeExpandedResource.Name()) 863 } 864 865 // graphNodeConfig impl. 866 func (n *graphNodeExpandedResourceDestroy) ConfigType() GraphNodeConfigType { 867 return GraphNodeConfigTypeResource 868 } 869 870 // GraphNodeEvalable impl. 871 func (n *graphNodeExpandedResourceDestroy) EvalTree() EvalNode { 872 info := n.instanceInfo() 873 info.uniqueExtra = "destroy" 874 875 var diffApply *InstanceDiff 876 var provider ResourceProvider 877 var state *InstanceState 878 var err error 879 return &EvalOpFilter{ 880 Ops: []walkOperation{walkApply, walkDestroy}, 881 Node: &EvalSequence{ 882 Nodes: []EvalNode{ 883 // Get the saved diff for apply 884 &EvalReadDiff{ 885 Name: n.stateId(), 886 Diff: &diffApply, 887 }, 888 889 // Filter the diff so we only get the destroy 890 &EvalFilterDiff{ 891 Diff: &diffApply, 892 Output: &diffApply, 893 Destroy: true, 894 }, 895 896 // If we're not destroying, then compare diffs 897 &EvalIf{ 898 If: func(ctx EvalContext) (bool, error) { 899 if diffApply != nil && diffApply.GetDestroy() { 900 return true, nil 901 } 902 903 return true, EvalEarlyExitError{} 904 }, 905 Then: EvalNoop{}, 906 }, 907 908 // Load the instance info so we have the module path set 909 &EvalInstanceInfo{Info: info}, 910 911 &EvalGetProvider{ 912 Name: n.ProvidedBy()[0], 913 Output: &provider, 914 }, 915 &EvalReadState{ 916 Name: n.stateId(), 917 Output: &state, 918 }, 919 &EvalRequireState{ 920 State: &state, 921 }, 922 // Make sure we handle data sources properly. 923 &EvalIf{ 924 If: func(ctx EvalContext) (bool, error) { 925 if n.Resource.Mode == config.DataResourceMode { 926 return true, nil 927 } 928 929 return false, nil 930 }, 931 932 Then: &EvalReadDataApply{ 933 Info: info, 934 Diff: &diffApply, 935 Provider: &provider, 936 Output: &state, 937 }, 938 Else: &EvalApply{ 939 Info: info, 940 State: &state, 941 Diff: &diffApply, 942 Provider: &provider, 943 Output: &state, 944 Error: &err, 945 }, 946 }, 947 &EvalWriteState{ 948 Name: n.stateId(), 949 ResourceType: n.Resource.Type, 950 Provider: n.Resource.Provider, 951 Dependencies: n.StateDependencies(), 952 State: &state, 953 }, 954 &EvalApplyPost{ 955 Info: info, 956 State: &state, 957 Error: &err, 958 }, 959 }, 960 }, 961 } 962 }