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