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