github.com/pbthorste/terraform@v0.8.6-0.20170127005045-deb56bd93da2/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 &EvalApplyPre{ 504 Info: info, 505 State: &state, 506 Diff: &diffApply, 507 }, 508 &EvalApply{ 509 Info: info, 510 State: &state, 511 Diff: &diffApply, 512 Provider: &provider, 513 Output: &state, 514 Error: &err, 515 CreateNew: &createNew, 516 }, 517 &EvalWriteState{ 518 Name: n.stateId(), 519 ResourceType: n.Resource.Type, 520 Provider: n.Resource.Provider, 521 Dependencies: n.StateDependencies(), 522 State: &state, 523 }, 524 &EvalApplyProvisioners{ 525 Info: info, 526 State: &state, 527 Resource: n.Resource, 528 InterpResource: resource, 529 CreateNew: &createNew, 530 Error: &err, 531 }, 532 &EvalIf{ 533 If: func(ctx EvalContext) (bool, error) { 534 return createBeforeDestroyEnabled && err != nil, nil 535 }, 536 Then: &EvalUndeposeState{ 537 Name: n.stateId(), 538 State: &state, 539 }, 540 Else: &EvalWriteState{ 541 Name: n.stateId(), 542 ResourceType: n.Resource.Type, 543 Provider: n.Resource.Provider, 544 Dependencies: n.StateDependencies(), 545 State: &state, 546 }, 547 }, 548 549 // We clear the diff out here so that future nodes 550 // don't see a diff that is already complete. There 551 // is no longer a diff! 552 &EvalWriteDiff{ 553 Name: n.stateId(), 554 Diff: nil, 555 }, 556 557 &EvalApplyPost{ 558 Info: info, 559 State: &state, 560 Error: &err, 561 }, 562 &EvalUpdateStateHook{}, 563 }, 564 }, 565 }) 566 567 return nodes 568 } 569 570 func (n *graphNodeExpandedResource) dataResourceEvalNodes(resource *Resource, info *InstanceInfo, resourceConfig *ResourceConfig) []EvalNode { 571 //var diff *InstanceDiff 572 var provider ResourceProvider 573 var config *ResourceConfig 574 var diff *InstanceDiff 575 var state *InstanceState 576 577 nodes := make([]EvalNode, 0, 5) 578 579 // Refresh the resource 580 nodes = append(nodes, &EvalOpFilter{ 581 Ops: []walkOperation{walkRefresh}, 582 Node: &EvalSequence{ 583 Nodes: []EvalNode{ 584 585 // Always destroy the existing state first, since we must 586 // make sure that values from a previous read will not 587 // get interpolated if we end up needing to defer our 588 // loading until apply time. 589 &EvalWriteState{ 590 Name: n.stateId(), 591 ResourceType: n.Resource.Type, 592 Provider: n.Resource.Provider, 593 Dependencies: n.StateDependencies(), 594 State: &state, // state is nil here 595 }, 596 597 &EvalInterpolate{ 598 Config: n.Resource.RawConfig.Copy(), 599 Resource: resource, 600 Output: &config, 601 }, 602 603 // The rest of this pass can proceed only if there are no 604 // computed values in our config. 605 // (If there are, we'll deal with this during the plan and 606 // apply phases.) 607 &EvalIf{ 608 If: func(ctx EvalContext) (bool, error) { 609 610 if config.ComputedKeys != nil && len(config.ComputedKeys) > 0 { 611 return true, EvalEarlyExitError{} 612 } 613 614 // If the config explicitly has a depends_on for this 615 // data source, assume the intention is to prevent 616 // refreshing ahead of that dependency. 617 if len(n.Resource.DependsOn) > 0 { 618 return true, EvalEarlyExitError{} 619 } 620 621 return true, nil 622 }, 623 Then: EvalNoop{}, 624 }, 625 626 // The remainder of this pass is the same as running 627 // a "plan" pass immediately followed by an "apply" pass, 628 // populating the state early so it'll be available to 629 // provider configurations that need this data during 630 // refresh/plan. 631 632 &EvalGetProvider{ 633 Name: n.ProvidedBy()[0], 634 Output: &provider, 635 }, 636 637 &EvalReadDataDiff{ 638 Info: info, 639 Config: &config, 640 Provider: &provider, 641 Output: &diff, 642 OutputState: &state, 643 }, 644 645 &EvalReadDataApply{ 646 Info: info, 647 Diff: &diff, 648 Provider: &provider, 649 Output: &state, 650 }, 651 652 &EvalWriteState{ 653 Name: n.stateId(), 654 ResourceType: n.Resource.Type, 655 Provider: n.Resource.Provider, 656 Dependencies: n.StateDependencies(), 657 State: &state, 658 }, 659 660 &EvalUpdateStateHook{}, 661 }, 662 }, 663 }) 664 665 // Diff the resource 666 nodes = append(nodes, &EvalOpFilter{ 667 Ops: []walkOperation{walkPlan}, 668 Node: &EvalSequence{ 669 Nodes: []EvalNode{ 670 671 &EvalReadState{ 672 Name: n.stateId(), 673 Output: &state, 674 }, 675 676 // We need to re-interpolate the config here because some 677 // of the attributes may have become computed during 678 // earlier planning, due to other resources having 679 // "requires new resource" diffs. 680 &EvalInterpolate{ 681 Config: n.Resource.RawConfig.Copy(), 682 Resource: resource, 683 Output: &config, 684 }, 685 686 &EvalIf{ 687 If: func(ctx EvalContext) (bool, error) { 688 computed := config.ComputedKeys != nil && len(config.ComputedKeys) > 0 689 690 // If the configuration is complete and we 691 // already have a state then we don't need to 692 // do any further work during apply, because we 693 // already populated the state during refresh. 694 if !computed && state != nil { 695 return true, EvalEarlyExitError{} 696 } 697 698 return true, nil 699 }, 700 Then: EvalNoop{}, 701 }, 702 703 &EvalGetProvider{ 704 Name: n.ProvidedBy()[0], 705 Output: &provider, 706 }, 707 708 &EvalReadDataDiff{ 709 Info: info, 710 Config: &config, 711 Provider: &provider, 712 Output: &diff, 713 OutputState: &state, 714 }, 715 716 &EvalWriteState{ 717 Name: n.stateId(), 718 ResourceType: n.Resource.Type, 719 Provider: n.Resource.Provider, 720 Dependencies: n.StateDependencies(), 721 State: &state, 722 }, 723 724 &EvalWriteDiff{ 725 Name: n.stateId(), 726 Diff: &diff, 727 }, 728 }, 729 }, 730 }) 731 732 // Diff the resource for destruction 733 nodes = append(nodes, &EvalOpFilter{ 734 Ops: []walkOperation{walkPlanDestroy}, 735 Node: &EvalSequence{ 736 Nodes: []EvalNode{ 737 738 &EvalReadState{ 739 Name: n.stateId(), 740 Output: &state, 741 }, 742 743 // Since EvalDiffDestroy doesn't interact with the 744 // provider at all, we can safely share the same 745 // implementation for data vs. managed resources. 746 &EvalDiffDestroy{ 747 Info: info, 748 State: &state, 749 Output: &diff, 750 }, 751 752 &EvalWriteDiff{ 753 Name: n.stateId(), 754 Diff: &diff, 755 }, 756 }, 757 }, 758 }) 759 760 // Apply 761 nodes = append(nodes, &EvalOpFilter{ 762 Ops: []walkOperation{walkApply, walkDestroy}, 763 Node: &EvalSequence{ 764 Nodes: []EvalNode{ 765 // Get the saved diff for apply 766 &EvalReadDiff{ 767 Name: n.stateId(), 768 Diff: &diff, 769 }, 770 771 // Stop here if we don't actually have a diff 772 &EvalIf{ 773 If: func(ctx EvalContext) (bool, error) { 774 if diff == nil { 775 return true, EvalEarlyExitError{} 776 } 777 778 if diff.GetAttributesLen() == 0 { 779 return true, EvalEarlyExitError{} 780 } 781 782 return true, nil 783 }, 784 Then: EvalNoop{}, 785 }, 786 787 // We need to re-interpolate the config here, rather than 788 // just using the diff's values directly, because we've 789 // potentially learned more variable values during the 790 // apply pass that weren't known when the diff was produced. 791 &EvalInterpolate{ 792 Config: n.Resource.RawConfig.Copy(), 793 Resource: resource, 794 Output: &config, 795 }, 796 797 &EvalGetProvider{ 798 Name: n.ProvidedBy()[0], 799 Output: &provider, 800 }, 801 802 // Make a new diff with our newly-interpolated config. 803 &EvalReadDataDiff{ 804 Info: info, 805 Config: &config, 806 Previous: &diff, 807 Provider: &provider, 808 Output: &diff, 809 }, 810 811 &EvalReadDataApply{ 812 Info: info, 813 Diff: &diff, 814 Provider: &provider, 815 Output: &state, 816 }, 817 818 &EvalWriteState{ 819 Name: n.stateId(), 820 ResourceType: n.Resource.Type, 821 Provider: n.Resource.Provider, 822 Dependencies: n.StateDependencies(), 823 State: &state, 824 }, 825 826 // Clear the diff now that we've applied it, so 827 // later nodes won't see a diff that's now a no-op. 828 &EvalWriteDiff{ 829 Name: n.stateId(), 830 Diff: nil, 831 }, 832 833 &EvalUpdateStateHook{}, 834 }, 835 }, 836 }) 837 838 return nodes 839 } 840 841 // instanceInfo is used for EvalTree. 842 func (n *graphNodeExpandedResource) instanceInfo() *InstanceInfo { 843 return &InstanceInfo{Id: n.stateId(), Type: n.Resource.Type} 844 } 845 846 // stateId is the name used for the state key 847 func (n *graphNodeExpandedResource) stateId() string { 848 if n.Index == -1 { 849 return n.Resource.Id() 850 } 851 852 return fmt.Sprintf("%s.%d", n.Resource.Id(), n.Index) 853 } 854 855 // GraphNodeStateRepresentative impl. 856 func (n *graphNodeExpandedResource) StateId() []string { 857 return []string{n.stateId()} 858 } 859 860 // graphNodeExpandedResourceDestroy represents an expanded resource that 861 // is to be destroyed. 862 type graphNodeExpandedResourceDestroy struct { 863 *graphNodeExpandedResource 864 } 865 866 func (n *graphNodeExpandedResourceDestroy) Name() string { 867 return fmt.Sprintf("%s (destroy)", n.graphNodeExpandedResource.Name()) 868 } 869 870 // graphNodeConfig impl. 871 func (n *graphNodeExpandedResourceDestroy) ConfigType() GraphNodeConfigType { 872 return GraphNodeConfigTypeResource 873 } 874 875 // GraphNodeEvalable impl. 876 func (n *graphNodeExpandedResourceDestroy) EvalTree() EvalNode { 877 info := n.instanceInfo() 878 info.uniqueExtra = "destroy" 879 880 var diffApply *InstanceDiff 881 var provider ResourceProvider 882 var state *InstanceState 883 var err error 884 return &EvalOpFilter{ 885 Ops: []walkOperation{walkApply, walkDestroy}, 886 Node: &EvalSequence{ 887 Nodes: []EvalNode{ 888 // Get the saved diff for apply 889 &EvalReadDiff{ 890 Name: n.stateId(), 891 Diff: &diffApply, 892 }, 893 894 // Filter the diff so we only get the destroy 895 &EvalFilterDiff{ 896 Diff: &diffApply, 897 Output: &diffApply, 898 Destroy: true, 899 }, 900 901 // If we're not destroying, then compare diffs 902 &EvalIf{ 903 If: func(ctx EvalContext) (bool, error) { 904 if diffApply != nil && diffApply.GetDestroy() { 905 return true, nil 906 } 907 908 return true, EvalEarlyExitError{} 909 }, 910 Then: EvalNoop{}, 911 }, 912 913 // Load the instance info so we have the module path set 914 &EvalInstanceInfo{Info: info}, 915 916 &EvalGetProvider{ 917 Name: n.ProvidedBy()[0], 918 Output: &provider, 919 }, 920 &EvalReadState{ 921 Name: n.stateId(), 922 Output: &state, 923 }, 924 &EvalRequireState{ 925 State: &state, 926 }, 927 // Make sure we handle data sources properly. 928 &EvalIf{ 929 If: func(ctx EvalContext) (bool, error) { 930 if n.Resource.Mode == config.DataResourceMode { 931 return true, nil 932 } 933 934 return false, nil 935 }, 936 937 Then: &EvalReadDataApply{ 938 Info: info, 939 Diff: &diffApply, 940 Provider: &provider, 941 Output: &state, 942 }, 943 Else: &EvalApply{ 944 Info: info, 945 State: &state, 946 Diff: &diffApply, 947 Provider: &provider, 948 Output: &state, 949 Error: &err, 950 }, 951 }, 952 &EvalWriteState{ 953 Name: n.stateId(), 954 ResourceType: n.Resource.Type, 955 Provider: n.Resource.Provider, 956 Dependencies: n.StateDependencies(), 957 State: &state, 958 }, 959 &EvalApplyPost{ 960 Info: info, 961 State: &state, 962 Error: &err, 963 }, 964 }, 965 }, 966 } 967 }