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