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