github.com/hashicorp/terraform-plugin-sdk@v1.17.2/internal/helper/plugin/grpc_provider_test.go (about) 1 package plugin 2 3 import ( 4 "context" 5 "fmt" 6 "math/big" 7 "strconv" 8 "strings" 9 "testing" 10 "time" 11 12 "github.com/google/go-cmp/cmp" 13 "github.com/google/go-cmp/cmp/cmpopts" 14 "github.com/hashicorp/terraform-plugin-sdk/helper/schema" 15 "github.com/hashicorp/terraform-plugin-sdk/internal/plugin/convert" 16 proto "github.com/hashicorp/terraform-plugin-sdk/internal/tfplugin5" 17 "github.com/hashicorp/terraform-plugin-sdk/terraform" 18 "github.com/zclconf/go-cty/cty" 19 "github.com/zclconf/go-cty/cty/msgpack" 20 ) 21 22 // The GRPCProviderServer will directly implement the go protobuf server 23 var _ proto.ProviderServer = (*GRPCProviderServer)(nil) 24 25 var ( 26 valueComparer = cmp.Comparer(cty.Value.RawEquals) 27 equateEmpty = cmpopts.EquateEmpty() 28 ) 29 30 func TestUpgradeState_jsonState(t *testing.T) { 31 r := &schema.Resource{ 32 SchemaVersion: 2, 33 Schema: map[string]*schema.Schema{ 34 "two": { 35 Type: schema.TypeInt, 36 Optional: true, 37 }, 38 }, 39 } 40 41 r.StateUpgraders = []schema.StateUpgrader{ 42 { 43 Version: 0, 44 Type: cty.Object(map[string]cty.Type{ 45 "id": cty.String, 46 "zero": cty.Number, 47 }), 48 Upgrade: func(m map[string]interface{}, meta interface{}) (map[string]interface{}, error) { 49 _, ok := m["zero"].(float64) 50 if !ok { 51 return nil, fmt.Errorf("zero not found in %#v", m) 52 } 53 m["one"] = float64(1) 54 delete(m, "zero") 55 return m, nil 56 }, 57 }, 58 { 59 Version: 1, 60 Type: cty.Object(map[string]cty.Type{ 61 "id": cty.String, 62 "one": cty.Number, 63 }), 64 Upgrade: func(m map[string]interface{}, meta interface{}) (map[string]interface{}, error) { 65 _, ok := m["one"].(float64) 66 if !ok { 67 return nil, fmt.Errorf("one not found in %#v", m) 68 } 69 m["two"] = float64(2) 70 delete(m, "one") 71 return m, nil 72 }, 73 }, 74 } 75 76 server := &GRPCProviderServer{ 77 provider: &schema.Provider{ 78 ResourcesMap: map[string]*schema.Resource{ 79 "test": r, 80 }, 81 }, 82 } 83 84 req := &proto.UpgradeResourceState_Request{ 85 TypeName: "test", 86 Version: 0, 87 RawState: &proto.RawState{ 88 Json: []byte(`{"id":"bar","zero":0}`), 89 }, 90 } 91 92 resp, err := server.UpgradeResourceState(nil, req) 93 if err != nil { 94 t.Fatal(err) 95 } 96 97 if len(resp.Diagnostics) > 0 { 98 for _, d := range resp.Diagnostics { 99 t.Errorf("%#v", d) 100 } 101 t.Fatal("error") 102 } 103 104 val, err := msgpack.Unmarshal(resp.UpgradedState.Msgpack, r.CoreConfigSchema().ImpliedType()) 105 if err != nil { 106 t.Fatal(err) 107 } 108 109 expected := cty.ObjectVal(map[string]cty.Value{ 110 "id": cty.StringVal("bar"), 111 "two": cty.NumberIntVal(2), 112 }) 113 114 if !cmp.Equal(expected, val, valueComparer, equateEmpty) { 115 t.Fatal(cmp.Diff(expected, val, valueComparer, equateEmpty)) 116 } 117 } 118 119 func TestUpgradeState_jsonStateBigInt(t *testing.T) { 120 r := &schema.Resource{ 121 UseJSONNumber: true, 122 SchemaVersion: 2, 123 Schema: map[string]*schema.Schema{ 124 "int": { 125 Type: schema.TypeInt, 126 Required: true, 127 }, 128 }, 129 } 130 131 server := NewGRPCProviderServerShim(&schema.Provider{ 132 ResourcesMap: map[string]*schema.Resource{ 133 "test": r, 134 }, 135 }) 136 137 req := &proto.UpgradeResourceState_Request{ 138 TypeName: "test", 139 Version: 0, 140 RawState: &proto.RawState{ 141 Json: []byte(`{"id":"bar","int":7227701560655103598}`), 142 }, 143 } 144 145 resp, err := server.UpgradeResourceState(nil, req) 146 if err != nil { 147 t.Fatal(err) 148 } 149 150 if len(resp.Diagnostics) > 0 { 151 for _, d := range resp.Diagnostics { 152 t.Errorf("%#v", d) 153 } 154 t.Fatal("error") 155 } 156 157 val, err := msgpack.Unmarshal(resp.UpgradedState.Msgpack, r.CoreConfigSchema().ImpliedType()) 158 if err != nil { 159 t.Fatal(err) 160 } 161 162 expected := cty.ObjectVal(map[string]cty.Value{ 163 "id": cty.StringVal("bar"), 164 "int": cty.NumberIntVal(7227701560655103598), 165 }) 166 167 if !cmp.Equal(expected, val, valueComparer, equateEmpty) { 168 t.Fatal(cmp.Diff(expected, val, valueComparer, equateEmpty)) 169 } 170 } 171 172 func TestUpgradeState_removedAttr(t *testing.T) { 173 r1 := &schema.Resource{ 174 Schema: map[string]*schema.Schema{ 175 "two": { 176 Type: schema.TypeString, 177 Optional: true, 178 }, 179 }, 180 } 181 182 r2 := &schema.Resource{ 183 Schema: map[string]*schema.Schema{ 184 "multi": { 185 Type: schema.TypeSet, 186 Optional: true, 187 Elem: &schema.Resource{ 188 Schema: map[string]*schema.Schema{ 189 "set": { 190 Type: schema.TypeSet, 191 Optional: true, 192 Elem: &schema.Resource{ 193 Schema: map[string]*schema.Schema{ 194 "required": { 195 Type: schema.TypeString, 196 Required: true, 197 }, 198 }, 199 }, 200 }, 201 }, 202 }, 203 }, 204 }, 205 } 206 207 r3 := &schema.Resource{ 208 Schema: map[string]*schema.Schema{ 209 "config_mode_attr": { 210 Type: schema.TypeList, 211 ConfigMode: schema.SchemaConfigModeAttr, 212 Optional: true, 213 Elem: &schema.Resource{ 214 Schema: map[string]*schema.Schema{ 215 "foo": { 216 Type: schema.TypeString, 217 Optional: true, 218 }, 219 }, 220 }, 221 }, 222 }, 223 } 224 225 p := &schema.Provider{ 226 ResourcesMap: map[string]*schema.Resource{ 227 "r1": r1, 228 "r2": r2, 229 "r3": r3, 230 }, 231 } 232 233 server := &GRPCProviderServer{ 234 provider: p, 235 } 236 237 for _, tc := range []struct { 238 name string 239 raw string 240 expected cty.Value 241 }{ 242 { 243 name: "r1", 244 raw: `{"id":"bar","removed":"removed","two":"2"}`, 245 expected: cty.ObjectVal(map[string]cty.Value{ 246 "id": cty.StringVal("bar"), 247 "two": cty.StringVal("2"), 248 }), 249 }, 250 { 251 name: "r2", 252 raw: `{"id":"bar","multi":[{"set":[{"required":"ok","removed":"removed"}]}]}`, 253 expected: cty.ObjectVal(map[string]cty.Value{ 254 "id": cty.StringVal("bar"), 255 "multi": cty.SetVal([]cty.Value{ 256 cty.ObjectVal(map[string]cty.Value{ 257 "set": cty.SetVal([]cty.Value{ 258 cty.ObjectVal(map[string]cty.Value{ 259 "required": cty.StringVal("ok"), 260 }), 261 }), 262 }), 263 }), 264 }), 265 }, 266 { 267 name: "r3", 268 raw: `{"id":"bar","config_mode_attr":[{"foo":"ok","removed":"removed"}]}`, 269 expected: cty.ObjectVal(map[string]cty.Value{ 270 "id": cty.StringVal("bar"), 271 "config_mode_attr": cty.ListVal([]cty.Value{ 272 cty.ObjectVal(map[string]cty.Value{ 273 "foo": cty.StringVal("ok"), 274 }), 275 }), 276 }), 277 }, 278 } { 279 t.Run(tc.name, func(t *testing.T) { 280 req := &proto.UpgradeResourceState_Request{ 281 TypeName: tc.name, 282 Version: 0, 283 RawState: &proto.RawState{ 284 Json: []byte(tc.raw), 285 }, 286 } 287 resp, err := server.UpgradeResourceState(nil, req) 288 if err != nil { 289 t.Fatal(err) 290 } 291 292 if len(resp.Diagnostics) > 0 { 293 for _, d := range resp.Diagnostics { 294 t.Errorf("%#v", d) 295 } 296 t.Fatal("error") 297 } 298 val, err := msgpack.Unmarshal(resp.UpgradedState.Msgpack, p.ResourcesMap[tc.name].CoreConfigSchema().ImpliedType()) 299 if err != nil { 300 t.Fatal(err) 301 } 302 if !tc.expected.RawEquals(val) { 303 t.Fatalf("\nexpected: %#v\ngot: %#v\n", tc.expected, val) 304 } 305 }) 306 } 307 308 } 309 310 func TestUpgradeState_flatmapState(t *testing.T) { 311 r := &schema.Resource{ 312 SchemaVersion: 4, 313 Schema: map[string]*schema.Schema{ 314 "four": { 315 Type: schema.TypeInt, 316 Required: true, 317 }, 318 "block": { 319 Type: schema.TypeList, 320 Optional: true, 321 Elem: &schema.Resource{ 322 Schema: map[string]*schema.Schema{ 323 "attr": { 324 Type: schema.TypeString, 325 Optional: true, 326 }, 327 }, 328 }, 329 }, 330 }, 331 // this MigrateState will take the state to version 2 332 MigrateState: func(v int, is *terraform.InstanceState, _ interface{}) (*terraform.InstanceState, error) { 333 switch v { 334 case 0: 335 _, ok := is.Attributes["zero"] 336 if !ok { 337 return nil, fmt.Errorf("zero not found in %#v", is.Attributes) 338 } 339 is.Attributes["one"] = "1" 340 delete(is.Attributes, "zero") 341 fallthrough 342 case 1: 343 _, ok := is.Attributes["one"] 344 if !ok { 345 return nil, fmt.Errorf("one not found in %#v", is.Attributes) 346 } 347 is.Attributes["two"] = "2" 348 delete(is.Attributes, "one") 349 default: 350 return nil, fmt.Errorf("invalid schema version %d", v) 351 } 352 return is, nil 353 }, 354 } 355 356 r.StateUpgraders = []schema.StateUpgrader{ 357 { 358 Version: 2, 359 Type: cty.Object(map[string]cty.Type{ 360 "id": cty.String, 361 "two": cty.Number, 362 }), 363 Upgrade: func(m map[string]interface{}, meta interface{}) (map[string]interface{}, error) { 364 _, ok := m["two"].(float64) 365 if !ok { 366 return nil, fmt.Errorf("two not found in %#v", m) 367 } 368 m["three"] = float64(3) 369 delete(m, "two") 370 return m, nil 371 }, 372 }, 373 { 374 Version: 3, 375 Type: cty.Object(map[string]cty.Type{ 376 "id": cty.String, 377 "three": cty.Number, 378 }), 379 Upgrade: func(m map[string]interface{}, meta interface{}) (map[string]interface{}, error) { 380 _, ok := m["three"].(float64) 381 if !ok { 382 return nil, fmt.Errorf("three not found in %#v", m) 383 } 384 m["four"] = float64(4) 385 delete(m, "three") 386 return m, nil 387 }, 388 }, 389 } 390 391 server := &GRPCProviderServer{ 392 provider: &schema.Provider{ 393 ResourcesMap: map[string]*schema.Resource{ 394 "test": r, 395 }, 396 }, 397 } 398 399 testReqs := []*proto.UpgradeResourceState_Request{ 400 { 401 TypeName: "test", 402 Version: 0, 403 RawState: &proto.RawState{ 404 Flatmap: map[string]string{ 405 "id": "bar", 406 "zero": "0", 407 }, 408 }, 409 }, 410 { 411 TypeName: "test", 412 Version: 1, 413 RawState: &proto.RawState{ 414 Flatmap: map[string]string{ 415 "id": "bar", 416 "one": "1", 417 }, 418 }, 419 }, 420 // two and up could be stored in flatmap or json states 421 { 422 TypeName: "test", 423 Version: 2, 424 RawState: &proto.RawState{ 425 Flatmap: map[string]string{ 426 "id": "bar", 427 "two": "2", 428 }, 429 }, 430 }, 431 { 432 TypeName: "test", 433 Version: 2, 434 RawState: &proto.RawState{ 435 Json: []byte(`{"id":"bar","two":2}`), 436 }, 437 }, 438 { 439 TypeName: "test", 440 Version: 3, 441 RawState: &proto.RawState{ 442 Flatmap: map[string]string{ 443 "id": "bar", 444 "three": "3", 445 }, 446 }, 447 }, 448 { 449 TypeName: "test", 450 Version: 3, 451 RawState: &proto.RawState{ 452 Json: []byte(`{"id":"bar","three":3}`), 453 }, 454 }, 455 { 456 TypeName: "test", 457 Version: 4, 458 RawState: &proto.RawState{ 459 Flatmap: map[string]string{ 460 "id": "bar", 461 "four": "4", 462 }, 463 }, 464 }, 465 { 466 TypeName: "test", 467 Version: 4, 468 RawState: &proto.RawState{ 469 Json: []byte(`{"id":"bar","four":4}`), 470 }, 471 }, 472 } 473 474 for i, req := range testReqs { 475 t.Run(fmt.Sprintf("%d-%d", i, req.Version), func(t *testing.T) { 476 resp, err := server.UpgradeResourceState(nil, req) 477 if err != nil { 478 t.Fatal(err) 479 } 480 481 if len(resp.Diagnostics) > 0 { 482 for _, d := range resp.Diagnostics { 483 t.Errorf("%#v", d) 484 } 485 t.Fatal("error") 486 } 487 488 val, err := msgpack.Unmarshal(resp.UpgradedState.Msgpack, r.CoreConfigSchema().ImpliedType()) 489 if err != nil { 490 t.Fatal(err) 491 } 492 493 expected := cty.ObjectVal(map[string]cty.Value{ 494 "block": cty.ListValEmpty(cty.Object(map[string]cty.Type{"attr": cty.String})), 495 "id": cty.StringVal("bar"), 496 "four": cty.NumberIntVal(4), 497 }) 498 499 if !cmp.Equal(expected, val, valueComparer, equateEmpty) { 500 t.Fatal(cmp.Diff(expected, val, valueComparer, equateEmpty)) 501 } 502 }) 503 } 504 } 505 506 func TestUpgradeState_flatmapStateMissingMigrateState(t *testing.T) { 507 r := &schema.Resource{ 508 SchemaVersion: 1, 509 Schema: map[string]*schema.Schema{ 510 "one": { 511 Type: schema.TypeInt, 512 Required: true, 513 }, 514 }, 515 } 516 517 server := &GRPCProviderServer{ 518 provider: &schema.Provider{ 519 ResourcesMap: map[string]*schema.Resource{ 520 "test": r, 521 }, 522 }, 523 } 524 525 testReqs := []*proto.UpgradeResourceState_Request{ 526 { 527 TypeName: "test", 528 Version: 0, 529 RawState: &proto.RawState{ 530 Flatmap: map[string]string{ 531 "id": "bar", 532 "one": "1", 533 }, 534 }, 535 }, 536 { 537 TypeName: "test", 538 Version: 1, 539 RawState: &proto.RawState{ 540 Flatmap: map[string]string{ 541 "id": "bar", 542 "one": "1", 543 }, 544 }, 545 }, 546 { 547 TypeName: "test", 548 Version: 1, 549 RawState: &proto.RawState{ 550 Json: []byte(`{"id":"bar","one":1}`), 551 }, 552 }, 553 } 554 555 for i, req := range testReqs { 556 t.Run(fmt.Sprintf("%d-%d", i, req.Version), func(t *testing.T) { 557 resp, err := server.UpgradeResourceState(nil, req) 558 if err != nil { 559 t.Fatal(err) 560 } 561 562 if len(resp.Diagnostics) > 0 { 563 for _, d := range resp.Diagnostics { 564 t.Errorf("%#v", d) 565 } 566 t.Fatal("error") 567 } 568 569 val, err := msgpack.Unmarshal(resp.UpgradedState.Msgpack, r.CoreConfigSchema().ImpliedType()) 570 if err != nil { 571 t.Fatal(err) 572 } 573 574 expected := cty.ObjectVal(map[string]cty.Value{ 575 "id": cty.StringVal("bar"), 576 "one": cty.NumberIntVal(1), 577 }) 578 579 if !cmp.Equal(expected, val, valueComparer, equateEmpty) { 580 t.Fatal(cmp.Diff(expected, val, valueComparer, equateEmpty)) 581 } 582 }) 583 } 584 } 585 586 func TestPlanResourceChange(t *testing.T) { 587 r := &schema.Resource{ 588 SchemaVersion: 4, 589 Schema: map[string]*schema.Schema{ 590 "foo": { 591 Type: schema.TypeInt, 592 Optional: true, 593 }, 594 }, 595 } 596 597 server := &GRPCProviderServer{ 598 provider: &schema.Provider{ 599 ResourcesMap: map[string]*schema.Resource{ 600 "test": r, 601 }, 602 }, 603 } 604 605 schema := r.CoreConfigSchema() 606 priorState, err := msgpack.Marshal(cty.NullVal(schema.ImpliedType()), schema.ImpliedType()) 607 if err != nil { 608 t.Fatal(err) 609 } 610 611 // A propsed state with only the ID unknown will produce a nil diff, and 612 // should return the propsed state value. 613 proposedVal, err := schema.CoerceValue(cty.ObjectVal(map[string]cty.Value{ 614 "id": cty.UnknownVal(cty.String), 615 })) 616 if err != nil { 617 t.Fatal(err) 618 } 619 proposedState, err := msgpack.Marshal(proposedVal, schema.ImpliedType()) 620 if err != nil { 621 t.Fatal(err) 622 } 623 624 testReq := &proto.PlanResourceChange_Request{ 625 TypeName: "test", 626 PriorState: &proto.DynamicValue{ 627 Msgpack: priorState, 628 }, 629 ProposedNewState: &proto.DynamicValue{ 630 Msgpack: proposedState, 631 }, 632 } 633 634 resp, err := server.PlanResourceChange(context.Background(), testReq) 635 if err != nil { 636 t.Fatal(err) 637 } 638 639 plannedStateVal, err := msgpack.Unmarshal(resp.PlannedState.Msgpack, schema.ImpliedType()) 640 if err != nil { 641 t.Fatal(err) 642 } 643 644 if !cmp.Equal(proposedVal, plannedStateVal, valueComparer) { 645 t.Fatal(cmp.Diff(proposedVal, plannedStateVal, valueComparer)) 646 } 647 } 648 649 func TestPlanResourceChange_bigint(t *testing.T) { 650 r := &schema.Resource{ 651 UseJSONNumber: true, 652 Schema: map[string]*schema.Schema{ 653 "foo": { 654 Type: schema.TypeInt, 655 Required: true, 656 }, 657 }, 658 } 659 660 server := NewGRPCProviderServerShim(&schema.Provider{ 661 ResourcesMap: map[string]*schema.Resource{ 662 "test": r, 663 }, 664 }) 665 666 schema := r.CoreConfigSchema() 667 priorState, err := msgpack.Marshal(cty.NullVal(schema.ImpliedType()), schema.ImpliedType()) 668 if err != nil { 669 t.Fatal(err) 670 } 671 672 proposedVal := cty.ObjectVal(map[string]cty.Value{ 673 "id": cty.UnknownVal(cty.String), 674 "foo": cty.MustParseNumberVal("7227701560655103598"), 675 }) 676 proposedState, err := msgpack.Marshal(proposedVal, schema.ImpliedType()) 677 if err != nil { 678 t.Fatal(err) 679 } 680 681 testReq := &proto.PlanResourceChange_Request{ 682 TypeName: "test", 683 PriorState: &proto.DynamicValue{ 684 Msgpack: priorState, 685 }, 686 ProposedNewState: &proto.DynamicValue{ 687 Msgpack: proposedState, 688 }, 689 } 690 691 resp, err := server.PlanResourceChange(context.Background(), testReq) 692 if err != nil { 693 t.Fatal(err) 694 } 695 696 plannedStateVal, err := msgpack.Unmarshal(resp.PlannedState.Msgpack, schema.ImpliedType()) 697 if err != nil { 698 t.Fatal(err) 699 } 700 701 if !cmp.Equal(proposedVal, plannedStateVal, valueComparer) { 702 t.Fatal(cmp.Diff(proposedVal, plannedStateVal, valueComparer)) 703 } 704 705 plannedStateFoo, acc := plannedStateVal.GetAttr("foo").AsBigFloat().Int64() 706 if acc != big.Exact { 707 t.Fatalf("Expected exact accuracy, got %s", acc) 708 } 709 if plannedStateFoo != 7227701560655103598 { 710 t.Fatalf("Expected %d, got %d, this represents a loss of precision in planning large numbers", 7227701560655103598, plannedStateFoo) 711 } 712 } 713 714 func TestApplyResourceChange(t *testing.T) { 715 r := &schema.Resource{ 716 SchemaVersion: 4, 717 Schema: map[string]*schema.Schema{ 718 "foo": { 719 Type: schema.TypeInt, 720 Optional: true, 721 }, 722 }, 723 Create: func(rd *schema.ResourceData, _ interface{}) error { 724 rd.SetId("bar") 725 return nil 726 }, 727 } 728 729 server := &GRPCProviderServer{ 730 provider: &schema.Provider{ 731 ResourcesMap: map[string]*schema.Resource{ 732 "test": r, 733 }, 734 }, 735 } 736 737 schema := r.CoreConfigSchema() 738 priorState, err := msgpack.Marshal(cty.NullVal(schema.ImpliedType()), schema.ImpliedType()) 739 if err != nil { 740 t.Fatal(err) 741 } 742 743 // A proposed state with only the ID unknown will produce a nil diff, and 744 // should return the proposed state value. 745 plannedVal, err := schema.CoerceValue(cty.ObjectVal(map[string]cty.Value{ 746 "id": cty.UnknownVal(cty.String), 747 })) 748 if err != nil { 749 t.Fatal(err) 750 } 751 plannedState, err := msgpack.Marshal(plannedVal, schema.ImpliedType()) 752 if err != nil { 753 t.Fatal(err) 754 } 755 756 testReq := &proto.ApplyResourceChange_Request{ 757 TypeName: "test", 758 PriorState: &proto.DynamicValue{ 759 Msgpack: priorState, 760 }, 761 PlannedState: &proto.DynamicValue{ 762 Msgpack: plannedState, 763 }, 764 } 765 766 resp, err := server.ApplyResourceChange(context.Background(), testReq) 767 if err != nil { 768 t.Fatal(err) 769 } 770 771 newStateVal, err := msgpack.Unmarshal(resp.NewState.Msgpack, schema.ImpliedType()) 772 if err != nil { 773 t.Fatal(err) 774 } 775 776 id := newStateVal.GetAttr("id").AsString() 777 if id != "bar" { 778 t.Fatalf("incorrect final state: %#v\n", newStateVal) 779 } 780 } 781 782 func TestApplyResourceChange_bigint(t *testing.T) { 783 r := &schema.Resource{ 784 UseJSONNumber: true, 785 Schema: map[string]*schema.Schema{ 786 "foo": { 787 Type: schema.TypeInt, 788 Required: true, 789 }, 790 }, 791 Create: func(rd *schema.ResourceData, _ interface{}) error { 792 rd.SetId("bar") 793 return nil 794 }, 795 } 796 797 server := NewGRPCProviderServerShim(&schema.Provider{ 798 ResourcesMap: map[string]*schema.Resource{ 799 "test": r, 800 }, 801 }) 802 803 schema := r.CoreConfigSchema() 804 priorState, err := msgpack.Marshal(cty.NullVal(schema.ImpliedType()), schema.ImpliedType()) 805 if err != nil { 806 t.Fatal(err) 807 } 808 809 plannedVal := cty.ObjectVal(map[string]cty.Value{ 810 "id": cty.UnknownVal(cty.String), 811 "foo": cty.MustParseNumberVal("7227701560655103598"), 812 }) 813 plannedState, err := msgpack.Marshal(plannedVal, schema.ImpliedType()) 814 if err != nil { 815 t.Fatal(err) 816 } 817 818 testReq := &proto.ApplyResourceChange_Request{ 819 TypeName: "test", 820 PriorState: &proto.DynamicValue{ 821 Msgpack: priorState, 822 }, 823 PlannedState: &proto.DynamicValue{ 824 Msgpack: plannedState, 825 }, 826 } 827 828 resp, err := server.ApplyResourceChange(context.Background(), testReq) 829 if err != nil { 830 t.Fatal(err) 831 } 832 833 newStateVal, err := msgpack.Unmarshal(resp.NewState.Msgpack, schema.ImpliedType()) 834 if err != nil { 835 t.Fatal(err) 836 } 837 838 id := newStateVal.GetAttr("id").AsString() 839 if id != "bar" { 840 t.Fatalf("incorrect final state: %#v\n", newStateVal) 841 } 842 843 foo, acc := newStateVal.GetAttr("foo").AsBigFloat().Int64() 844 if acc != big.Exact { 845 t.Fatalf("Expected exact accuracy, got %s", acc) 846 } 847 if foo != 7227701560655103598 { 848 t.Fatalf("Expected %d, got %d, this represents a loss of precision in applying large numbers", 7227701560655103598, foo) 849 } 850 } 851 852 func TestPrepareProviderConfig(t *testing.T) { 853 for _, tc := range []struct { 854 Name string 855 Schema map[string]*schema.Schema 856 ConfigVal cty.Value 857 ExpectError string 858 ExpectConfig cty.Value 859 }{ 860 { 861 Name: "test prepare", 862 Schema: map[string]*schema.Schema{ 863 "foo": { 864 Type: schema.TypeString, 865 Optional: true, 866 }, 867 }, 868 ConfigVal: cty.ObjectVal(map[string]cty.Value{ 869 "foo": cty.StringVal("bar"), 870 }), 871 ExpectConfig: cty.ObjectVal(map[string]cty.Value{ 872 "foo": cty.StringVal("bar"), 873 }), 874 }, 875 { 876 Name: "test default", 877 Schema: map[string]*schema.Schema{ 878 "foo": { 879 Type: schema.TypeString, 880 Optional: true, 881 Default: "default", 882 }, 883 }, 884 ConfigVal: cty.ObjectVal(map[string]cty.Value{ 885 "foo": cty.NullVal(cty.String), 886 }), 887 ExpectConfig: cty.ObjectVal(map[string]cty.Value{ 888 "foo": cty.StringVal("default"), 889 }), 890 }, 891 { 892 Name: "test defaultfunc", 893 Schema: map[string]*schema.Schema{ 894 "foo": { 895 Type: schema.TypeString, 896 Optional: true, 897 DefaultFunc: func() (interface{}, error) { 898 return "defaultfunc", nil 899 }, 900 }, 901 }, 902 ConfigVal: cty.ObjectVal(map[string]cty.Value{ 903 "foo": cty.NullVal(cty.String), 904 }), 905 ExpectConfig: cty.ObjectVal(map[string]cty.Value{ 906 "foo": cty.StringVal("defaultfunc"), 907 }), 908 }, 909 { 910 Name: "test default required", 911 Schema: map[string]*schema.Schema{ 912 "foo": { 913 Type: schema.TypeString, 914 Required: true, 915 DefaultFunc: func() (interface{}, error) { 916 return "defaultfunc", nil 917 }, 918 }, 919 }, 920 ConfigVal: cty.ObjectVal(map[string]cty.Value{ 921 "foo": cty.NullVal(cty.String), 922 }), 923 ExpectConfig: cty.ObjectVal(map[string]cty.Value{ 924 "foo": cty.StringVal("defaultfunc"), 925 }), 926 }, 927 { 928 Name: "test incorrect type", 929 Schema: map[string]*schema.Schema{ 930 "foo": { 931 Type: schema.TypeString, 932 Required: true, 933 }, 934 }, 935 ConfigVal: cty.ObjectVal(map[string]cty.Value{ 936 "foo": cty.NumberIntVal(3), 937 }), 938 ExpectConfig: cty.ObjectVal(map[string]cty.Value{ 939 "foo": cty.StringVal("3"), 940 }), 941 }, 942 { 943 Name: "test incorrect default type", 944 Schema: map[string]*schema.Schema{ 945 "foo": { 946 Type: schema.TypeString, 947 Optional: true, 948 Default: true, 949 }, 950 }, 951 ConfigVal: cty.ObjectVal(map[string]cty.Value{ 952 "foo": cty.NullVal(cty.String), 953 }), 954 ExpectConfig: cty.ObjectVal(map[string]cty.Value{ 955 "foo": cty.StringVal("true"), 956 }), 957 }, 958 { 959 Name: "test incorrect default bool type", 960 Schema: map[string]*schema.Schema{ 961 "foo": { 962 Type: schema.TypeBool, 963 Optional: true, 964 Default: "", 965 }, 966 }, 967 ConfigVal: cty.ObjectVal(map[string]cty.Value{ 968 "foo": cty.NullVal(cty.Bool), 969 }), 970 ExpectConfig: cty.ObjectVal(map[string]cty.Value{ 971 "foo": cty.False, 972 }), 973 }, 974 { 975 Name: "test deprecated default", 976 Schema: map[string]*schema.Schema{ 977 "foo": { 978 Type: schema.TypeString, 979 Optional: true, 980 Default: "do not use", 981 Removed: "don't use this", 982 }, 983 }, 984 ConfigVal: cty.ObjectVal(map[string]cty.Value{ 985 "foo": cty.NullVal(cty.String), 986 }), 987 ExpectConfig: cty.ObjectVal(map[string]cty.Value{ 988 "foo": cty.NullVal(cty.String), 989 }), 990 }, 991 } { 992 t.Run(tc.Name, func(t *testing.T) { 993 server := &GRPCProviderServer{ 994 provider: &schema.Provider{ 995 Schema: tc.Schema, 996 }, 997 } 998 999 block := schema.InternalMap(tc.Schema).CoreConfigSchema() 1000 1001 rawConfig, err := msgpack.Marshal(tc.ConfigVal, block.ImpliedType()) 1002 if err != nil { 1003 t.Fatal(err) 1004 } 1005 1006 testReq := &proto.PrepareProviderConfig_Request{ 1007 Config: &proto.DynamicValue{ 1008 Msgpack: rawConfig, 1009 }, 1010 } 1011 1012 resp, err := server.PrepareProviderConfig(nil, testReq) 1013 if err != nil { 1014 t.Fatal(err) 1015 } 1016 1017 if tc.ExpectError != "" && len(resp.Diagnostics) > 0 { 1018 for _, d := range resp.Diagnostics { 1019 if !strings.Contains(d.Summary, tc.ExpectError) { 1020 t.Fatalf("Unexpected error: %s/%s", d.Summary, d.Detail) 1021 } 1022 } 1023 return 1024 } 1025 1026 // we should have no errors past this point 1027 for _, d := range resp.Diagnostics { 1028 if d.Severity == proto.Diagnostic_ERROR { 1029 t.Fatal(resp.Diagnostics) 1030 } 1031 } 1032 1033 val, err := msgpack.Unmarshal(resp.PreparedConfig.Msgpack, block.ImpliedType()) 1034 if err != nil { 1035 t.Fatal(err) 1036 } 1037 1038 if tc.ExpectConfig.GoString() != val.GoString() { 1039 t.Fatalf("\nexpected: %#v\ngot: %#v", tc.ExpectConfig, val) 1040 } 1041 }) 1042 } 1043 } 1044 1045 func TestGetSchemaTimeouts(t *testing.T) { 1046 r := &schema.Resource{ 1047 SchemaVersion: 4, 1048 Timeouts: &schema.ResourceTimeout{ 1049 Create: schema.DefaultTimeout(time.Second), 1050 Read: schema.DefaultTimeout(2 * time.Second), 1051 Update: schema.DefaultTimeout(3 * time.Second), 1052 Default: schema.DefaultTimeout(10 * time.Second), 1053 }, 1054 Schema: map[string]*schema.Schema{ 1055 "foo": { 1056 Type: schema.TypeInt, 1057 Optional: true, 1058 }, 1059 }, 1060 } 1061 1062 // verify that the timeouts appear in the schema as defined 1063 block := r.CoreConfigSchema() 1064 timeoutsBlock := block.BlockTypes["timeouts"] 1065 if timeoutsBlock == nil { 1066 t.Fatal("missing timeouts in schema") 1067 } 1068 1069 if timeoutsBlock.Attributes["create"] == nil { 1070 t.Fatal("missing create timeout in schema") 1071 } 1072 if timeoutsBlock.Attributes["read"] == nil { 1073 t.Fatal("missing read timeout in schema") 1074 } 1075 if timeoutsBlock.Attributes["update"] == nil { 1076 t.Fatal("missing update timeout in schema") 1077 } 1078 if d := timeoutsBlock.Attributes["delete"]; d != nil { 1079 t.Fatalf("unexpected delete timeout in schema: %#v", d) 1080 } 1081 if timeoutsBlock.Attributes["default"] == nil { 1082 t.Fatal("missing default timeout in schema") 1083 } 1084 } 1085 1086 func TestNormalizeNullValues(t *testing.T) { 1087 for i, tc := range []struct { 1088 Src, Dst, Expect cty.Value 1089 Apply bool 1090 }{ 1091 { 1092 // The known set value is copied over the null set value 1093 Src: cty.ObjectVal(map[string]cty.Value{ 1094 "set": cty.SetVal([]cty.Value{ 1095 cty.ObjectVal(map[string]cty.Value{ 1096 "foo": cty.NullVal(cty.String), 1097 }), 1098 }), 1099 }), 1100 Dst: cty.ObjectVal(map[string]cty.Value{ 1101 "set": cty.NullVal(cty.Set(cty.Object(map[string]cty.Type{ 1102 "foo": cty.String, 1103 }))), 1104 }), 1105 Expect: cty.ObjectVal(map[string]cty.Value{ 1106 "set": cty.SetVal([]cty.Value{ 1107 cty.ObjectVal(map[string]cty.Value{ 1108 "foo": cty.NullVal(cty.String), 1109 }), 1110 }), 1111 }), 1112 Apply: true, 1113 }, 1114 { 1115 // A zero set value is kept 1116 Src: cty.ObjectVal(map[string]cty.Value{ 1117 "set": cty.SetValEmpty(cty.String), 1118 }), 1119 Dst: cty.ObjectVal(map[string]cty.Value{ 1120 "set": cty.SetValEmpty(cty.String), 1121 }), 1122 Expect: cty.ObjectVal(map[string]cty.Value{ 1123 "set": cty.SetValEmpty(cty.String), 1124 }), 1125 }, 1126 { 1127 // The known set value is copied over the null set value 1128 Src: cty.ObjectVal(map[string]cty.Value{ 1129 "set": cty.SetVal([]cty.Value{ 1130 cty.ObjectVal(map[string]cty.Value{ 1131 "foo": cty.NullVal(cty.String), 1132 }), 1133 }), 1134 }), 1135 Dst: cty.ObjectVal(map[string]cty.Value{ 1136 "set": cty.NullVal(cty.Set(cty.Object(map[string]cty.Type{ 1137 "foo": cty.String, 1138 }))), 1139 }), 1140 // If we're only in a plan, we can't compare sets at all 1141 Expect: cty.ObjectVal(map[string]cty.Value{ 1142 "set": cty.NullVal(cty.Set(cty.Object(map[string]cty.Type{ 1143 "foo": cty.String, 1144 }))), 1145 }), 1146 }, 1147 { 1148 // The empty map is copied over the null map 1149 Src: cty.ObjectVal(map[string]cty.Value{ 1150 "map": cty.MapValEmpty(cty.String), 1151 }), 1152 Dst: cty.ObjectVal(map[string]cty.Value{ 1153 "map": cty.NullVal(cty.Map(cty.String)), 1154 }), 1155 Expect: cty.ObjectVal(map[string]cty.Value{ 1156 "map": cty.MapValEmpty(cty.String), 1157 }), 1158 Apply: true, 1159 }, 1160 { 1161 // A zero value primitive is copied over a null primitive 1162 Src: cty.ObjectVal(map[string]cty.Value{ 1163 "string": cty.StringVal(""), 1164 }), 1165 Dst: cty.ObjectVal(map[string]cty.Value{ 1166 "string": cty.NullVal(cty.String), 1167 }), 1168 Expect: cty.ObjectVal(map[string]cty.Value{ 1169 "string": cty.StringVal(""), 1170 }), 1171 Apply: true, 1172 }, 1173 { 1174 // Plan primitives are kept 1175 Src: cty.ObjectVal(map[string]cty.Value{ 1176 "string": cty.NumberIntVal(0), 1177 }), 1178 Dst: cty.ObjectVal(map[string]cty.Value{ 1179 "string": cty.NullVal(cty.Number), 1180 }), 1181 Expect: cty.ObjectVal(map[string]cty.Value{ 1182 "string": cty.NullVal(cty.Number), 1183 }), 1184 }, 1185 { 1186 // Neither plan nor apply should remove empty strings 1187 Src: cty.ObjectVal(map[string]cty.Value{ 1188 "string": cty.StringVal(""), 1189 }), 1190 Dst: cty.ObjectVal(map[string]cty.Value{ 1191 "string": cty.NullVal(cty.String), 1192 }), 1193 Expect: cty.ObjectVal(map[string]cty.Value{ 1194 "string": cty.StringVal(""), 1195 }), 1196 }, 1197 { 1198 // Neither plan nor apply should remove empty strings 1199 Src: cty.ObjectVal(map[string]cty.Value{ 1200 "string": cty.StringVal(""), 1201 }), 1202 Dst: cty.ObjectVal(map[string]cty.Value{ 1203 "string": cty.NullVal(cty.String), 1204 }), 1205 Expect: cty.ObjectVal(map[string]cty.Value{ 1206 "string": cty.StringVal(""), 1207 }), 1208 Apply: true, 1209 }, 1210 { 1211 // The null map is retained, because the src was unknown 1212 Src: cty.ObjectVal(map[string]cty.Value{ 1213 "map": cty.UnknownVal(cty.Map(cty.String)), 1214 }), 1215 Dst: cty.ObjectVal(map[string]cty.Value{ 1216 "map": cty.NullVal(cty.Map(cty.String)), 1217 }), 1218 Expect: cty.ObjectVal(map[string]cty.Value{ 1219 "map": cty.NullVal(cty.Map(cty.String)), 1220 }), 1221 Apply: true, 1222 }, 1223 { 1224 // the nul set is retained, because the src set contains an unknown value 1225 Src: cty.ObjectVal(map[string]cty.Value{ 1226 "set": cty.SetVal([]cty.Value{ 1227 cty.ObjectVal(map[string]cty.Value{ 1228 "foo": cty.UnknownVal(cty.String), 1229 }), 1230 }), 1231 }), 1232 Dst: cty.ObjectVal(map[string]cty.Value{ 1233 "set": cty.NullVal(cty.Set(cty.Object(map[string]cty.Type{ 1234 "foo": cty.String, 1235 }))), 1236 }), 1237 Expect: cty.ObjectVal(map[string]cty.Value{ 1238 "set": cty.NullVal(cty.Set(cty.Object(map[string]cty.Type{ 1239 "foo": cty.String, 1240 }))), 1241 }), 1242 Apply: true, 1243 }, 1244 { 1245 // Retain don't re-add unexpected planned values in a map 1246 Src: cty.ObjectVal(map[string]cty.Value{ 1247 "map": cty.MapVal(map[string]cty.Value{ 1248 "a": cty.StringVal("a"), 1249 "b": cty.StringVal(""), 1250 }), 1251 }), 1252 Dst: cty.ObjectVal(map[string]cty.Value{ 1253 "map": cty.MapVal(map[string]cty.Value{ 1254 "a": cty.StringVal("a"), 1255 }), 1256 }), 1257 Expect: cty.ObjectVal(map[string]cty.Value{ 1258 "map": cty.MapVal(map[string]cty.Value{ 1259 "a": cty.StringVal("a"), 1260 }), 1261 }), 1262 }, 1263 { 1264 // Remove extra values after apply 1265 Src: cty.ObjectVal(map[string]cty.Value{ 1266 "map": cty.MapVal(map[string]cty.Value{ 1267 "a": cty.StringVal("a"), 1268 "b": cty.StringVal("b"), 1269 }), 1270 }), 1271 Dst: cty.ObjectVal(map[string]cty.Value{ 1272 "map": cty.MapVal(map[string]cty.Value{ 1273 "a": cty.StringVal("a"), 1274 }), 1275 }), 1276 Expect: cty.ObjectVal(map[string]cty.Value{ 1277 "map": cty.MapVal(map[string]cty.Value{ 1278 "a": cty.StringVal("a"), 1279 }), 1280 }), 1281 Apply: true, 1282 }, 1283 { 1284 Src: cty.ObjectVal(map[string]cty.Value{ 1285 "a": cty.StringVal("a"), 1286 }), 1287 Dst: cty.EmptyObjectVal, 1288 Expect: cty.ObjectVal(map[string]cty.Value{ 1289 "a": cty.NullVal(cty.String), 1290 }), 1291 }, 1292 1293 // a list in an object in a list, going from null to empty 1294 { 1295 Src: cty.ObjectVal(map[string]cty.Value{ 1296 "network_interface": cty.ListVal([]cty.Value{ 1297 cty.ObjectVal(map[string]cty.Value{ 1298 "network_ip": cty.UnknownVal(cty.String), 1299 "access_config": cty.NullVal(cty.List(cty.Object(map[string]cty.Type{"public_ptr_domain_name": cty.String, "nat_ip": cty.String}))), 1300 "address": cty.NullVal(cty.String), 1301 "name": cty.StringVal("nic0"), 1302 })}), 1303 }), 1304 Dst: cty.ObjectVal(map[string]cty.Value{ 1305 "network_interface": cty.ListVal([]cty.Value{ 1306 cty.ObjectVal(map[string]cty.Value{ 1307 "network_ip": cty.StringVal("10.128.0.64"), 1308 "access_config": cty.ListValEmpty(cty.Object(map[string]cty.Type{"public_ptr_domain_name": cty.String, "nat_ip": cty.String})), 1309 "address": cty.StringVal("address"), 1310 "name": cty.StringVal("nic0"), 1311 }), 1312 }), 1313 }), 1314 Expect: cty.ObjectVal(map[string]cty.Value{ 1315 "network_interface": cty.ListVal([]cty.Value{ 1316 cty.ObjectVal(map[string]cty.Value{ 1317 "network_ip": cty.StringVal("10.128.0.64"), 1318 "access_config": cty.NullVal(cty.List(cty.Object(map[string]cty.Type{"public_ptr_domain_name": cty.String, "nat_ip": cty.String}))), 1319 "address": cty.StringVal("address"), 1320 "name": cty.StringVal("nic0"), 1321 }), 1322 }), 1323 }), 1324 Apply: true, 1325 }, 1326 1327 // a list in an object in a list, going from empty to null 1328 { 1329 Src: cty.ObjectVal(map[string]cty.Value{ 1330 "network_interface": cty.ListVal([]cty.Value{ 1331 cty.ObjectVal(map[string]cty.Value{ 1332 "network_ip": cty.UnknownVal(cty.String), 1333 "access_config": cty.ListValEmpty(cty.Object(map[string]cty.Type{"public_ptr_domain_name": cty.String, "nat_ip": cty.String})), 1334 "address": cty.NullVal(cty.String), 1335 "name": cty.StringVal("nic0"), 1336 })}), 1337 }), 1338 Dst: cty.ObjectVal(map[string]cty.Value{ 1339 "network_interface": cty.ListVal([]cty.Value{ 1340 cty.ObjectVal(map[string]cty.Value{ 1341 "network_ip": cty.StringVal("10.128.0.64"), 1342 "access_config": cty.NullVal(cty.List(cty.Object(map[string]cty.Type{"public_ptr_domain_name": cty.String, "nat_ip": cty.String}))), 1343 "address": cty.StringVal("address"), 1344 "name": cty.StringVal("nic0"), 1345 }), 1346 }), 1347 }), 1348 Expect: cty.ObjectVal(map[string]cty.Value{ 1349 "network_interface": cty.ListVal([]cty.Value{ 1350 cty.ObjectVal(map[string]cty.Value{ 1351 "network_ip": cty.StringVal("10.128.0.64"), 1352 "access_config": cty.ListValEmpty(cty.Object(map[string]cty.Type{"public_ptr_domain_name": cty.String, "nat_ip": cty.String})), 1353 "address": cty.StringVal("address"), 1354 "name": cty.StringVal("nic0"), 1355 }), 1356 }), 1357 }), 1358 Apply: true, 1359 }, 1360 // the empty list should be transferred, but the new unknown should not be overridden 1361 { 1362 Src: cty.ObjectVal(map[string]cty.Value{ 1363 "network_interface": cty.ListVal([]cty.Value{ 1364 cty.ObjectVal(map[string]cty.Value{ 1365 "network_ip": cty.StringVal("10.128.0.64"), 1366 "access_config": cty.ListValEmpty(cty.Object(map[string]cty.Type{"public_ptr_domain_name": cty.String, "nat_ip": cty.String})), 1367 "address": cty.NullVal(cty.String), 1368 "name": cty.StringVal("nic0"), 1369 })}), 1370 }), 1371 Dst: cty.ObjectVal(map[string]cty.Value{ 1372 "network_interface": cty.ListVal([]cty.Value{ 1373 cty.ObjectVal(map[string]cty.Value{ 1374 "network_ip": cty.UnknownVal(cty.String), 1375 "access_config": cty.NullVal(cty.List(cty.Object(map[string]cty.Type{"public_ptr_domain_name": cty.String, "nat_ip": cty.String}))), 1376 "address": cty.StringVal("address"), 1377 "name": cty.StringVal("nic0"), 1378 }), 1379 }), 1380 }), 1381 Expect: cty.ObjectVal(map[string]cty.Value{ 1382 "network_interface": cty.ListVal([]cty.Value{ 1383 cty.ObjectVal(map[string]cty.Value{ 1384 "network_ip": cty.UnknownVal(cty.String), 1385 "access_config": cty.ListValEmpty(cty.Object(map[string]cty.Type{"public_ptr_domain_name": cty.String, "nat_ip": cty.String})), 1386 "address": cty.StringVal("address"), 1387 "name": cty.StringVal("nic0"), 1388 }), 1389 }), 1390 }), 1391 }, 1392 { 1393 // fix unknowns added to a map 1394 Src: cty.ObjectVal(map[string]cty.Value{ 1395 "map": cty.MapVal(map[string]cty.Value{ 1396 "a": cty.StringVal("a"), 1397 "b": cty.StringVal(""), 1398 }), 1399 }), 1400 Dst: cty.ObjectVal(map[string]cty.Value{ 1401 "map": cty.MapVal(map[string]cty.Value{ 1402 "a": cty.StringVal("a"), 1403 "b": cty.UnknownVal(cty.String), 1404 }), 1405 }), 1406 Expect: cty.ObjectVal(map[string]cty.Value{ 1407 "map": cty.MapVal(map[string]cty.Value{ 1408 "a": cty.StringVal("a"), 1409 "b": cty.StringVal(""), 1410 }), 1411 }), 1412 }, 1413 { 1414 // fix unknowns lost from a list 1415 Src: cty.ObjectVal(map[string]cty.Value{ 1416 "top": cty.ListVal([]cty.Value{ 1417 cty.ObjectVal(map[string]cty.Value{ 1418 "list": cty.ListVal([]cty.Value{ 1419 cty.ObjectVal(map[string]cty.Value{ 1420 "values": cty.ListVal([]cty.Value{cty.UnknownVal(cty.String)}), 1421 }), 1422 }), 1423 }), 1424 }), 1425 }), 1426 Dst: cty.ObjectVal(map[string]cty.Value{ 1427 "top": cty.ListVal([]cty.Value{ 1428 cty.ObjectVal(map[string]cty.Value{ 1429 "list": cty.ListVal([]cty.Value{ 1430 cty.ObjectVal(map[string]cty.Value{ 1431 "values": cty.NullVal(cty.List(cty.String)), 1432 }), 1433 }), 1434 }), 1435 }), 1436 }), 1437 Expect: cty.ObjectVal(map[string]cty.Value{ 1438 "top": cty.ListVal([]cty.Value{ 1439 cty.ObjectVal(map[string]cty.Value{ 1440 "list": cty.ListVal([]cty.Value{ 1441 cty.ObjectVal(map[string]cty.Value{ 1442 "values": cty.ListVal([]cty.Value{cty.UnknownVal(cty.String)}), 1443 }), 1444 }), 1445 }), 1446 }), 1447 }), 1448 }, 1449 { 1450 Src: cty.ObjectVal(map[string]cty.Value{ 1451 "set": cty.NullVal(cty.Set(cty.Object(map[string]cty.Type{ 1452 "list": cty.List(cty.String), 1453 }))), 1454 }), 1455 Dst: cty.ObjectVal(map[string]cty.Value{ 1456 "set": cty.SetValEmpty(cty.Object(map[string]cty.Type{ 1457 "list": cty.List(cty.String), 1458 })), 1459 }), 1460 Expect: cty.ObjectVal(map[string]cty.Value{ 1461 "set": cty.SetValEmpty(cty.Object(map[string]cty.Type{ 1462 "list": cty.List(cty.String), 1463 })), 1464 }), 1465 }, 1466 { 1467 Src: cty.ObjectVal(map[string]cty.Value{ 1468 "set": cty.NullVal(cty.Set(cty.Object(map[string]cty.Type{ 1469 "list": cty.List(cty.String), 1470 }))), 1471 }), 1472 Dst: cty.ObjectVal(map[string]cty.Value{ 1473 "set": cty.SetValEmpty(cty.Object(map[string]cty.Type{ 1474 "list": cty.List(cty.String), 1475 })), 1476 }), 1477 Expect: cty.ObjectVal(map[string]cty.Value{ 1478 "set": cty.NullVal(cty.Set(cty.Object(map[string]cty.Type{ 1479 "list": cty.List(cty.String), 1480 }))), 1481 }), 1482 Apply: true, 1483 }, 1484 } { 1485 t.Run(fmt.Sprintf("%d", i), func(t *testing.T) { 1486 got := normalizeNullValues(tc.Dst, tc.Src, tc.Apply) 1487 if !got.RawEquals(tc.Expect) { 1488 t.Fatalf("\nexpected: %#v\ngot: %#v\n", tc.Expect, got) 1489 } 1490 }) 1491 } 1492 } 1493 1494 func TestValidateNulls(t *testing.T) { 1495 for i, tc := range []struct { 1496 Cfg cty.Value 1497 Err bool 1498 }{ 1499 { 1500 Cfg: cty.ObjectVal(map[string]cty.Value{ 1501 "list": cty.ListVal([]cty.Value{ 1502 cty.StringVal("string"), 1503 cty.NullVal(cty.String), 1504 }), 1505 }), 1506 Err: true, 1507 }, 1508 { 1509 Cfg: cty.ObjectVal(map[string]cty.Value{ 1510 "map": cty.MapVal(map[string]cty.Value{ 1511 "string": cty.StringVal("string"), 1512 "null": cty.NullVal(cty.String), 1513 }), 1514 }), 1515 Err: false, 1516 }, 1517 { 1518 Cfg: cty.ObjectVal(map[string]cty.Value{ 1519 "object": cty.ObjectVal(map[string]cty.Value{ 1520 "list": cty.ListVal([]cty.Value{ 1521 cty.StringVal("string"), 1522 cty.NullVal(cty.String), 1523 }), 1524 }), 1525 }), 1526 Err: true, 1527 }, 1528 { 1529 Cfg: cty.ObjectVal(map[string]cty.Value{ 1530 "object": cty.ObjectVal(map[string]cty.Value{ 1531 "list": cty.ListVal([]cty.Value{ 1532 cty.StringVal("string"), 1533 cty.NullVal(cty.String), 1534 }), 1535 "list2": cty.ListVal([]cty.Value{ 1536 cty.StringVal("string"), 1537 cty.NullVal(cty.String), 1538 }), 1539 }), 1540 }), 1541 Err: true, 1542 }, 1543 { 1544 Cfg: cty.ObjectVal(map[string]cty.Value{ 1545 "object": cty.ObjectVal(map[string]cty.Value{ 1546 "list": cty.SetVal([]cty.Value{ 1547 cty.StringVal("string"), 1548 cty.NullVal(cty.String), 1549 }), 1550 }), 1551 }), 1552 Err: true, 1553 }, 1554 } { 1555 t.Run(strconv.Itoa(i), func(t *testing.T) { 1556 d := validateConfigNulls(tc.Cfg, nil) 1557 diags := convert.ProtoToDiagnostics(d) 1558 switch { 1559 case tc.Err: 1560 if !diags.HasErrors() { 1561 t.Fatal("expected error") 1562 } 1563 default: 1564 if diags.HasErrors() { 1565 t.Fatalf("unexpected error: %q", diags.Err()) 1566 } 1567 } 1568 }) 1569 } 1570 }