From ebd5a17b1701cb0f264b6e7dfdd49f1c19c82f77 Mon Sep 17 00:00:00 2001 From: James Bardin Date: Tue, 8 Nov 2022 10:21:35 -0500 Subject: [PATCH] ensure destroy edges from data sources Data resource dependencies are not stored in the state, so we need to take the latest dependency set to use for any direct connections to destroy nodes. --- .../node_resource_abstract_instance.go | 10 +++- .../terraform/transform_destroy_edge_test.go | 48 +++++++++++++++++++ 2 files changed, 56 insertions(+), 2 deletions(-) diff --git a/internal/terraform/node_resource_abstract_instance.go b/internal/terraform/node_resource_abstract_instance.go index 9ff15b8408..27c86b48de 100644 --- a/internal/terraform/node_resource_abstract_instance.go +++ b/internal/terraform/node_resource_abstract_instance.go @@ -96,15 +96,21 @@ func (n *NodeAbstractResourceInstance) References() []*addrs.Reference { return nil } -// StateDependencies returns the dependencies saved in the state. +// StateDependencies returns the dependencies which will be saved in the state +// for managed resources, or the most current dependencies for data resources. func (n *NodeAbstractResourceInstance) StateDependencies() []addrs.ConfigResource { + // Managed resources prefer the stored dependencies, to avoid possible + // conflicts in ordering when refactoring configuration. if s := n.instanceState; s != nil { if s.Current != nil { return s.Current.Dependencies } } - return nil + // If there are no stored dependencies, this is either a newly created + // managed resource, or a data source, and we can use the most recently + // calculated dependencies. + return n.Dependencies } // GraphNodeProviderConsumer diff --git a/internal/terraform/transform_destroy_edge_test.go b/internal/terraform/transform_destroy_edge_test.go index b4fc351be2..c82d07e385 100644 --- a/internal/terraform/transform_destroy_edge_test.go +++ b/internal/terraform/transform_destroy_edge_test.go @@ -509,6 +509,54 @@ test_object.C (destroy)`) } } +func TestDestroyEdgeTransformer_dataDependsOn(t *testing.T) { + g := Graph{Path: addrs.RootModuleInstance} + + addrA := mustResourceInstanceAddr("test_object.A") + instA := NewNodeAbstractResourceInstance(addrA) + a := &NodeDestroyResourceInstance{NodeAbstractResourceInstance: instA} + g.Add(a) + + // B here represents a data sources, which is effectively an update during + // apply, but won't have dependencies stored in the state. + addrB := mustResourceInstanceAddr("test_object.B") + instB := NewNodeAbstractResourceInstance(addrB) + instB.Dependencies = append(instB.Dependencies, addrA.ConfigResource()) + b := &NodeApplyableResourceInstance{NodeAbstractResourceInstance: instB} + + g.Add(b) + + state := states.NewState() + root := state.EnsureModule(addrs.RootModuleInstance) + root.SetResourceInstanceCurrent( + mustResourceInstanceAddr("test_object.A").Resource, + &states.ResourceInstanceObjectSrc{ + Status: states.ObjectReady, + AttrsJSON: []byte(`{"id":"A"}`), + }, + mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`), + ) + + if err := (&AttachStateTransformer{State: state}).Transform(&g); err != nil { + t.Fatal(err) + } + + tf := &DestroyEdgeTransformer{} + if err := tf.Transform(&g); err != nil { + t.Fatalf("err: %s", err) + } + + actual := strings.TrimSpace(g.String()) + expected := strings.TrimSpace(` +test_object.A (destroy) +test_object.B + test_object.A (destroy) +`) + if actual != expected { + t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected) + } +} + func testDestroyNode(addrString string) GraphNodeDestroyer { instAddr := mustResourceInstanceAddr(addrString) inst := NewNodeAbstractResourceInstance(instAddr)