kubeform.dev/terraform-backend-sdk@v0.0.0-20220310143633-45f07fe731c5/states/state_test.go (about) 1 package states 2 3 import ( 4 "fmt" 5 "reflect" 6 "testing" 7 8 "github.com/go-test/deep" 9 "github.com/zclconf/go-cty/cty" 10 11 "kubeform.dev/terraform-backend-sdk/addrs" 12 "kubeform.dev/terraform-backend-sdk/lang/marks" 13 ) 14 15 func TestState(t *testing.T) { 16 // This basic tests exercises the main mutation methods to construct 17 // a state. It is not fully comprehensive, so other tests should visit 18 // more esoteric codepaths. 19 20 state := NewState() 21 22 rootModule := state.RootModule() 23 if rootModule == nil { 24 t.Errorf("root module is nil; want valid object") 25 } 26 27 rootModule.SetLocalValue("foo", cty.StringVal("foo value")) 28 rootModule.SetOutputValue("bar", cty.StringVal("bar value"), false) 29 rootModule.SetOutputValue("secret", cty.StringVal("secret value"), true) 30 rootModule.SetResourceInstanceCurrent( 31 addrs.Resource{ 32 Mode: addrs.ManagedResourceMode, 33 Type: "test_thing", 34 Name: "baz", 35 }.Instance(addrs.IntKey(0)), 36 &ResourceInstanceObjectSrc{ 37 Status: ObjectReady, 38 SchemaVersion: 1, 39 AttrsJSON: []byte(`{"woozles":"confuzles"}`), 40 }, 41 addrs.AbsProviderConfig{ 42 Provider: addrs.NewDefaultProvider("test"), 43 Module: addrs.RootModule, 44 }, 45 ) 46 47 childModule := state.EnsureModule(addrs.RootModuleInstance.Child("child", addrs.NoKey)) 48 childModule.SetOutputValue("pizza", cty.StringVal("hawaiian"), false) 49 multiModA := state.EnsureModule(addrs.RootModuleInstance.Child("multi", addrs.StringKey("a"))) 50 multiModA.SetOutputValue("pizza", cty.StringVal("cheese"), false) 51 multiModB := state.EnsureModule(addrs.RootModuleInstance.Child("multi", addrs.StringKey("b"))) 52 multiModB.SetOutputValue("pizza", cty.StringVal("sausage"), false) 53 54 want := &State{ 55 Modules: map[string]*Module{ 56 "": { 57 Addr: addrs.RootModuleInstance, 58 LocalValues: map[string]cty.Value{ 59 "foo": cty.StringVal("foo value"), 60 }, 61 OutputValues: map[string]*OutputValue{ 62 "bar": { 63 Addr: addrs.AbsOutputValue{ 64 OutputValue: addrs.OutputValue{ 65 Name: "bar", 66 }, 67 }, 68 Value: cty.StringVal("bar value"), 69 Sensitive: false, 70 }, 71 "secret": { 72 Addr: addrs.AbsOutputValue{ 73 OutputValue: addrs.OutputValue{ 74 Name: "secret", 75 }, 76 }, 77 Value: cty.StringVal("secret value"), 78 Sensitive: true, 79 }, 80 }, 81 Resources: map[string]*Resource{ 82 "test_thing.baz": { 83 Addr: addrs.Resource{ 84 Mode: addrs.ManagedResourceMode, 85 Type: "test_thing", 86 Name: "baz", 87 }.Absolute(addrs.RootModuleInstance), 88 89 Instances: map[addrs.InstanceKey]*ResourceInstance{ 90 addrs.IntKey(0): { 91 Current: &ResourceInstanceObjectSrc{ 92 SchemaVersion: 1, 93 Status: ObjectReady, 94 AttrsJSON: []byte(`{"woozles":"confuzles"}`), 95 }, 96 Deposed: map[DeposedKey]*ResourceInstanceObjectSrc{}, 97 }, 98 }, 99 ProviderConfig: addrs.AbsProviderConfig{ 100 Provider: addrs.NewDefaultProvider("test"), 101 Module: addrs.RootModule, 102 }, 103 }, 104 }, 105 }, 106 "module.child": { 107 Addr: addrs.RootModuleInstance.Child("child", addrs.NoKey), 108 LocalValues: map[string]cty.Value{}, 109 OutputValues: map[string]*OutputValue{ 110 "pizza": { 111 Addr: addrs.AbsOutputValue{ 112 Module: addrs.RootModuleInstance.Child("child", addrs.NoKey), 113 OutputValue: addrs.OutputValue{ 114 Name: "pizza", 115 }, 116 }, 117 Value: cty.StringVal("hawaiian"), 118 Sensitive: false, 119 }, 120 }, 121 Resources: map[string]*Resource{}, 122 }, 123 `module.multi["a"]`: { 124 Addr: addrs.RootModuleInstance.Child("multi", addrs.StringKey("a")), 125 LocalValues: map[string]cty.Value{}, 126 OutputValues: map[string]*OutputValue{ 127 "pizza": { 128 Addr: addrs.AbsOutputValue{ 129 Module: addrs.RootModuleInstance.Child("multi", addrs.StringKey("a")), 130 OutputValue: addrs.OutputValue{ 131 Name: "pizza", 132 }, 133 }, 134 Value: cty.StringVal("cheese"), 135 Sensitive: false, 136 }, 137 }, 138 Resources: map[string]*Resource{}, 139 }, 140 `module.multi["b"]`: { 141 Addr: addrs.RootModuleInstance.Child("multi", addrs.StringKey("b")), 142 LocalValues: map[string]cty.Value{}, 143 OutputValues: map[string]*OutputValue{ 144 "pizza": { 145 Addr: addrs.AbsOutputValue{ 146 Module: addrs.RootModuleInstance.Child("multi", addrs.StringKey("b")), 147 OutputValue: addrs.OutputValue{ 148 Name: "pizza", 149 }, 150 }, 151 Value: cty.StringVal("sausage"), 152 Sensitive: false, 153 }, 154 }, 155 Resources: map[string]*Resource{}, 156 }, 157 }, 158 } 159 160 { 161 // Our structure goes deep, so we need to temporarily override the 162 // deep package settings to ensure that we visit the full structure. 163 oldDeepDepth := deep.MaxDepth 164 oldDeepCompareUnexp := deep.CompareUnexportedFields 165 deep.MaxDepth = 50 166 deep.CompareUnexportedFields = true 167 defer func() { 168 deep.MaxDepth = oldDeepDepth 169 deep.CompareUnexportedFields = oldDeepCompareUnexp 170 }() 171 } 172 173 for _, problem := range deep.Equal(state, want) { 174 t.Error(problem) 175 } 176 177 expectedOutputs := map[string]string{ 178 `module.multi["a"].output.pizza`: "cheese", 179 `module.multi["b"].output.pizza`: "sausage", 180 } 181 182 for _, o := range state.ModuleOutputs(addrs.RootModuleInstance, addrs.ModuleCall{Name: "multi"}) { 183 addr := o.Addr.String() 184 expected := expectedOutputs[addr] 185 delete(expectedOutputs, addr) 186 187 if expected != o.Value.AsString() { 188 t.Fatalf("expected %q:%q, got %q", addr, expected, o.Value.AsString()) 189 } 190 } 191 192 for addr, o := range expectedOutputs { 193 t.Fatalf("missing output %q:%q", addr, o) 194 } 195 } 196 197 func TestStateDeepCopyObject(t *testing.T) { 198 obj := &ResourceInstanceObject{ 199 Value: cty.ObjectVal(map[string]cty.Value{ 200 "id": cty.StringVal("id"), 201 }), 202 Private: []byte("private"), 203 Status: ObjectReady, 204 Dependencies: []addrs.ConfigResource{ 205 { 206 Module: addrs.RootModule, 207 Resource: addrs.Resource{ 208 Mode: addrs.ManagedResourceMode, 209 Type: "test_instance", 210 Name: "bar", 211 }, 212 }, 213 }, 214 CreateBeforeDestroy: true, 215 } 216 217 objCopy := obj.DeepCopy() 218 if !reflect.DeepEqual(obj, objCopy) { 219 t.Fatalf("not equal\n%#v\n%#v", obj, objCopy) 220 } 221 } 222 223 func TestStateDeepCopy(t *testing.T) { 224 state := NewState() 225 226 rootModule := state.RootModule() 227 if rootModule == nil { 228 t.Errorf("root module is nil; want valid object") 229 } 230 231 rootModule.SetLocalValue("foo", cty.StringVal("foo value")) 232 rootModule.SetOutputValue("bar", cty.StringVal("bar value"), false) 233 rootModule.SetOutputValue("secret", cty.StringVal("secret value"), true) 234 rootModule.SetResourceInstanceCurrent( 235 addrs.Resource{ 236 Mode: addrs.ManagedResourceMode, 237 Type: "test_thing", 238 Name: "baz", 239 }.Instance(addrs.IntKey(0)), 240 &ResourceInstanceObjectSrc{ 241 Status: ObjectReady, 242 SchemaVersion: 1, 243 AttrsJSON: []byte(`{"woozles":"confuzles"}`), 244 Private: []byte("private data"), 245 Dependencies: []addrs.ConfigResource{}, 246 CreateBeforeDestroy: true, 247 }, 248 addrs.AbsProviderConfig{ 249 Provider: addrs.NewDefaultProvider("test"), 250 Module: addrs.RootModule, 251 }, 252 ) 253 rootModule.SetResourceInstanceCurrent( 254 addrs.Resource{ 255 Mode: addrs.ManagedResourceMode, 256 Type: "test_thing", 257 Name: "bar", 258 }.Instance(addrs.IntKey(0)), 259 &ResourceInstanceObjectSrc{ 260 Status: ObjectReady, 261 SchemaVersion: 1, 262 AttrsJSON: []byte(`{"woozles":"confuzles"}`), 263 // Sensitive path at "woozles" 264 AttrSensitivePaths: []cty.PathValueMarks{ 265 { 266 Path: cty.Path{cty.GetAttrStep{Name: "woozles"}}, 267 Marks: cty.NewValueMarks(marks.Sensitive), 268 }, 269 }, 270 Private: []byte("private data"), 271 Dependencies: []addrs.ConfigResource{ 272 { 273 Module: addrs.RootModule, 274 Resource: addrs.Resource{ 275 Mode: addrs.ManagedResourceMode, 276 Type: "test_thing", 277 Name: "baz", 278 }, 279 }, 280 }, 281 }, 282 addrs.AbsProviderConfig{ 283 Provider: addrs.NewDefaultProvider("test"), 284 Module: addrs.RootModule, 285 }, 286 ) 287 288 childModule := state.EnsureModule(addrs.RootModuleInstance.Child("child", addrs.NoKey)) 289 childModule.SetOutputValue("pizza", cty.StringVal("hawaiian"), false) 290 291 stateCopy := state.DeepCopy() 292 if !state.Equal(stateCopy) { 293 t.Fatalf("\nexpected:\n%q\ngot:\n%q\n", state, stateCopy) 294 } 295 } 296 297 func TestState_MoveAbsResource(t *testing.T) { 298 // Set up a starter state for the embedded tests, which should start from a copy of this state. 299 state := NewState() 300 rootModule := state.RootModule() 301 rootModule.SetResourceInstanceCurrent( 302 addrs.Resource{ 303 Mode: addrs.ManagedResourceMode, 304 Type: "test_thing", 305 Name: "foo", 306 }.Instance(addrs.IntKey(0)), 307 &ResourceInstanceObjectSrc{ 308 Status: ObjectReady, 309 SchemaVersion: 1, 310 AttrsJSON: []byte(`{"woozles":"confuzles"}`), 311 }, 312 addrs.AbsProviderConfig{ 313 Provider: addrs.NewDefaultProvider("test"), 314 Module: addrs.RootModule, 315 }, 316 ) 317 src := addrs.Resource{Mode: addrs.ManagedResourceMode, Type: "test_thing", Name: "foo"}.Absolute(addrs.RootModuleInstance) 318 319 t.Run("basic move", func(t *testing.T) { 320 s := state.DeepCopy() 321 dst := addrs.Resource{Mode: addrs.ManagedResourceMode, Type: "test_thing", Name: "bar"}.Absolute(addrs.RootModuleInstance) 322 323 s.MoveAbsResource(src, dst) 324 325 if s.Empty() { 326 t.Fatal("unexpected empty state") 327 } 328 329 if len(s.RootModule().Resources) != 1 { 330 t.Fatalf("wrong number of resources in state; expected 1, found %d", len(state.RootModule().Resources)) 331 } 332 333 got := s.Resource(dst) 334 if got.Addr.Resource != dst.Resource { 335 t.Fatalf("dst resource not in state") 336 } 337 }) 338 339 t.Run("move to new module", func(t *testing.T) { 340 s := state.DeepCopy() 341 dstModule := addrs.RootModuleInstance.Child("kinder", addrs.StringKey("one")) 342 dst := addrs.Resource{Mode: addrs.ManagedResourceMode, Type: "test_thing", Name: "bar"}.Absolute(dstModule) 343 344 s.MoveAbsResource(src, dst) 345 346 if s.Empty() { 347 t.Fatal("unexpected empty state") 348 } 349 350 if s.Module(dstModule) == nil { 351 t.Fatalf("child module %s not in state", dstModule.String()) 352 } 353 354 if len(s.Module(dstModule).Resources) != 1 { 355 t.Fatalf("wrong number of resources in state; expected 1, found %d", len(s.Module(dstModule).Resources)) 356 } 357 358 got := s.Resource(dst) 359 if got.Addr.Resource != dst.Resource { 360 t.Fatalf("dst resource not in state") 361 } 362 }) 363 364 t.Run("from a child module to root", func(t *testing.T) { 365 s := state.DeepCopy() 366 srcModule := addrs.RootModuleInstance.Child("kinder", addrs.NoKey) 367 cm := s.EnsureModule(srcModule) 368 cm.SetResourceInstanceCurrent( 369 addrs.Resource{ 370 Mode: addrs.ManagedResourceMode, 371 Type: "test_thing", 372 Name: "child", 373 }.Instance(addrs.IntKey(0)), // Moving the AbsResouce moves all instances 374 &ResourceInstanceObjectSrc{ 375 Status: ObjectReady, 376 SchemaVersion: 1, 377 AttrsJSON: []byte(`{"woozles":"confuzles"}`), 378 }, 379 addrs.AbsProviderConfig{ 380 Provider: addrs.NewDefaultProvider("test"), 381 Module: addrs.RootModule, 382 }, 383 ) 384 cm.SetResourceInstanceCurrent( 385 addrs.Resource{ 386 Mode: addrs.ManagedResourceMode, 387 Type: "test_thing", 388 Name: "child", 389 }.Instance(addrs.IntKey(1)), // Moving the AbsResouce moves all instances 390 &ResourceInstanceObjectSrc{ 391 Status: ObjectReady, 392 SchemaVersion: 1, 393 AttrsJSON: []byte(`{"woozles":"confuzles"}`), 394 }, 395 addrs.AbsProviderConfig{ 396 Provider: addrs.NewDefaultProvider("test"), 397 Module: addrs.RootModule, 398 }, 399 ) 400 401 src := addrs.Resource{Mode: addrs.ManagedResourceMode, Type: "test_thing", Name: "child"}.Absolute(srcModule) 402 dst := addrs.Resource{Mode: addrs.ManagedResourceMode, Type: "test_thing", Name: "child"}.Absolute(addrs.RootModuleInstance) 403 s.MoveAbsResource(src, dst) 404 405 if s.Empty() { 406 t.Fatal("unexpected empty state") 407 } 408 409 // The child module should have been removed after removing its only resource 410 if s.Module(srcModule) != nil { 411 t.Fatalf("child module %s was not removed from state after mv", srcModule.String()) 412 } 413 414 if len(s.RootModule().Resources) != 2 { 415 t.Fatalf("wrong number of resources in state; expected 2, found %d", len(s.RootModule().Resources)) 416 } 417 418 if len(s.Resource(dst).Instances) != 2 { 419 t.Fatalf("wrong number of resource instances for dst, got %d expected 2", len(s.Resource(dst).Instances)) 420 } 421 422 got := s.Resource(dst) 423 if got.Addr.Resource != dst.Resource { 424 t.Fatalf("dst resource not in state") 425 } 426 }) 427 428 t.Run("module to new module", func(t *testing.T) { 429 s := NewState() 430 srcModule := addrs.RootModuleInstance.Child("kinder", addrs.StringKey("exists")) 431 dstModule := addrs.RootModuleInstance.Child("kinder", addrs.StringKey("new")) 432 cm := s.EnsureModule(srcModule) 433 cm.SetResourceInstanceCurrent( 434 addrs.Resource{ 435 Mode: addrs.ManagedResourceMode, 436 Type: "test_thing", 437 Name: "child", 438 }.Instance(addrs.NoKey), 439 &ResourceInstanceObjectSrc{ 440 Status: ObjectReady, 441 SchemaVersion: 1, 442 AttrsJSON: []byte(`{"woozles":"confuzles"}`), 443 }, 444 addrs.AbsProviderConfig{ 445 Provider: addrs.NewDefaultProvider("test"), 446 Module: addrs.RootModule, 447 }, 448 ) 449 450 src := addrs.Resource{Mode: addrs.ManagedResourceMode, Type: "test_thing", Name: "child"}.Absolute(srcModule) 451 dst := addrs.Resource{Mode: addrs.ManagedResourceMode, Type: "test_thing", Name: "child"}.Absolute(dstModule) 452 s.MoveAbsResource(src, dst) 453 454 if s.Empty() { 455 t.Fatal("unexpected empty state") 456 } 457 458 // The child module should have been removed after removing its only resource 459 if s.Module(srcModule) != nil { 460 t.Fatalf("child module %s was not removed from state after mv", srcModule.String()) 461 } 462 463 gotMod := s.Module(dstModule) 464 if len(gotMod.Resources) != 1 { 465 t.Fatalf("wrong number of resources in state; expected 1, found %d", len(gotMod.Resources)) 466 } 467 468 got := s.Resource(dst) 469 if got.Addr.Resource != dst.Resource { 470 t.Fatalf("dst resource not in state") 471 } 472 }) 473 474 t.Run("module to new module", func(t *testing.T) { 475 s := NewState() 476 srcModule := addrs.RootModuleInstance.Child("kinder", addrs.StringKey("exists")) 477 dstModule := addrs.RootModuleInstance.Child("kinder", addrs.StringKey("new")) 478 cm := s.EnsureModule(srcModule) 479 cm.SetResourceInstanceCurrent( 480 addrs.Resource{ 481 Mode: addrs.ManagedResourceMode, 482 Type: "test_thing", 483 Name: "child", 484 }.Instance(addrs.NoKey), 485 &ResourceInstanceObjectSrc{ 486 Status: ObjectReady, 487 SchemaVersion: 1, 488 AttrsJSON: []byte(`{"woozles":"confuzles"}`), 489 }, 490 addrs.AbsProviderConfig{ 491 Provider: addrs.NewDefaultProvider("test"), 492 Module: addrs.RootModule, 493 }, 494 ) 495 496 src := addrs.Resource{Mode: addrs.ManagedResourceMode, Type: "test_thing", Name: "child"}.Absolute(srcModule) 497 dst := addrs.Resource{Mode: addrs.ManagedResourceMode, Type: "test_thing", Name: "child"}.Absolute(dstModule) 498 s.MoveAbsResource(src, dst) 499 500 if s.Empty() { 501 t.Fatal("unexpected empty state") 502 } 503 504 // The child module should have been removed after removing its only resource 505 if s.Module(srcModule) != nil { 506 t.Fatalf("child module %s was not removed from state after mv", srcModule.String()) 507 } 508 509 gotMod := s.Module(dstModule) 510 if len(gotMod.Resources) != 1 { 511 t.Fatalf("wrong number of resources in state; expected 1, found %d", len(gotMod.Resources)) 512 } 513 514 got := s.Resource(dst) 515 if got.Addr.Resource != dst.Resource { 516 t.Fatalf("dst resource not in state") 517 } 518 }) 519 } 520 521 func TestState_MaybeMoveAbsResource(t *testing.T) { 522 state := NewState() 523 rootModule := state.RootModule() 524 rootModule.SetResourceInstanceCurrent( 525 addrs.Resource{ 526 Mode: addrs.ManagedResourceMode, 527 Type: "test_thing", 528 Name: "foo", 529 }.Instance(addrs.IntKey(0)), 530 &ResourceInstanceObjectSrc{ 531 Status: ObjectReady, 532 SchemaVersion: 1, 533 AttrsJSON: []byte(`{"woozles":"confuzles"}`), 534 }, 535 addrs.AbsProviderConfig{ 536 Provider: addrs.NewDefaultProvider("test"), 537 Module: addrs.RootModule, 538 }, 539 ) 540 541 src := addrs.Resource{Mode: addrs.ManagedResourceMode, Type: "test_thing", Name: "foo"}.Absolute(addrs.RootModuleInstance) 542 dst := addrs.Resource{Mode: addrs.ManagedResourceMode, Type: "test_thing", Name: "bar"}.Absolute(addrs.RootModuleInstance) 543 544 // First move, success 545 t.Run("first move", func(t *testing.T) { 546 moved := state.MaybeMoveAbsResource(src, dst) 547 if !moved { 548 t.Fatal("wrong result") 549 } 550 }) 551 552 // Trying to move a resource that doesn't exist in state to a resource which does exist should be a noop. 553 t.Run("noop", func(t *testing.T) { 554 moved := state.MaybeMoveAbsResource(src, dst) 555 if moved { 556 t.Fatal("wrong result") 557 } 558 }) 559 } 560 561 func TestState_MoveAbsResourceInstance(t *testing.T) { 562 state := NewState() 563 rootModule := state.RootModule() 564 rootModule.SetResourceInstanceCurrent( 565 addrs.Resource{ 566 Mode: addrs.ManagedResourceMode, 567 Type: "test_thing", 568 Name: "foo", 569 }.Instance(addrs.NoKey), 570 &ResourceInstanceObjectSrc{ 571 Status: ObjectReady, 572 SchemaVersion: 1, 573 AttrsJSON: []byte(`{"woozles":"confuzles"}`), 574 }, 575 addrs.AbsProviderConfig{ 576 Provider: addrs.NewDefaultProvider("test"), 577 Module: addrs.RootModule, 578 }, 579 ) 580 // src resource from the state above 581 src := addrs.Resource{Mode: addrs.ManagedResourceMode, Type: "test_thing", Name: "foo"}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance) 582 583 t.Run("resource to resource instance", func(t *testing.T) { 584 s := state.DeepCopy() 585 // For a little extra fun, move a resource to a resource instance: test_thing.foo to test_thing.foo[1] 586 dst := addrs.Resource{Mode: addrs.ManagedResourceMode, Type: "test_thing", Name: "foo"}.Instance(addrs.IntKey(1)).Absolute(addrs.RootModuleInstance) 587 588 s.MoveAbsResourceInstance(src, dst) 589 590 if s.Empty() { 591 t.Fatal("unexpected empty state") 592 } 593 594 if len(s.RootModule().Resources) != 1 { 595 t.Fatalf("wrong number of resources in state; expected 1, found %d", len(state.RootModule().Resources)) 596 } 597 598 got := s.ResourceInstance(dst) 599 if got == nil { 600 t.Fatalf("dst resource not in state") 601 } 602 }) 603 604 t.Run("move to new module", func(t *testing.T) { 605 s := state.DeepCopy() 606 // test_thing.foo to module.kinder.test_thing.foo["baz"] 607 dstModule := addrs.RootModuleInstance.Child("kinder", addrs.NoKey) 608 dst := addrs.Resource{Mode: addrs.ManagedResourceMode, Type: "test_thing", Name: "foo"}.Instance(addrs.IntKey(1)).Absolute(dstModule) 609 610 s.MoveAbsResourceInstance(src, dst) 611 612 if s.Empty() { 613 t.Fatal("unexpected empty state") 614 } 615 616 if s.Module(dstModule) == nil { 617 t.Fatalf("child module %s not in state", dstModule.String()) 618 } 619 620 if len(s.Module(dstModule).Resources) != 1 { 621 t.Fatalf("wrong number of resources in state; expected 1, found %d", len(s.Module(dstModule).Resources)) 622 } 623 624 got := s.ResourceInstance(dst) 625 if got == nil { 626 t.Fatalf("dst resource not in state") 627 } 628 }) 629 } 630 631 func TestState_MaybeMoveAbsResourceInstance(t *testing.T) { 632 state := NewState() 633 rootModule := state.RootModule() 634 rootModule.SetResourceInstanceCurrent( 635 addrs.Resource{ 636 Mode: addrs.ManagedResourceMode, 637 Type: "test_thing", 638 Name: "foo", 639 }.Instance(addrs.NoKey), 640 &ResourceInstanceObjectSrc{ 641 Status: ObjectReady, 642 SchemaVersion: 1, 643 AttrsJSON: []byte(`{"woozles":"confuzles"}`), 644 }, 645 addrs.AbsProviderConfig{ 646 Provider: addrs.NewDefaultProvider("test"), 647 Module: addrs.RootModule, 648 }, 649 ) 650 651 // For a little extra fun, let's go from a resource to a resource instance: test_thing.foo to test_thing.bar[1] 652 src := addrs.Resource{Mode: addrs.ManagedResourceMode, Type: "test_thing", Name: "foo"}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance) 653 dst := addrs.Resource{Mode: addrs.ManagedResourceMode, Type: "test_thing", Name: "foo"}.Instance(addrs.IntKey(1)).Absolute(addrs.RootModuleInstance) 654 655 // First move, success 656 t.Run("first move", func(t *testing.T) { 657 moved := state.MaybeMoveAbsResourceInstance(src, dst) 658 if !moved { 659 t.Fatal("wrong result") 660 } 661 got := state.ResourceInstance(dst) 662 if got == nil { 663 t.Fatal("destination resource instance not in state") 664 } 665 }) 666 667 // Moving a resource instance that doesn't exist in state to a resource which does exist should be a noop. 668 t.Run("noop", func(t *testing.T) { 669 moved := state.MaybeMoveAbsResourceInstance(src, dst) 670 if moved { 671 t.Fatal("wrong result") 672 } 673 }) 674 } 675 676 func TestState_MoveModuleInstance(t *testing.T) { 677 state := NewState() 678 srcModule := addrs.RootModuleInstance.Child("kinder", addrs.NoKey) 679 m := state.EnsureModule(srcModule) 680 m.SetResourceInstanceCurrent( 681 addrs.Resource{ 682 Mode: addrs.ManagedResourceMode, 683 Type: "test_thing", 684 Name: "foo", 685 }.Instance(addrs.NoKey), 686 &ResourceInstanceObjectSrc{ 687 Status: ObjectReady, 688 SchemaVersion: 1, 689 AttrsJSON: []byte(`{"woozles":"confuzles"}`), 690 }, 691 addrs.AbsProviderConfig{ 692 Provider: addrs.NewDefaultProvider("test"), 693 Module: addrs.RootModule, 694 }, 695 ) 696 697 dstModule := addrs.RootModuleInstance.Child("child", addrs.IntKey(3)) 698 state.MoveModuleInstance(srcModule, dstModule) 699 700 // srcModule should have been removed, dstModule should exist and have one resource 701 if len(state.Modules) != 2 { // kinder[3] and root 702 t.Fatalf("wrong number of modules in state. Expected 2, got %d", len(state.Modules)) 703 } 704 705 got := state.Module(dstModule) 706 if got == nil { 707 t.Fatal("dstModule not found") 708 } 709 710 gone := state.Module(srcModule) 711 if gone != nil { 712 t.Fatal("srcModule not removed from state") 713 } 714 715 r := got.Resource(mustAbsResourceAddr("test_thing.foo").Resource) 716 if r.Addr.Module.String() != dstModule.String() { 717 fmt.Println(r.Addr.Module.String()) 718 t.Fatal("resource address was not updated") 719 } 720 721 } 722 723 func TestState_MaybeMoveModuleInstance(t *testing.T) { 724 state := NewState() 725 src := addrs.RootModuleInstance.Child("child", addrs.StringKey("a")) 726 cm := state.EnsureModule(src) 727 cm.SetResourceInstanceCurrent( 728 addrs.Resource{ 729 Mode: addrs.ManagedResourceMode, 730 Type: "test_thing", 731 Name: "foo", 732 }.Instance(addrs.NoKey), 733 &ResourceInstanceObjectSrc{ 734 Status: ObjectReady, 735 SchemaVersion: 1, 736 AttrsJSON: []byte(`{"woozles":"confuzles"}`), 737 }, 738 addrs.AbsProviderConfig{ 739 Provider: addrs.NewDefaultProvider("test"), 740 Module: addrs.RootModule, 741 }, 742 ) 743 744 dst := addrs.RootModuleInstance.Child("kinder", addrs.StringKey("b")) 745 746 // First move, success 747 t.Run("first move", func(t *testing.T) { 748 moved := state.MaybeMoveModuleInstance(src, dst) 749 if !moved { 750 t.Fatal("wrong result") 751 } 752 }) 753 754 // Second move, should be a noop 755 t.Run("noop", func(t *testing.T) { 756 moved := state.MaybeMoveModuleInstance(src, dst) 757 if moved { 758 t.Fatal("wrong result") 759 } 760 }) 761 } 762 763 func TestState_MoveModule(t *testing.T) { 764 // For this test, add two module instances (kinder and kinder["a"]). 765 // MoveModule(kinder) should move both instances. 766 state := NewState() // starter state, should be copied by the subtests. 767 srcModule := addrs.RootModule.Child("kinder") 768 m := state.EnsureModule(srcModule.UnkeyedInstanceShim()) 769 m.SetResourceInstanceCurrent( 770 addrs.Resource{ 771 Mode: addrs.ManagedResourceMode, 772 Type: "test_thing", 773 Name: "foo", 774 }.Instance(addrs.NoKey), 775 &ResourceInstanceObjectSrc{ 776 Status: ObjectReady, 777 SchemaVersion: 1, 778 AttrsJSON: []byte(`{"woozles":"confuzles"}`), 779 }, 780 addrs.AbsProviderConfig{ 781 Provider: addrs.NewDefaultProvider("test"), 782 Module: addrs.RootModule, 783 }, 784 ) 785 786 moduleInstance := addrs.RootModuleInstance.Child("kinder", addrs.StringKey("a")) 787 mi := state.EnsureModule(moduleInstance) 788 mi.SetResourceInstanceCurrent( 789 addrs.Resource{ 790 Mode: addrs.ManagedResourceMode, 791 Type: "test_thing", 792 Name: "foo", 793 }.Instance(addrs.NoKey), 794 &ResourceInstanceObjectSrc{ 795 Status: ObjectReady, 796 SchemaVersion: 1, 797 AttrsJSON: []byte(`{"woozles":"confuzles"}`), 798 }, 799 addrs.AbsProviderConfig{ 800 Provider: addrs.NewDefaultProvider("test"), 801 Module: addrs.RootModule, 802 }, 803 ) 804 805 _, mc := srcModule.Call() 806 src := mc.Absolute(addrs.RootModuleInstance.Child("kinder", addrs.NoKey)) 807 808 t.Run("basic", func(t *testing.T) { 809 s := state.DeepCopy() 810 _, dstMC := addrs.RootModule.Child("child").Call() 811 dst := dstMC.Absolute(addrs.RootModuleInstance.Child("child", addrs.NoKey)) 812 s.MoveModule(src, dst) 813 814 // srcModule should have been removed, dstModule should exist and have one resource 815 if len(s.Modules) != 3 { // child, child["a"] and root 816 t.Fatalf("wrong number of modules in state. Expected 3, got %d", len(s.Modules)) 817 } 818 819 got := s.Module(dst.Module) 820 if got == nil { 821 t.Fatal("dstModule not found") 822 } 823 824 got = s.Module(addrs.RootModuleInstance.Child("child", addrs.StringKey("a"))) 825 if got == nil { 826 t.Fatal("dstModule instance \"a\" not found") 827 } 828 829 gone := s.Module(srcModule.UnkeyedInstanceShim()) 830 if gone != nil { 831 t.Fatal("srcModule not removed from state") 832 } 833 }) 834 835 t.Run("nested modules", func(t *testing.T) { 836 s := state.DeepCopy() 837 838 // add a child module to module.kinder 839 mi := mustParseModuleInstanceStr(`module.kinder.module.grand[1]`) 840 m := s.EnsureModule(mi) 841 m.SetResourceInstanceCurrent( 842 addrs.Resource{ 843 Mode: addrs.ManagedResourceMode, 844 Type: "test_thing", 845 Name: "foo", 846 }.Instance(addrs.NoKey), 847 &ResourceInstanceObjectSrc{ 848 Status: ObjectReady, 849 SchemaVersion: 1, 850 AttrsJSON: []byte(`{"woozles":"confuzles"}`), 851 }, 852 addrs.AbsProviderConfig{ 853 Provider: addrs.NewDefaultProvider("test"), 854 Module: addrs.RootModule, 855 }, 856 ) 857 858 _, dstMC := addrs.RootModule.Child("child").Call() 859 dst := dstMC.Absolute(addrs.RootModuleInstance.Child("child", addrs.NoKey)) 860 s.MoveModule(src, dst) 861 862 moved := s.Module(addrs.RootModuleInstance.Child("child", addrs.StringKey("a"))) 863 if moved == nil { 864 t.Fatal("dstModule not found") 865 } 866 867 // The nested module's relative address should also have been updated 868 nested := s.Module(mustParseModuleInstanceStr(`module.child.module.grand[1]`)) 869 if nested == nil { 870 t.Fatal("nested child module of src wasn't moved") 871 } 872 }) 873 } 874 875 func mustParseModuleInstanceStr(str string) addrs.ModuleInstance { 876 addr, diags := addrs.ParseModuleInstanceStr(str) 877 if diags.HasErrors() { 878 panic(diags.Err()) 879 } 880 return addr 881 } 882 883 func mustAbsResourceAddr(s string) addrs.AbsResource { 884 addr, diags := addrs.ParseAbsResourceStr(s) 885 if diags.HasErrors() { 886 panic(diags.Err()) 887 } 888 return addr 889 }