github.com/opentofu/opentofu@v1.7.1/internal/command/jsonstate/state_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 jsonstate 7 8 import ( 9 "encoding/json" 10 "reflect" 11 "testing" 12 13 "github.com/google/go-cmp/cmp" 14 "github.com/zclconf/go-cty/cty" 15 16 "github.com/opentofu/opentofu/internal/addrs" 17 "github.com/opentofu/opentofu/internal/configs/configschema" 18 "github.com/opentofu/opentofu/internal/lang/marks" 19 "github.com/opentofu/opentofu/internal/providers" 20 "github.com/opentofu/opentofu/internal/states" 21 "github.com/opentofu/opentofu/internal/tofu" 22 ) 23 24 func TestMarshalOutputs(t *testing.T) { 25 tests := []struct { 26 Outputs map[string]*states.OutputValue 27 Want map[string]Output 28 Err bool 29 }{ 30 { 31 nil, 32 nil, 33 false, 34 }, 35 { 36 map[string]*states.OutputValue{ 37 "test": { 38 Sensitive: true, 39 Value: cty.StringVal("sekret"), 40 }, 41 }, 42 map[string]Output{ 43 "test": { 44 Sensitive: true, 45 Value: json.RawMessage(`"sekret"`), 46 Type: json.RawMessage(`"string"`), 47 }, 48 }, 49 false, 50 }, 51 { 52 map[string]*states.OutputValue{ 53 "test": { 54 Sensitive: false, 55 Value: cty.StringVal("not_so_sekret"), 56 }, 57 }, 58 map[string]Output{ 59 "test": { 60 Sensitive: false, 61 Value: json.RawMessage(`"not_so_sekret"`), 62 Type: json.RawMessage(`"string"`), 63 }, 64 }, 65 false, 66 }, 67 { 68 map[string]*states.OutputValue{ 69 "mapstring": { 70 Sensitive: false, 71 Value: cty.MapVal(map[string]cty.Value{ 72 "beep": cty.StringVal("boop"), 73 }), 74 }, 75 "setnumber": { 76 Sensitive: false, 77 Value: cty.SetVal([]cty.Value{ 78 cty.NumberIntVal(3), 79 cty.NumberIntVal(5), 80 cty.NumberIntVal(7), 81 cty.NumberIntVal(11), 82 }), 83 }, 84 }, 85 map[string]Output{ 86 "mapstring": { 87 Sensitive: false, 88 Value: json.RawMessage(`{"beep":"boop"}`), 89 Type: json.RawMessage(`["map","string"]`), 90 }, 91 "setnumber": { 92 Sensitive: false, 93 Value: json.RawMessage(`[3,5,7,11]`), 94 Type: json.RawMessage(`["set","number"]`), 95 }, 96 }, 97 false, 98 }, 99 } 100 101 for _, test := range tests { 102 got, err := MarshalOutputs(test.Outputs) 103 if test.Err { 104 if err == nil { 105 t.Fatal("succeeded; want error") 106 } 107 return 108 } else if err != nil { 109 t.Fatalf("unexpected error: %s", err) 110 } 111 if !cmp.Equal(test.Want, got) { 112 t.Fatalf("wrong result:\n%s", cmp.Diff(test.Want, got)) 113 } 114 } 115 } 116 117 func TestMarshalAttributeValues(t *testing.T) { 118 tests := []struct { 119 Attr cty.Value 120 Want AttributeValues 121 }{ 122 { 123 cty.NilVal, 124 nil, 125 }, 126 { 127 cty.NullVal(cty.String), 128 nil, 129 }, 130 { 131 cty.ObjectVal(map[string]cty.Value{ 132 "foo": cty.StringVal("bar"), 133 }), 134 AttributeValues{"foo": json.RawMessage(`"bar"`)}, 135 }, 136 { 137 cty.ObjectVal(map[string]cty.Value{ 138 "foo": cty.NullVal(cty.String), 139 }), 140 AttributeValues{"foo": json.RawMessage(`null`)}, 141 }, 142 { 143 cty.ObjectVal(map[string]cty.Value{ 144 "bar": cty.MapVal(map[string]cty.Value{ 145 "hello": cty.StringVal("world"), 146 }), 147 "baz": cty.ListVal([]cty.Value{ 148 cty.StringVal("goodnight"), 149 cty.StringVal("moon"), 150 }), 151 }), 152 AttributeValues{ 153 "bar": json.RawMessage(`{"hello":"world"}`), 154 "baz": json.RawMessage(`["goodnight","moon"]`), 155 }, 156 }, 157 // Marked values 158 { 159 cty.ObjectVal(map[string]cty.Value{ 160 "bar": cty.MapVal(map[string]cty.Value{ 161 "hello": cty.StringVal("world"), 162 }), 163 "baz": cty.ListVal([]cty.Value{ 164 cty.StringVal("goodnight"), 165 cty.StringVal("moon").Mark(marks.Sensitive), 166 }), 167 }), 168 AttributeValues{ 169 "bar": json.RawMessage(`{"hello":"world"}`), 170 "baz": json.RawMessage(`["goodnight","moon"]`), 171 }, 172 }, 173 } 174 175 for _, test := range tests { 176 got := marshalAttributeValues(test.Attr) 177 eq := reflect.DeepEqual(got, test.Want) 178 if !eq { 179 t.Fatalf("wrong result:\nGot: %#v\nWant: %#v\n", got, test.Want) 180 } 181 } 182 } 183 184 func TestMarshalResources(t *testing.T) { 185 deposedKey := states.NewDeposedKey() 186 tests := map[string]struct { 187 Resources map[string]*states.Resource 188 Schemas *tofu.Schemas 189 Want []Resource 190 Err bool 191 }{ 192 "nil": { 193 nil, 194 nil, 195 nil, 196 false, 197 }, 198 "single resource": { 199 map[string]*states.Resource{ 200 "test_thing.baz": { 201 Addr: addrs.AbsResource{ 202 Resource: addrs.Resource{ 203 Mode: addrs.ManagedResourceMode, 204 Type: "test_thing", 205 Name: "bar", 206 }, 207 }, 208 Instances: map[addrs.InstanceKey]*states.ResourceInstance{ 209 addrs.NoKey: { 210 Current: &states.ResourceInstanceObjectSrc{ 211 Status: states.ObjectReady, 212 AttrsJSON: []byte(`{"woozles":"confuzles"}`), 213 }, 214 }, 215 }, 216 ProviderConfig: addrs.AbsProviderConfig{ 217 Provider: addrs.NewDefaultProvider("test"), 218 Module: addrs.RootModule, 219 }, 220 }, 221 }, 222 testSchemas(), 223 []Resource{ 224 { 225 Address: "test_thing.bar", 226 Mode: "managed", 227 Type: "test_thing", 228 Name: "bar", 229 Index: nil, 230 ProviderName: "registry.opentofu.org/hashicorp/test", 231 AttributeValues: AttributeValues{ 232 "foozles": json.RawMessage(`null`), 233 "woozles": json.RawMessage(`"confuzles"`), 234 }, 235 SensitiveValues: json.RawMessage("{\"foozles\":true}"), 236 }, 237 }, 238 false, 239 }, 240 "single resource_with_sensitive": { 241 map[string]*states.Resource{ 242 "test_thing.baz": { 243 Addr: addrs.AbsResource{ 244 Resource: addrs.Resource{ 245 Mode: addrs.ManagedResourceMode, 246 Type: "test_thing", 247 Name: "bar", 248 }, 249 }, 250 Instances: map[addrs.InstanceKey]*states.ResourceInstance{ 251 addrs.NoKey: { 252 Current: &states.ResourceInstanceObjectSrc{ 253 Status: states.ObjectReady, 254 AttrsJSON: []byte(`{"woozles":"confuzles","foozles":"sensuzles"}`), 255 }, 256 }, 257 }, 258 ProviderConfig: addrs.AbsProviderConfig{ 259 Provider: addrs.NewDefaultProvider("test"), 260 Module: addrs.RootModule, 261 }, 262 }, 263 }, 264 testSchemas(), 265 []Resource{ 266 { 267 Address: "test_thing.bar", 268 Mode: "managed", 269 Type: "test_thing", 270 Name: "bar", 271 Index: nil, 272 ProviderName: "registry.opentofu.org/hashicorp/test", 273 AttributeValues: AttributeValues{ 274 "foozles": json.RawMessage(`"sensuzles"`), 275 "woozles": json.RawMessage(`"confuzles"`), 276 }, 277 SensitiveValues: json.RawMessage("{\"foozles\":true}"), 278 }, 279 }, 280 false, 281 }, 282 "resource with marks": { 283 map[string]*states.Resource{ 284 "test_thing.bar": { 285 Addr: addrs.AbsResource{ 286 Resource: addrs.Resource{ 287 Mode: addrs.ManagedResourceMode, 288 Type: "test_thing", 289 Name: "bar", 290 }, 291 }, 292 Instances: map[addrs.InstanceKey]*states.ResourceInstance{ 293 addrs.NoKey: { 294 Current: &states.ResourceInstanceObjectSrc{ 295 Status: states.ObjectReady, 296 AttrsJSON: []byte(`{"foozles":"confuzles"}`), 297 AttrSensitivePaths: []cty.PathValueMarks{{ 298 Path: cty.Path{cty.GetAttrStep{Name: "foozles"}}, 299 Marks: cty.NewValueMarks(marks.Sensitive)}, 300 }, 301 }, 302 }, 303 }, 304 ProviderConfig: addrs.AbsProviderConfig{ 305 Provider: addrs.NewDefaultProvider("test"), 306 Module: addrs.RootModule, 307 }, 308 }, 309 }, 310 testSchemas(), 311 []Resource{ 312 { 313 Address: "test_thing.bar", 314 Mode: "managed", 315 Type: "test_thing", 316 Name: "bar", 317 Index: nil, 318 ProviderName: "registry.opentofu.org/hashicorp/test", 319 AttributeValues: AttributeValues{ 320 "foozles": json.RawMessage(`"confuzles"`), 321 "woozles": json.RawMessage(`null`), 322 }, 323 SensitiveValues: json.RawMessage(`{"foozles":true}`), 324 }, 325 }, 326 false, 327 }, 328 "single resource wrong schema": { 329 map[string]*states.Resource{ 330 "test_thing.baz": { 331 Addr: addrs.AbsResource{ 332 Resource: addrs.Resource{ 333 Mode: addrs.ManagedResourceMode, 334 Type: "test_thing", 335 Name: "bar", 336 }, 337 }, 338 Instances: map[addrs.InstanceKey]*states.ResourceInstance{ 339 addrs.NoKey: { 340 Current: &states.ResourceInstanceObjectSrc{ 341 SchemaVersion: 1, 342 Status: states.ObjectReady, 343 AttrsJSON: []byte(`{"woozles":["confuzles"]}`), 344 }, 345 }, 346 }, 347 ProviderConfig: addrs.AbsProviderConfig{ 348 Provider: addrs.NewDefaultProvider("test"), 349 Module: addrs.RootModule, 350 }, 351 }, 352 }, 353 testSchemas(), 354 nil, 355 true, 356 }, 357 "resource with count": { 358 map[string]*states.Resource{ 359 "test_thing.bar": { 360 Addr: addrs.AbsResource{ 361 Resource: addrs.Resource{ 362 Mode: addrs.ManagedResourceMode, 363 Type: "test_thing", 364 Name: "bar", 365 }, 366 }, 367 Instances: map[addrs.InstanceKey]*states.ResourceInstance{ 368 addrs.IntKey(0): { 369 Current: &states.ResourceInstanceObjectSrc{ 370 Status: states.ObjectReady, 371 AttrsJSON: []byte(`{"woozles":"confuzles"}`), 372 }, 373 }, 374 }, 375 ProviderConfig: addrs.AbsProviderConfig{ 376 Provider: addrs.NewDefaultProvider("test"), 377 Module: addrs.RootModule, 378 }, 379 }, 380 }, 381 testSchemas(), 382 []Resource{ 383 { 384 Address: "test_thing.bar[0]", 385 Mode: "managed", 386 Type: "test_thing", 387 Name: "bar", 388 Index: json.RawMessage(`0`), 389 ProviderName: "registry.opentofu.org/hashicorp/test", 390 AttributeValues: AttributeValues{ 391 "foozles": json.RawMessage(`null`), 392 "woozles": json.RawMessage(`"confuzles"`), 393 }, 394 SensitiveValues: json.RawMessage("{\"foozles\":true}"), 395 }, 396 }, 397 false, 398 }, 399 "resource with for_each": { 400 map[string]*states.Resource{ 401 "test_thing.bar": { 402 Addr: addrs.AbsResource{ 403 Resource: addrs.Resource{ 404 Mode: addrs.ManagedResourceMode, 405 Type: "test_thing", 406 Name: "bar", 407 }, 408 }, 409 Instances: map[addrs.InstanceKey]*states.ResourceInstance{ 410 addrs.StringKey("rockhopper"): { 411 Current: &states.ResourceInstanceObjectSrc{ 412 Status: states.ObjectReady, 413 AttrsJSON: []byte(`{"woozles":"confuzles"}`), 414 }, 415 }, 416 }, 417 ProviderConfig: addrs.AbsProviderConfig{ 418 Provider: addrs.NewDefaultProvider("test"), 419 Module: addrs.RootModule, 420 }, 421 }, 422 }, 423 testSchemas(), 424 []Resource{ 425 { 426 Address: "test_thing.bar[\"rockhopper\"]", 427 Mode: "managed", 428 Type: "test_thing", 429 Name: "bar", 430 Index: json.RawMessage(`"rockhopper"`), 431 ProviderName: "registry.opentofu.org/hashicorp/test", 432 AttributeValues: AttributeValues{ 433 "foozles": json.RawMessage(`null`), 434 "woozles": json.RawMessage(`"confuzles"`), 435 }, 436 SensitiveValues: json.RawMessage("{\"foozles\":true}"), 437 }, 438 }, 439 false, 440 }, 441 "deposed resource": { 442 map[string]*states.Resource{ 443 "test_thing.baz": { 444 Addr: addrs.AbsResource{ 445 Resource: addrs.Resource{ 446 Mode: addrs.ManagedResourceMode, 447 Type: "test_thing", 448 Name: "bar", 449 }, 450 }, 451 Instances: map[addrs.InstanceKey]*states.ResourceInstance{ 452 addrs.NoKey: { 453 Deposed: map[states.DeposedKey]*states.ResourceInstanceObjectSrc{ 454 states.DeposedKey(deposedKey): { 455 Status: states.ObjectReady, 456 AttrsJSON: []byte(`{"woozles":"confuzles"}`), 457 }, 458 }, 459 }, 460 }, 461 ProviderConfig: addrs.AbsProviderConfig{ 462 Provider: addrs.NewDefaultProvider("test"), 463 Module: addrs.RootModule, 464 }, 465 }, 466 }, 467 testSchemas(), 468 []Resource{ 469 { 470 Address: "test_thing.bar", 471 Mode: "managed", 472 Type: "test_thing", 473 Name: "bar", 474 Index: nil, 475 ProviderName: "registry.opentofu.org/hashicorp/test", 476 DeposedKey: deposedKey.String(), 477 AttributeValues: AttributeValues{ 478 "foozles": json.RawMessage(`null`), 479 "woozles": json.RawMessage(`"confuzles"`), 480 }, 481 SensitiveValues: json.RawMessage("{\"foozles\":true}"), 482 }, 483 }, 484 false, 485 }, 486 "deposed and current resource": { 487 map[string]*states.Resource{ 488 "test_thing.baz": { 489 Addr: addrs.AbsResource{ 490 Resource: addrs.Resource{ 491 Mode: addrs.ManagedResourceMode, 492 Type: "test_thing", 493 Name: "bar", 494 }, 495 }, 496 Instances: map[addrs.InstanceKey]*states.ResourceInstance{ 497 addrs.NoKey: { 498 Deposed: map[states.DeposedKey]*states.ResourceInstanceObjectSrc{ 499 states.DeposedKey(deposedKey): { 500 Status: states.ObjectReady, 501 AttrsJSON: []byte(`{"woozles":"confuzles"}`), 502 }, 503 }, 504 Current: &states.ResourceInstanceObjectSrc{ 505 Status: states.ObjectReady, 506 AttrsJSON: []byte(`{"woozles":"confuzles"}`), 507 }, 508 }, 509 }, 510 ProviderConfig: addrs.AbsProviderConfig{ 511 Provider: addrs.NewDefaultProvider("test"), 512 Module: addrs.RootModule, 513 }, 514 }, 515 }, 516 testSchemas(), 517 []Resource{ 518 { 519 Address: "test_thing.bar", 520 Mode: "managed", 521 Type: "test_thing", 522 Name: "bar", 523 Index: nil, 524 ProviderName: "registry.opentofu.org/hashicorp/test", 525 AttributeValues: AttributeValues{ 526 "foozles": json.RawMessage(`null`), 527 "woozles": json.RawMessage(`"confuzles"`), 528 }, 529 SensitiveValues: json.RawMessage("{\"foozles\":true}"), 530 }, 531 { 532 Address: "test_thing.bar", 533 Mode: "managed", 534 Type: "test_thing", 535 Name: "bar", 536 Index: nil, 537 ProviderName: "registry.opentofu.org/hashicorp/test", 538 DeposedKey: deposedKey.String(), 539 AttributeValues: AttributeValues{ 540 "foozles": json.RawMessage(`null`), 541 "woozles": json.RawMessage(`"confuzles"`), 542 }, 543 SensitiveValues: json.RawMessage("{\"foozles\":true}"), 544 }, 545 }, 546 false, 547 }, 548 "resource with marked map attr": { 549 map[string]*states.Resource{ 550 "test_map_attr.bar": { 551 Addr: addrs.AbsResource{ 552 Resource: addrs.Resource{ 553 Mode: addrs.ManagedResourceMode, 554 Type: "test_map_attr", 555 Name: "bar", 556 }, 557 }, 558 Instances: map[addrs.InstanceKey]*states.ResourceInstance{ 559 addrs.NoKey: { 560 Current: &states.ResourceInstanceObjectSrc{ 561 Status: states.ObjectReady, 562 AttrsJSON: []byte(`{"data":{"woozles":"confuzles"}}`), 563 AttrSensitivePaths: []cty.PathValueMarks{{ 564 Path: cty.Path{cty.GetAttrStep{Name: "data"}}, 565 Marks: cty.NewValueMarks(marks.Sensitive)}, 566 }, 567 }, 568 }, 569 }, 570 ProviderConfig: addrs.AbsProviderConfig{ 571 Provider: addrs.NewDefaultProvider("test"), 572 Module: addrs.RootModule, 573 }, 574 }, 575 }, 576 testSchemas(), 577 []Resource{ 578 { 579 Address: "test_map_attr.bar", 580 Mode: "managed", 581 Type: "test_map_attr", 582 Name: "bar", 583 Index: nil, 584 ProviderName: "registry.opentofu.org/hashicorp/test", 585 AttributeValues: AttributeValues{ 586 "data": json.RawMessage(`{"woozles":"confuzles"}`), 587 }, 588 SensitiveValues: json.RawMessage(`{"data":true}`), 589 }, 590 }, 591 false, 592 }, 593 } 594 595 for name, test := range tests { 596 t.Run(name, func(t *testing.T) { 597 got, err := marshalResources(test.Resources, addrs.RootModuleInstance, test.Schemas) 598 if test.Err { 599 if err == nil { 600 t.Fatal("succeeded; want error") 601 } 602 return 603 } else if err != nil { 604 t.Fatalf("unexpected error: %s", err) 605 } 606 607 diff := cmp.Diff(got, test.Want) 608 if diff != "" { 609 t.Fatalf("wrong result: %s\n", diff) 610 } 611 612 }) 613 } 614 } 615 616 func TestMarshalModules_basic(t *testing.T) { 617 childModule, _ := addrs.ParseModuleInstanceStr("module.child") 618 subModule, _ := addrs.ParseModuleInstanceStr("module.submodule") 619 testState := states.BuildState(func(s *states.SyncState) { 620 s.SetResourceInstanceCurrent( 621 addrs.Resource{ 622 Mode: addrs.ManagedResourceMode, 623 Type: "test_instance", 624 Name: "foo", 625 }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance), 626 &states.ResourceInstanceObjectSrc{ 627 AttrsJSON: []byte(`{"id":"bar","foo":"value","bar":"value"}`), 628 Status: states.ObjectReady, 629 }, 630 addrs.AbsProviderConfig{ 631 Provider: addrs.NewDefaultProvider("test"), 632 Module: addrs.RootModule, 633 }, 634 ) 635 s.SetResourceInstanceCurrent( 636 addrs.Resource{ 637 Mode: addrs.ManagedResourceMode, 638 Type: "test_instance", 639 Name: "foo", 640 }.Instance(addrs.NoKey).Absolute(childModule), 641 &states.ResourceInstanceObjectSrc{ 642 AttrsJSON: []byte(`{"id":"foo","foo":"value","bar":"value"}`), 643 Status: states.ObjectReady, 644 }, 645 addrs.AbsProviderConfig{ 646 Provider: addrs.NewDefaultProvider("test"), 647 Module: childModule.Module(), 648 }, 649 ) 650 s.SetResourceInstanceCurrent( 651 addrs.Resource{ 652 Mode: addrs.ManagedResourceMode, 653 Type: "test_instance", 654 Name: "foo", 655 }.Instance(addrs.NoKey).Absolute(subModule), 656 &states.ResourceInstanceObjectSrc{ 657 AttrsJSON: []byte(`{"id":"foo","foo":"value","bar":"value"}`), 658 Status: states.ObjectReady, 659 }, 660 addrs.AbsProviderConfig{ 661 Provider: addrs.NewDefaultProvider("test"), 662 Module: subModule.Module(), 663 }, 664 ) 665 }) 666 moduleMap := make(map[string][]addrs.ModuleInstance) 667 moduleMap[""] = []addrs.ModuleInstance{childModule, subModule} 668 669 got, err := marshalModules(testState, testSchemas(), moduleMap[""], moduleMap) 670 671 if err != nil { 672 t.Fatalf("unexpected error: %s", err.Error()) 673 } 674 675 if len(got) != 2 { 676 t.Fatalf("wrong result! got %d modules, expected 2", len(got)) 677 } 678 679 if got[0].Address != "module.child" || got[1].Address != "module.submodule" { 680 t.Fatalf("wrong result! got %#v\n", got) 681 } 682 683 } 684 685 func TestMarshalModules_nested(t *testing.T) { 686 childModule, _ := addrs.ParseModuleInstanceStr("module.child") 687 subModule, _ := addrs.ParseModuleInstanceStr("module.child.module.submodule") 688 testState := states.BuildState(func(s *states.SyncState) { 689 s.SetResourceInstanceCurrent( 690 addrs.Resource{ 691 Mode: addrs.ManagedResourceMode, 692 Type: "test_instance", 693 Name: "foo", 694 }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance), 695 &states.ResourceInstanceObjectSrc{ 696 AttrsJSON: []byte(`{"id":"bar","foo":"value","bar":"value"}`), 697 Status: states.ObjectReady, 698 }, 699 addrs.AbsProviderConfig{ 700 Provider: addrs.NewDefaultProvider("test"), 701 Module: addrs.RootModule, 702 }, 703 ) 704 s.SetResourceInstanceCurrent( 705 addrs.Resource{ 706 Mode: addrs.ManagedResourceMode, 707 Type: "test_instance", 708 Name: "foo", 709 }.Instance(addrs.NoKey).Absolute(childModule), 710 &states.ResourceInstanceObjectSrc{ 711 AttrsJSON: []byte(`{"id":"foo","foo":"value","bar":"value"}`), 712 Status: states.ObjectReady, 713 }, 714 addrs.AbsProviderConfig{ 715 Provider: addrs.NewDefaultProvider("test"), 716 Module: childModule.Module(), 717 }, 718 ) 719 s.SetResourceInstanceCurrent( 720 addrs.Resource{ 721 Mode: addrs.ManagedResourceMode, 722 Type: "test_instance", 723 Name: "foo", 724 }.Instance(addrs.NoKey).Absolute(subModule), 725 &states.ResourceInstanceObjectSrc{ 726 AttrsJSON: []byte(`{"id":"foo","foo":"value","bar":"value"}`), 727 Status: states.ObjectReady, 728 }, 729 addrs.AbsProviderConfig{ 730 Provider: addrs.NewDefaultProvider("test"), 731 Module: subModule.Module(), 732 }, 733 ) 734 }) 735 moduleMap := make(map[string][]addrs.ModuleInstance) 736 moduleMap[""] = []addrs.ModuleInstance{childModule} 737 moduleMap[childModule.String()] = []addrs.ModuleInstance{subModule} 738 739 got, err := marshalModules(testState, testSchemas(), moduleMap[""], moduleMap) 740 741 if err != nil { 742 t.Fatalf("unexpected error: %s", err.Error()) 743 } 744 745 if len(got) != 1 { 746 t.Fatalf("wrong result! got %d modules, expected 1", len(got)) 747 } 748 749 if got[0].Address != "module.child" { 750 t.Fatalf("wrong result! got %#v\n", got) 751 } 752 753 if got[0].ChildModules[0].Address != "module.child.module.submodule" { 754 t.Fatalf("wrong result! got %#v\n", got) 755 } 756 } 757 758 func TestMarshalModules_parent_no_resources(t *testing.T) { 759 subModule, _ := addrs.ParseModuleInstanceStr("module.child.module.submodule") 760 testState := states.BuildState(func(s *states.SyncState) { 761 s.SetResourceInstanceCurrent( 762 addrs.Resource{ 763 Mode: addrs.ManagedResourceMode, 764 Type: "test_instance", 765 Name: "foo", 766 }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance), 767 &states.ResourceInstanceObjectSrc{ 768 AttrsJSON: []byte(`{"id":"bar","foo":"value","bar":"value"}`), 769 Status: states.ObjectReady, 770 }, 771 addrs.AbsProviderConfig{ 772 Provider: addrs.NewDefaultProvider("test"), 773 Module: addrs.RootModule, 774 }, 775 ) 776 s.SetResourceInstanceCurrent( 777 addrs.Resource{ 778 Mode: addrs.ManagedResourceMode, 779 Type: "test_instance", 780 Name: "foo", 781 }.Instance(addrs.NoKey).Absolute(subModule), 782 &states.ResourceInstanceObjectSrc{ 783 AttrsJSON: []byte(`{"id":"foo","foo":"value","bar":"value"}`), 784 Status: states.ObjectReady, 785 }, 786 addrs.AbsProviderConfig{ 787 Provider: addrs.NewDefaultProvider("test"), 788 Module: subModule.Module(), 789 }, 790 ) 791 }) 792 got, err := marshalRootModule(testState, testSchemas()) 793 794 if err != nil { 795 t.Fatalf("unexpected error: %s", err.Error()) 796 } 797 798 if len(got.ChildModules) != 1 { 799 t.Fatalf("wrong result! got %d modules, expected 1", len(got.ChildModules)) 800 } 801 802 if got.ChildModules[0].Address != "module.child" { 803 t.Fatalf("wrong result! got %#v\n", got) 804 } 805 806 if got.ChildModules[0].ChildModules[0].Address != "module.child.module.submodule" { 807 t.Fatalf("wrong result! got %#v\n", got) 808 } 809 } 810 811 func testSchemas() *tofu.Schemas { 812 return &tofu.Schemas{ 813 Providers: map[addrs.Provider]providers.ProviderSchema{ 814 addrs.NewDefaultProvider("test"): { 815 ResourceTypes: map[string]providers.Schema{ 816 "test_thing": { 817 Block: &configschema.Block{ 818 Attributes: map[string]*configschema.Attribute{ 819 "woozles": {Type: cty.String, Optional: true, Computed: true}, 820 "foozles": {Type: cty.String, Optional: true, Sensitive: true}, 821 }, 822 }, 823 }, 824 "test_instance": { 825 Block: &configschema.Block{ 826 Attributes: map[string]*configschema.Attribute{ 827 "id": {Type: cty.String, Optional: true, Computed: true}, 828 "foo": {Type: cty.String, Optional: true}, 829 "bar": {Type: cty.String, Optional: true}, 830 }, 831 }, 832 }, 833 "test_map_attr": { 834 Block: &configschema.Block{ 835 Attributes: map[string]*configschema.Attribute{ 836 "data": {Type: cty.Map(cty.String), Optional: true, Computed: true, Sensitive: true}, 837 }, 838 }, 839 }, 840 }, 841 }, 842 }, 843 } 844 } 845 846 func TestSensitiveAsBool(t *testing.T) { 847 tests := []struct { 848 Input cty.Value 849 Want cty.Value 850 }{ 851 { 852 cty.StringVal("hello"), 853 cty.False, 854 }, 855 { 856 cty.NullVal(cty.String), 857 cty.False, 858 }, 859 { 860 cty.StringVal("hello").Mark(marks.Sensitive), 861 cty.True, 862 }, 863 { 864 cty.NullVal(cty.String).Mark(marks.Sensitive), 865 cty.True, 866 }, 867 868 { 869 cty.NullVal(cty.DynamicPseudoType).Mark(marks.Sensitive), 870 cty.True, 871 }, 872 { 873 cty.NullVal(cty.Object(map[string]cty.Type{"test": cty.String})), 874 cty.False, 875 }, 876 { 877 cty.NullVal(cty.Object(map[string]cty.Type{"test": cty.String})).Mark(marks.Sensitive), 878 cty.True, 879 }, 880 { 881 cty.DynamicVal, 882 cty.False, 883 }, 884 { 885 cty.DynamicVal.Mark(marks.Sensitive), 886 cty.True, 887 }, 888 889 { 890 cty.ListValEmpty(cty.String), 891 cty.EmptyTupleVal, 892 }, 893 { 894 cty.ListValEmpty(cty.String).Mark(marks.Sensitive), 895 cty.True, 896 }, 897 { 898 cty.ListVal([]cty.Value{ 899 cty.StringVal("hello"), 900 cty.StringVal("friend").Mark(marks.Sensitive), 901 }), 902 cty.TupleVal([]cty.Value{ 903 cty.False, 904 cty.True, 905 }), 906 }, 907 { 908 cty.SetValEmpty(cty.String), 909 cty.EmptyTupleVal, 910 }, 911 { 912 cty.SetValEmpty(cty.String).Mark(marks.Sensitive), 913 cty.True, 914 }, 915 { 916 cty.SetVal([]cty.Value{cty.StringVal("hello")}), 917 cty.TupleVal([]cty.Value{cty.False}), 918 }, 919 { 920 cty.SetVal([]cty.Value{cty.StringVal("hello").Mark(marks.Sensitive)}), 921 cty.True, 922 }, 923 { 924 cty.EmptyTupleVal.Mark(marks.Sensitive), 925 cty.True, 926 }, 927 { 928 cty.TupleVal([]cty.Value{ 929 cty.StringVal("hello"), 930 cty.StringVal("friend").Mark(marks.Sensitive), 931 }), 932 cty.TupleVal([]cty.Value{ 933 cty.False, 934 cty.True, 935 }), 936 }, 937 { 938 cty.MapValEmpty(cty.String), 939 cty.EmptyObjectVal, 940 }, 941 { 942 cty.MapValEmpty(cty.String).Mark(marks.Sensitive), 943 cty.True, 944 }, 945 { 946 cty.MapVal(map[string]cty.Value{ 947 "greeting": cty.StringVal("hello"), 948 "animal": cty.StringVal("horse"), 949 }), 950 cty.EmptyObjectVal, 951 }, 952 { 953 cty.MapVal(map[string]cty.Value{ 954 "greeting": cty.StringVal("hello"), 955 "animal": cty.StringVal("horse").Mark(marks.Sensitive), 956 }), 957 cty.ObjectVal(map[string]cty.Value{ 958 "animal": cty.True, 959 }), 960 }, 961 { 962 cty.MapVal(map[string]cty.Value{ 963 "greeting": cty.StringVal("hello"), 964 "animal": cty.StringVal("horse").Mark(marks.Sensitive), 965 }).Mark(marks.Sensitive), 966 cty.True, 967 }, 968 { 969 cty.EmptyObjectVal, 970 cty.EmptyObjectVal, 971 }, 972 { 973 cty.ObjectVal(map[string]cty.Value{ 974 "greeting": cty.StringVal("hello"), 975 "animal": cty.StringVal("horse"), 976 }), 977 cty.EmptyObjectVal, 978 }, 979 { 980 cty.ObjectVal(map[string]cty.Value{ 981 "greeting": cty.StringVal("hello"), 982 "animal": cty.StringVal("horse").Mark(marks.Sensitive), 983 }), 984 cty.ObjectVal(map[string]cty.Value{ 985 "animal": cty.True, 986 }), 987 }, 988 { 989 cty.ObjectVal(map[string]cty.Value{ 990 "greeting": cty.StringVal("hello"), 991 "animal": cty.StringVal("horse").Mark(marks.Sensitive), 992 }).Mark(marks.Sensitive), 993 cty.True, 994 }, 995 { 996 cty.ListVal([]cty.Value{ 997 cty.ObjectVal(map[string]cty.Value{ 998 "a": cty.UnknownVal(cty.String), 999 }), 1000 cty.ObjectVal(map[string]cty.Value{ 1001 "a": cty.StringVal("known").Mark(marks.Sensitive), 1002 }), 1003 }), 1004 cty.TupleVal([]cty.Value{ 1005 cty.EmptyObjectVal, 1006 cty.ObjectVal(map[string]cty.Value{ 1007 "a": cty.True, 1008 }), 1009 }), 1010 }, 1011 { 1012 cty.ListVal([]cty.Value{ 1013 cty.MapValEmpty(cty.String), 1014 cty.MapVal(map[string]cty.Value{ 1015 "a": cty.StringVal("known").Mark(marks.Sensitive), 1016 }), 1017 cty.MapVal(map[string]cty.Value{ 1018 "a": cty.UnknownVal(cty.String), 1019 }), 1020 }), 1021 cty.TupleVal([]cty.Value{ 1022 cty.EmptyObjectVal, 1023 cty.ObjectVal(map[string]cty.Value{ 1024 "a": cty.True, 1025 }), 1026 cty.EmptyObjectVal, 1027 }), 1028 }, 1029 { 1030 cty.ObjectVal(map[string]cty.Value{ 1031 "list": cty.UnknownVal(cty.List(cty.String)), 1032 "set": cty.UnknownVal(cty.Set(cty.Bool)), 1033 "tuple": cty.UnknownVal(cty.Tuple([]cty.Type{cty.String, cty.Number})), 1034 "map": cty.UnknownVal(cty.Map(cty.String)), 1035 "object": cty.UnknownVal(cty.Object(map[string]cty.Type{"a": cty.String})), 1036 }), 1037 cty.ObjectVal(map[string]cty.Value{ 1038 "list": cty.EmptyTupleVal, 1039 "set": cty.EmptyTupleVal, 1040 "tuple": cty.EmptyTupleVal, 1041 "map": cty.EmptyObjectVal, 1042 "object": cty.EmptyObjectVal, 1043 }), 1044 }, 1045 } 1046 1047 for _, test := range tests { 1048 got := SensitiveAsBool(test.Input) 1049 if !reflect.DeepEqual(got, test.Want) { 1050 t.Errorf( 1051 "wrong result\ninput: %#v\ngot: %#v\nwant: %#v", 1052 test.Input, got, test.Want, 1053 ) 1054 } 1055 } 1056 }