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 `