github.com/opentofu/opentofu@v1.7.1/internal/tofu/transform_reference_test.go (about)

     1  // Copyright (c) The OpenTofu Authors
     2  // SPDX-License-Identifier: MPL-2.0
     3  // Copyright (c) 2023 HashiCorp, Inc.
     4  // SPDX-License-Identifier: MPL-2.0
     5  
     6  package tofu
     7  
     8  import (
     9  	"reflect"
    10  	"sort"
    11  	"strings"
    12  	"testing"
    13  
    14  	"github.com/opentofu/opentofu/internal/addrs"
    15  	"github.com/opentofu/opentofu/internal/dag"
    16  )
    17  
    18  func TestReferenceTransformer_simple(t *testing.T) {
    19  	g := Graph{Path: addrs.RootModuleInstance}
    20  	g.Add(&graphNodeRefParentTest{
    21  		NameValue: "A",
    22  		Names:     []string{"A"},
    23  	})
    24  	g.Add(&graphNodeRefChildTest{
    25  		NameValue: "B",
    26  		Refs:      []string{"A"},
    27  	})
    28  
    29  	tf := &ReferenceTransformer{}
    30  	if err := tf.Transform(&g); err != nil {
    31  		t.Fatalf("err: %s", err)
    32  	}
    33  
    34  	actual := strings.TrimSpace(g.String())
    35  	expected := strings.TrimSpace(testTransformRefBasicStr)
    36  	if actual != expected {
    37  		t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected)
    38  	}
    39  }
    40  
    41  func TestReferenceTransformer_self(t *testing.T) {
    42  	g := Graph{Path: addrs.RootModuleInstance}
    43  	g.Add(&graphNodeRefParentTest{
    44  		NameValue: "A",
    45  		Names:     []string{"A"},
    46  	})
    47  	g.Add(&graphNodeRefChildTest{
    48  		NameValue: "B",
    49  		Refs:      []string{"A", "B"},
    50  	})
    51  
    52  	tf := &ReferenceTransformer{}
    53  	if err := tf.Transform(&g); err != nil {
    54  		t.Fatalf("err: %s", err)
    55  	}
    56  
    57  	actual := strings.TrimSpace(g.String())
    58  	expected := strings.TrimSpace(testTransformRefBasicStr)
    59  	if actual != expected {
    60  		t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected)
    61  	}
    62  }
    63  
    64  func TestReferenceTransformer_path(t *testing.T) {
    65  	g := Graph{Path: addrs.RootModuleInstance}
    66  	g.Add(&graphNodeRefParentTest{
    67  		NameValue: "A",
    68  		Names:     []string{"A"},
    69  	})
    70  	g.Add(&graphNodeRefChildTest{
    71  		NameValue: "B",
    72  		Refs:      []string{"A"},
    73  	})
    74  	g.Add(&graphNodeRefParentTest{
    75  		NameValue: "child.A",
    76  		PathValue: addrs.ModuleInstance{addrs.ModuleInstanceStep{Name: "child"}},
    77  		Names:     []string{"A"},
    78  	})
    79  	g.Add(&graphNodeRefChildTest{
    80  		NameValue: "child.B",
    81  		PathValue: addrs.ModuleInstance{addrs.ModuleInstanceStep{Name: "child"}},
    82  		Refs:      []string{"A"},
    83  	})
    84  
    85  	tf := &ReferenceTransformer{}
    86  	if err := tf.Transform(&g); err != nil {
    87  		t.Fatalf("err: %s", err)
    88  	}
    89  
    90  	actual := strings.TrimSpace(g.String())
    91  	expected := strings.TrimSpace(testTransformRefPathStr)
    92  	if actual != expected {
    93  		t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected)
    94  	}
    95  }
    96  
    97  func TestReferenceTransformer_resourceInstances(t *testing.T) {
    98  	// Our reference analyses are all done based on unexpanded addresses
    99  	// so that we can use this transformer both in the plan graph (where things
   100  	// are not expanded yet) and the apply graph (where resource instances are
   101  	// pre-expanded but nothing else is.)
   102  	// However, that would make the result too conservative about instances
   103  	// of the same resource in different instances of the same module, so we
   104  	// make an exception for that situation in particular, keeping references
   105  	// between resource instances segregated by their containing module
   106  	// instance.
   107  	g := Graph{Path: addrs.RootModuleInstance}
   108  	moduleInsts := []addrs.ModuleInstance{
   109  		{
   110  			{
   111  				Name: "foo", InstanceKey: addrs.IntKey(0),
   112  			},
   113  		},
   114  		{
   115  			{
   116  				Name: "foo", InstanceKey: addrs.IntKey(1),
   117  			},
   118  		},
   119  	}
   120  	resourceAs := make([]addrs.AbsResourceInstance, len(moduleInsts))
   121  	for i, moduleInst := range moduleInsts {
   122  		resourceAs[i] = addrs.Resource{
   123  			Mode: addrs.ManagedResourceMode,
   124  			Type: "thing",
   125  			Name: "a",
   126  		}.Instance(addrs.NoKey).Absolute(moduleInst)
   127  	}
   128  	resourceBs := make([]addrs.AbsResourceInstance, len(moduleInsts))
   129  	for i, moduleInst := range moduleInsts {
   130  		resourceBs[i] = addrs.Resource{
   131  			Mode: addrs.ManagedResourceMode,
   132  			Type: "thing",
   133  			Name: "b",
   134  		}.Instance(addrs.NoKey).Absolute(moduleInst)
   135  	}
   136  	g.Add(&graphNodeFakeResourceInstance{
   137  		Addr: resourceAs[0],
   138  	})
   139  	g.Add(&graphNodeFakeResourceInstance{
   140  		Addr: resourceBs[0],
   141  		Refs: []*addrs.Reference{
   142  			{
   143  				Subject: resourceAs[0].Resource,
   144  			},
   145  		},
   146  	})
   147  	g.Add(&graphNodeFakeResourceInstance{
   148  		Addr: resourceAs[1],
   149  	})
   150  	g.Add(&graphNodeFakeResourceInstance{
   151  		Addr: resourceBs[1],
   152  		Refs: []*addrs.Reference{
   153  			{
   154  				Subject: resourceAs[1].Resource,
   155  			},
   156  		},
   157  	})
   158  
   159  	tf := &ReferenceTransformer{}
   160  	if err := tf.Transform(&g); err != nil {
   161  		t.Fatalf("unexpected error: %s", err)
   162  	}
   163  
   164  	// Resource B should be connected to resource A in each module instance,
   165  	// but there should be no connections between the two module instances.
   166  	actual := strings.TrimSpace(g.String())
   167  	expected := strings.TrimSpace(`
   168  module.foo[0].thing.a
   169  module.foo[0].thing.b
   170    module.foo[0].thing.a
   171  module.foo[1].thing.a
   172  module.foo[1].thing.b
   173    module.foo[1].thing.a
   174  `)
   175  	if actual != expected {
   176  		t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected)
   177  	}
   178  }
   179  
   180  func TestReferenceMapReferences(t *testing.T) {
   181  	cases := map[string]struct {
   182  		Nodes  []dag.Vertex
   183  		Check  dag.Vertex
   184  		Result []string
   185  	}{
   186  		"simple": {
   187  			Nodes: []dag.Vertex{
   188  				&graphNodeRefParentTest{
   189  					NameValue: "A",
   190  					Names:     []string{"A"},
   191  				},
   192  			},
   193  			Check: &graphNodeRefChildTest{
   194  				NameValue: "foo",
   195  				Refs:      []string{"A"},
   196  			},
   197  			Result: []string{"A"},
   198  		},
   199  	}
   200  
   201  	for tn, tc := range cases {
   202  		t.Run(tn, func(t *testing.T) {
   203  			rm := NewReferenceMap(tc.Nodes)
   204  			result := rm.References(tc.Check)
   205  
   206  			var resultStr []string
   207  			for _, v := range result {
   208  				resultStr = append(resultStr, dag.VertexName(v))
   209  			}
   210  
   211  			sort.Strings(resultStr)
   212  			sort.Strings(tc.Result)
   213  			if !reflect.DeepEqual(resultStr, tc.Result) {
   214  				t.Fatalf("bad: %#v", resultStr)
   215  			}
   216  		})
   217  	}
   218  }
   219  
   220  type graphNodeRefParentTest struct {
   221  	NameValue string
   222  	PathValue addrs.ModuleInstance
   223  	Names     []string
   224  }
   225  
   226  var _ GraphNodeReferenceable = (*graphNodeRefParentTest)(nil)
   227  
   228  func (n *graphNodeRefParentTest) Name() string {
   229  	return n.NameValue
   230  }
   231  
   232  func (n *graphNodeRefParentTest) ReferenceableAddrs() []addrs.Referenceable {
   233  	ret := make([]addrs.Referenceable, len(n.Names))
   234  	for i, name := range n.Names {
   235  		ret[i] = addrs.LocalValue{Name: name}
   236  	}
   237  	return ret
   238  }
   239  
   240  func (n *graphNodeRefParentTest) Path() addrs.ModuleInstance {
   241  	return n.PathValue
   242  }
   243  
   244  func (n *graphNodeRefParentTest) ModulePath() addrs.Module {
   245  	return n.PathValue.Module()
   246  }
   247  
   248  type graphNodeRefChildTest struct {
   249  	NameValue string
   250  	PathValue addrs.ModuleInstance
   251  	Refs      []string
   252  }
   253  
   254  var _ GraphNodeReferencer = (*graphNodeRefChildTest)(nil)
   255  
   256  func (n *graphNodeRefChildTest) Name() string {
   257  	return n.NameValue
   258  }
   259  
   260  func (n *graphNodeRefChildTest) References() []*addrs.Reference {
   261  	ret := make([]*addrs.Reference, len(n.Refs))
   262  	for i, name := range n.Refs {
   263  		ret[i] = &addrs.Reference{
   264  			Subject: addrs.LocalValue{Name: name},
   265  		}
   266  	}
   267  	return ret
   268  }
   269  
   270  func (n *graphNodeRefChildTest) Path() addrs.ModuleInstance {
   271  	return n.PathValue
   272  }
   273  
   274  func (n *graphNodeRefChildTest) ModulePath() addrs.Module {
   275  	return n.PathValue.Module()
   276  }
   277  
   278  type graphNodeFakeResourceInstance struct {
   279  	Addr addrs.AbsResourceInstance
   280  	Refs []*addrs.Reference
   281  }
   282  
   283  var _ GraphNodeResourceInstance = (*graphNodeFakeResourceInstance)(nil)
   284  var _ GraphNodeReferenceable = (*graphNodeFakeResourceInstance)(nil)
   285  var _ GraphNodeReferencer = (*graphNodeFakeResourceInstance)(nil)
   286  
   287  func (n *graphNodeFakeResourceInstance) ResourceInstanceAddr() addrs.AbsResourceInstance {
   288  	return n.Addr
   289  }
   290  
   291  func (n *graphNodeFakeResourceInstance) ModulePath() addrs.Module {
   292  	return n.Addr.Module.Module()
   293  }
   294  
   295  func (n *graphNodeFakeResourceInstance) ReferenceableAddrs() []addrs.Referenceable {
   296  	return []addrs.Referenceable{n.Addr.Resource}
   297  }
   298  
   299  func (n *graphNodeFakeResourceInstance) References() []*addrs.Reference {
   300  	return n.Refs
   301  }
   302  
   303  func (n *graphNodeFakeResourceInstance) StateDependencies() []addrs.ConfigResource {
   304  	return nil
   305  }
   306  
   307  func (n *graphNodeFakeResourceInstance) String() string {
   308  	return n.Addr.String()
   309  }
   310  
   311  const testTransformRefBasicStr = `
   312  A
   313  B
   314    A
   315  `
   316  
   317  const testTransformRefPathStr = `
   318  A
   319  B
   320    A
   321  child.A
   322  child.B
   323    child.A
   324  `