github.com/muratcelep/terraform@v1.1.0-beta2-not-internal-4/not-internal/terraform/transform_destroy_edge_test.go (about)

     1  package terraform
     2  
     3  import (
     4  	"fmt"
     5  	"strings"
     6  	"testing"
     7  
     8  	"github.com/muratcelep/terraform/not-internal/addrs"
     9  	"github.com/muratcelep/terraform/not-internal/states"
    10  )
    11  
    12  func TestDestroyEdgeTransformer_basic(t *testing.T) {
    13  	g := Graph{Path: addrs.RootModuleInstance}
    14  	g.Add(testDestroyNode("test_object.A"))
    15  	g.Add(testDestroyNode("test_object.B"))
    16  
    17  	state := states.NewState()
    18  	root := state.EnsureModule(addrs.RootModuleInstance)
    19  	root.SetResourceInstanceCurrent(
    20  		mustResourceInstanceAddr("test_object.A").Resource,
    21  		&states.ResourceInstanceObjectSrc{
    22  			Status:    states.ObjectReady,
    23  			AttrsJSON: []byte(`{"id":"A"}`),
    24  		},
    25  		mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`),
    26  	)
    27  	root.SetResourceInstanceCurrent(
    28  		mustResourceInstanceAddr("test_object.B").Resource,
    29  		&states.ResourceInstanceObjectSrc{
    30  			Status:       states.ObjectReady,
    31  			AttrsJSON:    []byte(`{"id":"B","test_string":"x"}`),
    32  			Dependencies: []addrs.ConfigResource{mustConfigResourceAddr("test_object.A")},
    33  		},
    34  		mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`),
    35  	)
    36  	if err := (&AttachStateTransformer{State: state}).Transform(&g); err != nil {
    37  		t.Fatal(err)
    38  	}
    39  
    40  	tf := &DestroyEdgeTransformer{
    41  		Config: testModule(t, "transform-destroy-edge-basic"),
    42  	}
    43  	if err := tf.Transform(&g); err != nil {
    44  		t.Fatalf("err: %s", err)
    45  	}
    46  
    47  	actual := strings.TrimSpace(g.String())
    48  	expected := strings.TrimSpace(testTransformDestroyEdgeBasicStr)
    49  	if actual != expected {
    50  		t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected)
    51  	}
    52  }
    53  
    54  func TestDestroyEdgeTransformer_multi(t *testing.T) {
    55  	g := Graph{Path: addrs.RootModuleInstance}
    56  	g.Add(testDestroyNode("test_object.A"))
    57  	g.Add(testDestroyNode("test_object.B"))
    58  	g.Add(testDestroyNode("test_object.C"))
    59  
    60  	state := states.NewState()
    61  	root := state.EnsureModule(addrs.RootModuleInstance)
    62  	root.SetResourceInstanceCurrent(
    63  		mustResourceInstanceAddr("test_object.A").Resource,
    64  		&states.ResourceInstanceObjectSrc{
    65  			Status:    states.ObjectReady,
    66  			AttrsJSON: []byte(`{"id":"A"}`),
    67  		},
    68  		mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`),
    69  	)
    70  	root.SetResourceInstanceCurrent(
    71  		mustResourceInstanceAddr("test_object.B").Resource,
    72  		&states.ResourceInstanceObjectSrc{
    73  			Status:       states.ObjectReady,
    74  			AttrsJSON:    []byte(`{"id":"B","test_string":"x"}`),
    75  			Dependencies: []addrs.ConfigResource{mustConfigResourceAddr("test_object.A")},
    76  		},
    77  		mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`),
    78  	)
    79  	root.SetResourceInstanceCurrent(
    80  		mustResourceInstanceAddr("test_object.C").Resource,
    81  		&states.ResourceInstanceObjectSrc{
    82  			Status:    states.ObjectReady,
    83  			AttrsJSON: []byte(`{"id":"C","test_string":"x"}`),
    84  			Dependencies: []addrs.ConfigResource{
    85  				mustConfigResourceAddr("test_object.A"),
    86  				mustConfigResourceAddr("test_object.B"),
    87  			},
    88  		},
    89  		mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`),
    90  	)
    91  
    92  	if err := (&AttachStateTransformer{State: state}).Transform(&g); err != nil {
    93  		t.Fatal(err)
    94  	}
    95  
    96  	tf := &DestroyEdgeTransformer{
    97  		Config: testModule(t, "transform-destroy-edge-multi"),
    98  	}
    99  	if err := tf.Transform(&g); err != nil {
   100  		t.Fatalf("err: %s", err)
   101  	}
   102  
   103  	actual := strings.TrimSpace(g.String())
   104  	expected := strings.TrimSpace(testTransformDestroyEdgeMultiStr)
   105  	if actual != expected {
   106  		t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected)
   107  	}
   108  }
   109  
   110  func TestDestroyEdgeTransformer_selfRef(t *testing.T) {
   111  	g := Graph{Path: addrs.RootModuleInstance}
   112  	g.Add(testDestroyNode("test_object.A"))
   113  	tf := &DestroyEdgeTransformer{
   114  		Config: testModule(t, "transform-destroy-edge-self-ref"),
   115  	}
   116  	if err := tf.Transform(&g); err != nil {
   117  		t.Fatalf("err: %s", err)
   118  	}
   119  
   120  	actual := strings.TrimSpace(g.String())
   121  	expected := strings.TrimSpace(testTransformDestroyEdgeSelfRefStr)
   122  	if actual != expected {
   123  		t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected)
   124  	}
   125  }
   126  
   127  func TestDestroyEdgeTransformer_module(t *testing.T) {
   128  	g := Graph{Path: addrs.RootModuleInstance}
   129  	g.Add(testDestroyNode("module.child.test_object.b"))
   130  	g.Add(testDestroyNode("test_object.a"))
   131  	state := states.NewState()
   132  	root := state.EnsureModule(addrs.RootModuleInstance)
   133  	child := state.EnsureModule(addrs.RootModuleInstance.Child("child", addrs.NoKey))
   134  	root.SetResourceInstanceCurrent(
   135  		mustResourceInstanceAddr("test_object.a").Resource,
   136  		&states.ResourceInstanceObjectSrc{
   137  			Status:       states.ObjectReady,
   138  			AttrsJSON:    []byte(`{"id":"a"}`),
   139  			Dependencies: []addrs.ConfigResource{mustConfigResourceAddr("module.child.test_object.b")},
   140  		},
   141  		mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`),
   142  	)
   143  	child.SetResourceInstanceCurrent(
   144  		mustResourceInstanceAddr("test_object.b").Resource,
   145  		&states.ResourceInstanceObjectSrc{
   146  			Status:    states.ObjectReady,
   147  			AttrsJSON: []byte(`{"id":"b","test_string":"x"}`),
   148  		},
   149  		mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`),
   150  	)
   151  
   152  	if err := (&AttachStateTransformer{State: state}).Transform(&g); err != nil {
   153  		t.Fatal(err)
   154  	}
   155  
   156  	tf := &DestroyEdgeTransformer{
   157  		Config: testModule(t, "transform-destroy-edge-module"),
   158  	}
   159  	if err := tf.Transform(&g); err != nil {
   160  		t.Fatalf("err: %s", err)
   161  	}
   162  
   163  	actual := strings.TrimSpace(g.String())
   164  	expected := strings.TrimSpace(testTransformDestroyEdgeModuleStr)
   165  	if actual != expected {
   166  		t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected)
   167  	}
   168  }
   169  
   170  func TestDestroyEdgeTransformer_moduleOnly(t *testing.T) {
   171  	g := Graph{Path: addrs.RootModuleInstance}
   172  
   173  	state := states.NewState()
   174  	for moduleIdx := 0; moduleIdx < 2; moduleIdx++ {
   175  		g.Add(testDestroyNode(fmt.Sprintf("module.child[%d].test_object.a", moduleIdx)))
   176  		g.Add(testDestroyNode(fmt.Sprintf("module.child[%d].test_object.b", moduleIdx)))
   177  		g.Add(testDestroyNode(fmt.Sprintf("module.child[%d].test_object.c", moduleIdx)))
   178  
   179  		child := state.EnsureModule(addrs.RootModuleInstance.Child("child", addrs.IntKey(moduleIdx)))
   180  		child.SetResourceInstanceCurrent(
   181  			mustResourceInstanceAddr("test_object.a").Resource,
   182  			&states.ResourceInstanceObjectSrc{
   183  				Status:    states.ObjectReady,
   184  				AttrsJSON: []byte(`{"id":"a"}`),
   185  			},
   186  			mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`),
   187  		)
   188  		child.SetResourceInstanceCurrent(
   189  			mustResourceInstanceAddr("test_object.b").Resource,
   190  			&states.ResourceInstanceObjectSrc{
   191  				Status:    states.ObjectReady,
   192  				AttrsJSON: []byte(`{"id":"b","test_string":"x"}`),
   193  				Dependencies: []addrs.ConfigResource{
   194  					mustConfigResourceAddr("module.child.test_object.a"),
   195  				},
   196  			},
   197  			mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`),
   198  		)
   199  		child.SetResourceInstanceCurrent(
   200  			mustResourceInstanceAddr("test_object.c").Resource,
   201  			&states.ResourceInstanceObjectSrc{
   202  				Status:    states.ObjectReady,
   203  				AttrsJSON: []byte(`{"id":"c","test_string":"x"}`),
   204  				Dependencies: []addrs.ConfigResource{
   205  					mustConfigResourceAddr("module.child.test_object.a"),
   206  					mustConfigResourceAddr("module.child.test_object.b"),
   207  				},
   208  			},
   209  			mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`),
   210  		)
   211  	}
   212  
   213  	if err := (&AttachStateTransformer{State: state}).Transform(&g); err != nil {
   214  		t.Fatal(err)
   215  	}
   216  
   217  	tf := &DestroyEdgeTransformer{
   218  		Config: testModule(t, "transform-destroy-edge-module-only"),
   219  	}
   220  	if err := tf.Transform(&g); err != nil {
   221  		t.Fatalf("err: %s", err)
   222  	}
   223  
   224  	// The analyses done in the destroy edge transformer are between
   225  	// not-yet-expanded objects, which is conservative and so it will generate
   226  	// edges that aren't strictly necessary. As a special case we filter out
   227  	// any edges that are between resources instances that are in different
   228  	// instances of the same module, because those edges are never needed
   229  	// (one instance of a module cannot depend on another instance of the
   230  	// same module) and including them can, in complex cases, cause cycles due
   231  	// to unnecessary interactions between destroyed and created module
   232  	// instances in the same plan.
   233  	//
   234  	// Therefore below we expect to see the dependencies within each instance
   235  	// of module.child reflected, but we should not see any dependencies
   236  	// _between_ instances of module.child.
   237  
   238  	actual := strings.TrimSpace(g.String())
   239  	expected := strings.TrimSpace(`
   240  module.child[0].test_object.a (destroy)
   241    module.child[0].test_object.b (destroy)
   242    module.child[0].test_object.c (destroy)
   243  module.child[0].test_object.b (destroy)
   244    module.child[0].test_object.c (destroy)
   245  module.child[0].test_object.c (destroy)
   246  module.child[1].test_object.a (destroy)
   247    module.child[1].test_object.b (destroy)
   248    module.child[1].test_object.c (destroy)
   249  module.child[1].test_object.b (destroy)
   250    module.child[1].test_object.c (destroy)
   251  module.child[1].test_object.c (destroy)
   252  `)
   253  	if actual != expected {
   254  		t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected)
   255  	}
   256  }
   257  
   258  func TestDestroyEdgeTransformer_destroyThenUpdate(t *testing.T) {
   259  	g := Graph{Path: addrs.RootModuleInstance}
   260  	g.Add(testUpdateNode("test_object.A"))
   261  	g.Add(testDestroyNode("test_object.B"))
   262  
   263  	state := states.NewState()
   264  	root := state.EnsureModule(addrs.RootModuleInstance)
   265  	root.SetResourceInstanceCurrent(
   266  		mustResourceInstanceAddr("test_object.A").Resource,
   267  		&states.ResourceInstanceObjectSrc{
   268  			Status:    states.ObjectReady,
   269  			AttrsJSON: []byte(`{"id":"A","test_string":"old"}`),
   270  		},
   271  		mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`),
   272  	)
   273  	root.SetResourceInstanceCurrent(
   274  		mustResourceInstanceAddr("test_object.B").Resource,
   275  		&states.ResourceInstanceObjectSrc{
   276  			Status:       states.ObjectReady,
   277  			AttrsJSON:    []byte(`{"id":"B","test_string":"x"}`),
   278  			Dependencies: []addrs.ConfigResource{mustConfigResourceAddr("test_object.A")},
   279  		},
   280  		mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`),
   281  	)
   282  
   283  	if err := (&AttachStateTransformer{State: state}).Transform(&g); err != nil {
   284  		t.Fatal(err)
   285  	}
   286  
   287  	m := testModuleInline(t, map[string]string{
   288  		"main.tf": `
   289  resource "test_instance" "a" {
   290  	test_string = "udpated"
   291  }
   292  `,
   293  	})
   294  	tf := &DestroyEdgeTransformer{
   295  		Config: m,
   296  	}
   297  	if err := tf.Transform(&g); err != nil {
   298  		t.Fatalf("err: %s", err)
   299  	}
   300  
   301  	expected := strings.TrimSpace(`
   302  test_object.A
   303    test_object.B (destroy)
   304  test_object.B (destroy)
   305  `)
   306  	actual := strings.TrimSpace(g.String())
   307  
   308  	if actual != expected {
   309  		t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected)
   310  	}
   311  }
   312  
   313  func testDestroyNode(addrString string) GraphNodeDestroyer {
   314  	instAddr := mustResourceInstanceAddr(addrString)
   315  	inst := NewNodeAbstractResourceInstance(instAddr)
   316  	return &NodeDestroyResourceInstance{NodeAbstractResourceInstance: inst}
   317  }
   318  
   319  func testUpdateNode(addrString string) GraphNodeCreator {
   320  	instAddr := mustResourceInstanceAddr(addrString)
   321  	inst := NewNodeAbstractResourceInstance(instAddr)
   322  	return &NodeApplyableResourceInstance{NodeAbstractResourceInstance: inst}
   323  }
   324  
   325  const testTransformDestroyEdgeBasicStr = `
   326  test_object.A (destroy)
   327    test_object.B (destroy)
   328  test_object.B (destroy)
   329  `
   330  
   331  const testTransformDestroyEdgeMultiStr = `
   332  test_object.A (destroy)
   333    test_object.B (destroy)
   334    test_object.C (destroy)
   335  test_object.B (destroy)
   336    test_object.C (destroy)
   337  test_object.C (destroy)
   338  `
   339  
   340  const testTransformDestroyEdgeSelfRefStr = `
   341  test_object.A (destroy)
   342  `
   343  
   344  const testTransformDestroyEdgeModuleStr = `
   345  module.child.test_object.b (destroy)
   346    test_object.a (destroy)
   347  test_object.a (destroy)
   348  `