github.com/jaredpalmer/terraform@v1.1.0-alpha20210908.0.20210911170307-88705c943a03/internal/instances/expander_test.go (about) 1 package instances 2 3 import ( 4 "fmt" 5 "strings" 6 "testing" 7 8 "github.com/google/go-cmp/cmp" 9 "github.com/zclconf/go-cty/cty" 10 11 "github.com/hashicorp/terraform/internal/addrs" 12 ) 13 14 func TestExpander(t *testing.T) { 15 // Some module and resource addresses and values we'll use repeatedly below. 16 singleModuleAddr := addrs.ModuleCall{Name: "single"} 17 count2ModuleAddr := addrs.ModuleCall{Name: "count2"} 18 count0ModuleAddr := addrs.ModuleCall{Name: "count0"} 19 forEachModuleAddr := addrs.ModuleCall{Name: "for_each"} 20 singleResourceAddr := addrs.Resource{ 21 Mode: addrs.ManagedResourceMode, 22 Type: "test", 23 Name: "single", 24 } 25 count2ResourceAddr := addrs.Resource{ 26 Mode: addrs.ManagedResourceMode, 27 Type: "test", 28 Name: "count2", 29 } 30 count0ResourceAddr := addrs.Resource{ 31 Mode: addrs.ManagedResourceMode, 32 Type: "test", 33 Name: "count0", 34 } 35 forEachResourceAddr := addrs.Resource{ 36 Mode: addrs.ManagedResourceMode, 37 Type: "test", 38 Name: "for_each", 39 } 40 eachMap := map[string]cty.Value{ 41 "a": cty.NumberIntVal(1), 42 "b": cty.NumberIntVal(2), 43 } 44 45 // In normal use, Expander would be called in the context of a graph 46 // traversal to ensure that information is registered/requested in the 47 // correct sequence, but to keep this test self-contained we'll just 48 // manually write out the steps here. 49 // 50 // The steps below are assuming a configuration tree like the following: 51 // - root module 52 // - resource test.single with no count or for_each 53 // - resource test.count2 with count = 2 54 // - resource test.count0 with count = 0 55 // - resource test.for_each with for_each = { a = 1, b = 2 } 56 // - child module "single" with no count or for_each 57 // - resource test.single with no count or for_each 58 // - resource test.count2 with count = 2 59 // - child module "count2" with count = 2 60 // - resource test.single with no count or for_each 61 // - resource test.count2 with count = 2 62 // - child module "count2" with count = 2 63 // - resource test.count2 with count = 2 64 // - child module "count0" with count = 0 65 // - resource test.single with no count or for_each 66 // - child module for_each with for_each = { a = 1, b = 2 } 67 // - resource test.single with no count or for_each 68 // - resource test.count2 with count = 2 69 70 ex := NewExpander() 71 72 // We don't register the root module, because it's always implied to exist. 73 // 74 // Below we're going to use braces and indentation just to help visually 75 // reflect the tree structure from the tree in the above comment, in the 76 // hope that the following is easier to follow. 77 // 78 // The Expander API requires that we register containing modules before 79 // registering anything inside them, so we'll work through the above 80 // in a depth-first order in the registration steps that follow. 81 { 82 ex.SetResourceSingle(addrs.RootModuleInstance, singleResourceAddr) 83 ex.SetResourceCount(addrs.RootModuleInstance, count2ResourceAddr, 2) 84 ex.SetResourceCount(addrs.RootModuleInstance, count0ResourceAddr, 0) 85 ex.SetResourceForEach(addrs.RootModuleInstance, forEachResourceAddr, eachMap) 86 87 ex.SetModuleSingle(addrs.RootModuleInstance, singleModuleAddr) 88 { 89 // The single instance of the module 90 moduleInstanceAddr := addrs.RootModuleInstance.Child("single", addrs.NoKey) 91 ex.SetResourceSingle(moduleInstanceAddr, singleResourceAddr) 92 ex.SetResourceCount(moduleInstanceAddr, count2ResourceAddr, 2) 93 } 94 95 ex.SetModuleCount(addrs.RootModuleInstance, count2ModuleAddr, 2) 96 for i1 := 0; i1 < 2; i1++ { 97 moduleInstanceAddr := addrs.RootModuleInstance.Child("count2", addrs.IntKey(i1)) 98 ex.SetResourceSingle(moduleInstanceAddr, singleResourceAddr) 99 ex.SetResourceCount(moduleInstanceAddr, count2ResourceAddr, 2) 100 ex.SetModuleCount(moduleInstanceAddr, count2ModuleAddr, 2) 101 for i2 := 0; i2 < 2; i2++ { 102 moduleInstanceAddr := moduleInstanceAddr.Child("count2", addrs.IntKey(i2)) 103 ex.SetResourceCount(moduleInstanceAddr, count2ResourceAddr, 2) 104 } 105 } 106 107 ex.SetModuleCount(addrs.RootModuleInstance, count0ModuleAddr, 0) 108 { 109 // There are no instances of module "count0", so our nested module 110 // would never actually get registered here: the expansion node 111 // for the resource would see that its containing module has no 112 // instances and so do nothing. 113 } 114 115 ex.SetModuleForEach(addrs.RootModuleInstance, forEachModuleAddr, eachMap) 116 for k := range eachMap { 117 moduleInstanceAddr := addrs.RootModuleInstance.Child("for_each", addrs.StringKey(k)) 118 ex.SetResourceSingle(moduleInstanceAddr, singleResourceAddr) 119 ex.SetResourceCount(moduleInstanceAddr, count2ResourceAddr, 2) 120 } 121 } 122 123 t.Run("root module", func(t *testing.T) { 124 // Requesting expansion of the root module doesn't really mean anything 125 // since it's always a singleton, but for consistency it should work. 126 got := ex.ExpandModule(addrs.RootModule) 127 want := []addrs.ModuleInstance{addrs.RootModuleInstance} 128 if diff := cmp.Diff(want, got); diff != "" { 129 t.Errorf("wrong result\n%s", diff) 130 } 131 }) 132 t.Run("resource single", func(t *testing.T) { 133 got := ex.ExpandModuleResource( 134 addrs.RootModule, 135 singleResourceAddr, 136 ) 137 want := []addrs.AbsResourceInstance{ 138 mustAbsResourceInstanceAddr(`test.single`), 139 } 140 if diff := cmp.Diff(want, got); diff != "" { 141 t.Errorf("wrong result\n%s", diff) 142 } 143 }) 144 t.Run("resource count2", func(t *testing.T) { 145 got := ex.ExpandModuleResource( 146 addrs.RootModule, 147 count2ResourceAddr, 148 ) 149 want := []addrs.AbsResourceInstance{ 150 mustAbsResourceInstanceAddr(`test.count2[0]`), 151 mustAbsResourceInstanceAddr(`test.count2[1]`), 152 } 153 if diff := cmp.Diff(want, got); diff != "" { 154 t.Errorf("wrong result\n%s", diff) 155 } 156 }) 157 t.Run("resource count0", func(t *testing.T) { 158 got := ex.ExpandModuleResource( 159 addrs.RootModule, 160 count0ResourceAddr, 161 ) 162 want := []addrs.AbsResourceInstance(nil) 163 if diff := cmp.Diff(want, got); diff != "" { 164 t.Errorf("wrong result\n%s", diff) 165 } 166 }) 167 t.Run("resource for_each", func(t *testing.T) { 168 got := ex.ExpandModuleResource( 169 addrs.RootModule, 170 forEachResourceAddr, 171 ) 172 want := []addrs.AbsResourceInstance{ 173 mustAbsResourceInstanceAddr(`test.for_each["a"]`), 174 mustAbsResourceInstanceAddr(`test.for_each["b"]`), 175 } 176 if diff := cmp.Diff(want, got); diff != "" { 177 t.Errorf("wrong result\n%s", diff) 178 } 179 }) 180 t.Run("module single", func(t *testing.T) { 181 got := ex.ExpandModule(addrs.RootModule.Child("single")) 182 want := []addrs.ModuleInstance{ 183 mustModuleInstanceAddr(`module.single`), 184 } 185 if diff := cmp.Diff(want, got); diff != "" { 186 t.Errorf("wrong result\n%s", diff) 187 } 188 }) 189 t.Run("module single resource single", func(t *testing.T) { 190 got := ex.ExpandModuleResource( 191 mustModuleAddr("single"), 192 singleResourceAddr, 193 ) 194 want := []addrs.AbsResourceInstance{ 195 mustAbsResourceInstanceAddr("module.single.test.single"), 196 } 197 if diff := cmp.Diff(want, got); diff != "" { 198 t.Errorf("wrong result\n%s", diff) 199 } 200 }) 201 t.Run("module single resource count2", func(t *testing.T) { 202 got := ex.ExpandModuleResource( 203 mustModuleAddr(`single`), 204 count2ResourceAddr, 205 ) 206 want := []addrs.AbsResourceInstance{ 207 mustAbsResourceInstanceAddr(`module.single.test.count2[0]`), 208 mustAbsResourceInstanceAddr(`module.single.test.count2[1]`), 209 } 210 if diff := cmp.Diff(want, got); diff != "" { 211 t.Errorf("wrong result\n%s", diff) 212 } 213 }) 214 t.Run("module count2", func(t *testing.T) { 215 got := ex.ExpandModule(mustModuleAddr(`count2`)) 216 want := []addrs.ModuleInstance{ 217 mustModuleInstanceAddr(`module.count2[0]`), 218 mustModuleInstanceAddr(`module.count2[1]`), 219 } 220 if diff := cmp.Diff(want, got); diff != "" { 221 t.Errorf("wrong result\n%s", diff) 222 } 223 }) 224 t.Run("module count2 resource single", func(t *testing.T) { 225 got := ex.ExpandModuleResource( 226 mustModuleAddr(`count2`), 227 singleResourceAddr, 228 ) 229 want := []addrs.AbsResourceInstance{ 230 mustAbsResourceInstanceAddr(`module.count2[0].test.single`), 231 mustAbsResourceInstanceAddr(`module.count2[1].test.single`), 232 } 233 if diff := cmp.Diff(want, got); diff != "" { 234 t.Errorf("wrong result\n%s", diff) 235 } 236 }) 237 t.Run("module count2 resource count2", func(t *testing.T) { 238 got := ex.ExpandModuleResource( 239 mustModuleAddr(`count2`), 240 count2ResourceAddr, 241 ) 242 want := []addrs.AbsResourceInstance{ 243 mustAbsResourceInstanceAddr(`module.count2[0].test.count2[0]`), 244 mustAbsResourceInstanceAddr(`module.count2[0].test.count2[1]`), 245 mustAbsResourceInstanceAddr(`module.count2[1].test.count2[0]`), 246 mustAbsResourceInstanceAddr(`module.count2[1].test.count2[1]`), 247 } 248 if diff := cmp.Diff(want, got); diff != "" { 249 t.Errorf("wrong result\n%s", diff) 250 } 251 }) 252 t.Run("module count2 module count2", func(t *testing.T) { 253 got := ex.ExpandModule(mustModuleAddr(`count2.count2`)) 254 want := []addrs.ModuleInstance{ 255 mustModuleInstanceAddr(`module.count2[0].module.count2[0]`), 256 mustModuleInstanceAddr(`module.count2[0].module.count2[1]`), 257 mustModuleInstanceAddr(`module.count2[1].module.count2[0]`), 258 mustModuleInstanceAddr(`module.count2[1].module.count2[1]`), 259 } 260 if diff := cmp.Diff(want, got); diff != "" { 261 t.Errorf("wrong result\n%s", diff) 262 } 263 }) 264 t.Run("module count2 resource count2 resource count2", func(t *testing.T) { 265 got := ex.ExpandModuleResource( 266 mustModuleAddr(`count2.count2`), 267 count2ResourceAddr, 268 ) 269 want := []addrs.AbsResourceInstance{ 270 mustAbsResourceInstanceAddr(`module.count2[0].module.count2[0].test.count2[0]`), 271 mustAbsResourceInstanceAddr(`module.count2[0].module.count2[0].test.count2[1]`), 272 mustAbsResourceInstanceAddr(`module.count2[0].module.count2[1].test.count2[0]`), 273 mustAbsResourceInstanceAddr(`module.count2[0].module.count2[1].test.count2[1]`), 274 mustAbsResourceInstanceAddr(`module.count2[1].module.count2[0].test.count2[0]`), 275 mustAbsResourceInstanceAddr(`module.count2[1].module.count2[0].test.count2[1]`), 276 mustAbsResourceInstanceAddr(`module.count2[1].module.count2[1].test.count2[0]`), 277 mustAbsResourceInstanceAddr(`module.count2[1].module.count2[1].test.count2[1]`), 278 } 279 if diff := cmp.Diff(want, got); diff != "" { 280 t.Errorf("wrong result\n%s", diff) 281 } 282 }) 283 t.Run("module count2 resource count2 resource count2", func(t *testing.T) { 284 got := ex.ExpandResource( 285 count2ResourceAddr.Absolute(mustModuleInstanceAddr(`module.count2[0].module.count2[1]`)), 286 ) 287 want := []addrs.AbsResourceInstance{ 288 mustAbsResourceInstanceAddr(`module.count2[0].module.count2[1].test.count2[0]`), 289 mustAbsResourceInstanceAddr(`module.count2[0].module.count2[1].test.count2[1]`), 290 } 291 if diff := cmp.Diff(want, got); diff != "" { 292 t.Errorf("wrong result\n%s", diff) 293 } 294 }) 295 t.Run("module count0", func(t *testing.T) { 296 got := ex.ExpandModule(mustModuleAddr(`count0`)) 297 want := []addrs.ModuleInstance(nil) 298 if diff := cmp.Diff(want, got); diff != "" { 299 t.Errorf("wrong result\n%s", diff) 300 } 301 }) 302 t.Run("module count0 resource single", func(t *testing.T) { 303 got := ex.ExpandModuleResource( 304 mustModuleAddr(`count0`), 305 singleResourceAddr, 306 ) 307 // The containing module has zero instances, so therefore there 308 // are zero instances of this resource even though it doesn't have 309 // count = 0 set itself. 310 want := []addrs.AbsResourceInstance(nil) 311 if diff := cmp.Diff(want, got); diff != "" { 312 t.Errorf("wrong result\n%s", diff) 313 } 314 }) 315 t.Run("module for_each", func(t *testing.T) { 316 got := ex.ExpandModule(mustModuleAddr(`for_each`)) 317 want := []addrs.ModuleInstance{ 318 mustModuleInstanceAddr(`module.for_each["a"]`), 319 mustModuleInstanceAddr(`module.for_each["b"]`), 320 } 321 if diff := cmp.Diff(want, got); diff != "" { 322 t.Errorf("wrong result\n%s", diff) 323 } 324 }) 325 t.Run("module for_each resource single", func(t *testing.T) { 326 got := ex.ExpandModuleResource( 327 mustModuleAddr(`for_each`), 328 singleResourceAddr, 329 ) 330 want := []addrs.AbsResourceInstance{ 331 mustAbsResourceInstanceAddr(`module.for_each["a"].test.single`), 332 mustAbsResourceInstanceAddr(`module.for_each["b"].test.single`), 333 } 334 if diff := cmp.Diff(want, got); diff != "" { 335 t.Errorf("wrong result\n%s", diff) 336 } 337 }) 338 t.Run("module for_each resource count2", func(t *testing.T) { 339 got := ex.ExpandModuleResource( 340 mustModuleAddr(`for_each`), 341 count2ResourceAddr, 342 ) 343 want := []addrs.AbsResourceInstance{ 344 mustAbsResourceInstanceAddr(`module.for_each["a"].test.count2[0]`), 345 mustAbsResourceInstanceAddr(`module.for_each["a"].test.count2[1]`), 346 mustAbsResourceInstanceAddr(`module.for_each["b"].test.count2[0]`), 347 mustAbsResourceInstanceAddr(`module.for_each["b"].test.count2[1]`), 348 } 349 if diff := cmp.Diff(want, got); diff != "" { 350 t.Errorf("wrong result\n%s", diff) 351 } 352 }) 353 t.Run("module for_each resource count2", func(t *testing.T) { 354 got := ex.ExpandResource( 355 count2ResourceAddr.Absolute(mustModuleInstanceAddr(`module.for_each["a"]`)), 356 ) 357 want := []addrs.AbsResourceInstance{ 358 mustAbsResourceInstanceAddr(`module.for_each["a"].test.count2[0]`), 359 mustAbsResourceInstanceAddr(`module.for_each["a"].test.count2[1]`), 360 } 361 if diff := cmp.Diff(want, got); diff != "" { 362 t.Errorf("wrong result\n%s", diff) 363 } 364 }) 365 366 t.Run(`module.for_each["b"] repetitiondata`, func(t *testing.T) { 367 got := ex.GetModuleInstanceRepetitionData( 368 mustModuleInstanceAddr(`module.for_each["b"]`), 369 ) 370 want := RepetitionData{ 371 EachKey: cty.StringVal("b"), 372 EachValue: cty.NumberIntVal(2), 373 } 374 if diff := cmp.Diff(want, got, cmp.Comparer(valueEquals)); diff != "" { 375 t.Errorf("wrong result\n%s", diff) 376 } 377 }) 378 t.Run(`module.count2[0].module.count2[1] repetitiondata`, func(t *testing.T) { 379 got := ex.GetModuleInstanceRepetitionData( 380 mustModuleInstanceAddr(`module.count2[0].module.count2[1]`), 381 ) 382 want := RepetitionData{ 383 CountIndex: cty.NumberIntVal(1), 384 } 385 if diff := cmp.Diff(want, got, cmp.Comparer(valueEquals)); diff != "" { 386 t.Errorf("wrong result\n%s", diff) 387 } 388 }) 389 t.Run(`module.for_each["a"] repetitiondata`, func(t *testing.T) { 390 got := ex.GetModuleInstanceRepetitionData( 391 mustModuleInstanceAddr(`module.for_each["a"]`), 392 ) 393 want := RepetitionData{ 394 EachKey: cty.StringVal("a"), 395 EachValue: cty.NumberIntVal(1), 396 } 397 if diff := cmp.Diff(want, got, cmp.Comparer(valueEquals)); diff != "" { 398 t.Errorf("wrong result\n%s", diff) 399 } 400 }) 401 402 t.Run(`test.for_each["a"] repetitiondata`, func(t *testing.T) { 403 got := ex.GetResourceInstanceRepetitionData( 404 mustAbsResourceInstanceAddr(`test.for_each["a"]`), 405 ) 406 want := RepetitionData{ 407 EachKey: cty.StringVal("a"), 408 EachValue: cty.NumberIntVal(1), 409 } 410 if diff := cmp.Diff(want, got, cmp.Comparer(valueEquals)); diff != "" { 411 t.Errorf("wrong result\n%s", diff) 412 } 413 }) 414 t.Run(`module.for_each["a"].test.single repetitiondata`, func(t *testing.T) { 415 got := ex.GetResourceInstanceRepetitionData( 416 mustAbsResourceInstanceAddr(`module.for_each["a"].test.single`), 417 ) 418 want := RepetitionData{} 419 if diff := cmp.Diff(want, got, cmp.Comparer(valueEquals)); diff != "" { 420 t.Errorf("wrong result\n%s", diff) 421 } 422 }) 423 t.Run(`module.for_each["a"].test.count2[1] repetitiondata`, func(t *testing.T) { 424 got := ex.GetResourceInstanceRepetitionData( 425 mustAbsResourceInstanceAddr(`module.for_each["a"].test.count2[1]`), 426 ) 427 want := RepetitionData{ 428 CountIndex: cty.NumberIntVal(1), 429 } 430 if diff := cmp.Diff(want, got, cmp.Comparer(valueEquals)); diff != "" { 431 t.Errorf("wrong result\n%s", diff) 432 } 433 }) 434 } 435 436 func mustAbsResourceInstanceAddr(str string) addrs.AbsResourceInstance { 437 addr, diags := addrs.ParseAbsResourceInstanceStr(str) 438 if diags.HasErrors() { 439 panic(fmt.Sprintf("invalid absolute resource instance address: %s", diags.Err())) 440 } 441 return addr 442 } 443 444 func mustModuleAddr(str string) addrs.Module { 445 if len(str) == 0 { 446 return addrs.RootModule 447 } 448 // We don't have a real parser for these because they don't appear in the 449 // language anywhere, but this interpretation mimics the format we 450 // produce from the String method on addrs.Module. 451 parts := strings.Split(str, ".") 452 return addrs.Module(parts) 453 } 454 455 func mustModuleInstanceAddr(str string) addrs.ModuleInstance { 456 if len(str) == 0 { 457 return addrs.RootModuleInstance 458 } 459 addr, diags := addrs.ParseModuleInstanceStr(str) 460 if diags.HasErrors() { 461 panic(fmt.Sprintf("invalid module instance address: %s", diags.Err())) 462 } 463 return addr 464 } 465 466 func valueEquals(a, b cty.Value) bool { 467 if a == cty.NilVal || b == cty.NilVal { 468 return a == b 469 } 470 return a.RawEquals(b) 471 }