github.com/hashicorp/terraform-plugin-sdk@v1.17.2/terraform/resource_test.go (about) 1 package terraform 2 3 import ( 4 "fmt" 5 "reflect" 6 "strconv" 7 "testing" 8 9 "github.com/hashicorp/terraform-plugin-sdk/internal/configs/configschema" 10 "github.com/zclconf/go-cty/cty" 11 12 "github.com/hashicorp/terraform-plugin-sdk/internal/configs/hcl2shim" 13 "github.com/mitchellh/reflectwalk" 14 ) 15 16 func TestInstanceInfoResourceAddress(t *testing.T) { 17 tests := []struct { 18 Input *InstanceInfo 19 Want string 20 }{ 21 { 22 &InstanceInfo{ 23 Id: "test_resource.baz", 24 }, 25 "test_resource.baz", 26 }, 27 { 28 &InstanceInfo{ 29 Id: "test_resource.baz", 30 ModulePath: rootModulePath, 31 }, 32 "test_resource.baz", 33 }, 34 { 35 &InstanceInfo{ 36 Id: "test_resource.baz", 37 ModulePath: []string{"root", "foo"}, 38 }, 39 "module.foo.test_resource.baz", 40 }, 41 { 42 &InstanceInfo{ 43 Id: "test_resource.baz", 44 ModulePath: []string{"root", "foo", "bar"}, 45 }, 46 "module.foo.module.bar.test_resource.baz", 47 }, 48 { 49 &InstanceInfo{ 50 Id: "test_resource.baz (tainted)", 51 }, 52 "test_resource.baz.tainted", 53 }, 54 { 55 &InstanceInfo{ 56 Id: "test_resource.baz (deposed #0)", 57 }, 58 "test_resource.baz.deposed", 59 }, 60 } 61 62 for i, test := range tests { 63 t.Run(strconv.Itoa(i), func(t *testing.T) { 64 gotAddr := test.Input.ResourceAddress() 65 got := gotAddr.String() 66 if got != test.Want { 67 t.Fatalf("wrong result\ngot: %s\nwant: %s", got, test.Want) 68 } 69 }) 70 } 71 } 72 73 func TestResourceConfigGet(t *testing.T) { 74 fooStringSchema := &configschema.Block{ 75 Attributes: map[string]*configschema.Attribute{ 76 "foo": {Type: cty.String, Optional: true}, 77 }, 78 } 79 fooListSchema := &configschema.Block{ 80 Attributes: map[string]*configschema.Attribute{ 81 "foo": {Type: cty.List(cty.Number), Optional: true}, 82 }, 83 } 84 85 cases := []struct { 86 Config cty.Value 87 Schema *configschema.Block 88 Key string 89 Value interface{} 90 }{ 91 { 92 Config: cty.ObjectVal(map[string]cty.Value{ 93 "foo": cty.StringVal("bar"), 94 }), 95 Schema: fooStringSchema, 96 Key: "foo", 97 Value: "bar", 98 }, 99 100 { 101 Config: cty.ObjectVal(map[string]cty.Value{ 102 "foo": cty.UnknownVal(cty.String), 103 }), 104 Schema: fooStringSchema, 105 Key: "foo", 106 Value: hcl2shim.UnknownVariableValue, 107 }, 108 109 { 110 Config: cty.ObjectVal(map[string]cty.Value{ 111 "foo": cty.ListVal([]cty.Value{ 112 cty.NumberIntVal(1), 113 cty.NumberIntVal(2), 114 cty.NumberIntVal(5), 115 }), 116 }), 117 Schema: fooListSchema, 118 Key: "foo.0", 119 Value: 1, 120 }, 121 122 { 123 Config: cty.ObjectVal(map[string]cty.Value{ 124 "foo": cty.ListVal([]cty.Value{ 125 cty.NumberIntVal(1), 126 cty.NumberIntVal(2), 127 cty.NumberIntVal(5), 128 }), 129 }), 130 Schema: fooListSchema, 131 Key: "foo.5", 132 Value: nil, 133 }, 134 135 { 136 Config: cty.ObjectVal(map[string]cty.Value{ 137 "foo": cty.ListVal([]cty.Value{ 138 cty.NumberIntVal(1), 139 cty.NumberIntVal(2), 140 cty.NumberIntVal(5), 141 }), 142 }), 143 Schema: fooListSchema, 144 Key: "foo.-1", 145 Value: nil, 146 }, 147 148 // get from map 149 { 150 Config: cty.ObjectVal(map[string]cty.Value{ 151 "mapname": cty.ListVal([]cty.Value{ 152 cty.MapVal(map[string]cty.Value{ 153 "key": cty.NumberIntVal(1), 154 }), 155 }), 156 }), 157 Schema: &configschema.Block{ 158 Attributes: map[string]*configschema.Attribute{ 159 "mapname": {Type: cty.List(cty.Map(cty.Number)), Optional: true}, 160 }, 161 }, 162 Key: "mapname.0.key", 163 Value: 1, 164 }, 165 166 // get from map with dot in key 167 { 168 Config: cty.ObjectVal(map[string]cty.Value{ 169 "mapname": cty.ListVal([]cty.Value{ 170 cty.MapVal(map[string]cty.Value{ 171 "key.name": cty.NumberIntVal(1), 172 }), 173 }), 174 }), 175 Schema: &configschema.Block{ 176 Attributes: map[string]*configschema.Attribute{ 177 "mapname": {Type: cty.List(cty.Map(cty.Number)), Optional: true}, 178 }, 179 }, 180 Key: "mapname.0.key.name", 181 Value: 1, 182 }, 183 184 // get from map with overlapping key names 185 { 186 Config: cty.ObjectVal(map[string]cty.Value{ 187 "mapname": cty.ListVal([]cty.Value{ 188 cty.MapVal(map[string]cty.Value{ 189 "key.name": cty.NumberIntVal(1), 190 "key.name.2": cty.NumberIntVal(2), 191 }), 192 }), 193 }), 194 Schema: &configschema.Block{ 195 Attributes: map[string]*configschema.Attribute{ 196 "mapname": {Type: cty.List(cty.Map(cty.Number)), Optional: true}, 197 }, 198 }, 199 Key: "mapname.0.key.name.2", 200 Value: 2, 201 }, 202 { 203 Config: cty.ObjectVal(map[string]cty.Value{ 204 "mapname": cty.ListVal([]cty.Value{ 205 cty.MapVal(map[string]cty.Value{ 206 "key.name": cty.NumberIntVal(1), 207 "key.name.foo": cty.NumberIntVal(2), 208 }), 209 }), 210 }), 211 Schema: &configschema.Block{ 212 Attributes: map[string]*configschema.Attribute{ 213 "mapname": {Type: cty.List(cty.Map(cty.Number)), Optional: true}, 214 }, 215 }, 216 Key: "mapname.0.key.name", 217 Value: 1, 218 }, 219 { 220 Config: cty.ObjectVal(map[string]cty.Value{ 221 "mapname": cty.ListVal([]cty.Value{ 222 cty.MapVal(map[string]cty.Value{ 223 "listkey": cty.ListVal([]cty.Value{ 224 cty.MapVal(map[string]cty.Value{ 225 "key": cty.NumberIntVal(3), 226 }), 227 }), 228 }), 229 }), 230 }), 231 Schema: &configschema.Block{ 232 Attributes: map[string]*configschema.Attribute{ 233 "mapname": {Type: cty.List(cty.Map(cty.List(cty.Map(cty.Number)))), Optional: true}, 234 }, 235 }, 236 Key: "mapname.0.listkey.0.key", 237 Value: 3, 238 }, 239 } 240 241 for i, tc := range cases { 242 rc := NewResourceConfigShimmed(tc.Config, tc.Schema) 243 244 // Test getting a key 245 t.Run(fmt.Sprintf("get-%d", i), func(t *testing.T) { 246 v, ok := rc.Get(tc.Key) 247 if ok && v == nil { 248 t.Fatal("(nil, true) returned from Get") 249 } 250 251 if !reflect.DeepEqual(v, tc.Value) { 252 t.Fatalf("%d bad: %#v", i, v) 253 } 254 }) 255 256 // Test copying and equality 257 t.Run(fmt.Sprintf("copy-and-equal-%d", i), func(t *testing.T) { 258 copy := rc.DeepCopy() 259 if !reflect.DeepEqual(copy, rc) { 260 t.Fatalf("bad:\n\n%#v\n\n%#v", copy, rc) 261 } 262 263 if !copy.Equal(rc) { 264 t.Fatalf("copy != rc:\n\n%#v\n\n%#v", copy, rc) 265 } 266 if !rc.Equal(copy) { 267 t.Fatalf("rc != copy:\n\n%#v\n\n%#v", copy, rc) 268 } 269 }) 270 } 271 } 272 273 func TestResourceConfigDeepCopy_nil(t *testing.T) { 274 var nilRc *ResourceConfig 275 actual := nilRc.DeepCopy() 276 if actual != nil { 277 t.Fatalf("bad: %#v", actual) 278 } 279 } 280 281 func TestResourceConfigDeepCopy_nilComputed(t *testing.T) { 282 rc := &ResourceConfig{} 283 actual := rc.DeepCopy() 284 if actual.ComputedKeys != nil { 285 t.Fatalf("bad: %#v", actual) 286 } 287 } 288 289 func TestResourceConfigEqual_nil(t *testing.T) { 290 var nilRc *ResourceConfig 291 notNil := NewResourceConfigShimmed(cty.EmptyObjectVal, &configschema.Block{}) 292 293 if nilRc.Equal(notNil) { 294 t.Fatal("should not be equal") 295 } 296 297 if notNil.Equal(nilRc) { 298 t.Fatal("should not be equal") 299 } 300 } 301 302 func TestResourceConfigEqual_computedKeyOrder(t *testing.T) { 303 v := cty.ObjectVal(map[string]cty.Value{ 304 "foo": cty.UnknownVal(cty.String), 305 }) 306 schema := &configschema.Block{ 307 Attributes: map[string]*configschema.Attribute{ 308 "foo": {Type: cty.String, Optional: true}, 309 }, 310 } 311 rc := NewResourceConfigShimmed(v, schema) 312 rc2 := NewResourceConfigShimmed(v, schema) 313 314 // Set the computed keys manually to force ordering to differ 315 rc.ComputedKeys = []string{"foo", "bar"} 316 rc2.ComputedKeys = []string{"bar", "foo"} 317 318 if !rc.Equal(rc2) { 319 t.Fatal("should be equal") 320 } 321 } 322 323 func TestUnknownCheckWalker(t *testing.T) { 324 cases := []struct { 325 Name string 326 Input interface{} 327 Result bool 328 }{ 329 { 330 "primitive", 331 42, 332 false, 333 }, 334 335 { 336 "primitive computed", 337 hcl2shim.UnknownVariableValue, 338 true, 339 }, 340 341 { 342 "list", 343 []interface{}{"foo", hcl2shim.UnknownVariableValue}, 344 true, 345 }, 346 347 { 348 "nested list", 349 []interface{}{ 350 "foo", 351 []interface{}{hcl2shim.UnknownVariableValue}, 352 }, 353 true, 354 }, 355 } 356 357 for i, tc := range cases { 358 t.Run(fmt.Sprintf("%d-%s", i, tc.Name), func(t *testing.T) { 359 var w unknownCheckWalker 360 if err := reflectwalk.Walk(tc.Input, &w); err != nil { 361 t.Fatalf("err: %s", err) 362 } 363 364 if w.Unknown != tc.Result { 365 t.Fatalf("bad: %v", w.Unknown) 366 } 367 }) 368 } 369 } 370 371 func TestNewResourceConfigShimmed(t *testing.T) { 372 for _, tc := range []struct { 373 Name string 374 Val cty.Value 375 Schema *configschema.Block 376 Expected *ResourceConfig 377 }{ 378 { 379 Name: "empty object", 380 Val: cty.NullVal(cty.EmptyObject), 381 Schema: &configschema.Block{ 382 Attributes: map[string]*configschema.Attribute{ 383 "foo": { 384 Type: cty.String, 385 Optional: true, 386 }, 387 }, 388 }, 389 Expected: &ResourceConfig{ 390 Raw: map[string]interface{}{}, 391 Config: map[string]interface{}{}, 392 }, 393 }, 394 { 395 Name: "basic", 396 Val: cty.ObjectVal(map[string]cty.Value{ 397 "foo": cty.StringVal("bar"), 398 }), 399 Schema: &configschema.Block{ 400 Attributes: map[string]*configschema.Attribute{ 401 "foo": { 402 Type: cty.String, 403 Optional: true, 404 }, 405 }, 406 }, 407 Expected: &ResourceConfig{ 408 Raw: map[string]interface{}{ 409 "foo": "bar", 410 }, 411 Config: map[string]interface{}{ 412 "foo": "bar", 413 }, 414 }, 415 }, 416 { 417 Name: "null string", 418 Val: cty.ObjectVal(map[string]cty.Value{ 419 "foo": cty.NullVal(cty.String), 420 }), 421 Schema: &configschema.Block{ 422 Attributes: map[string]*configschema.Attribute{ 423 "foo": { 424 Type: cty.String, 425 Optional: true, 426 }, 427 }, 428 }, 429 Expected: &ResourceConfig{ 430 Raw: map[string]interface{}{}, 431 Config: map[string]interface{}{}, 432 }, 433 }, 434 { 435 Name: "unknown string", 436 Val: cty.ObjectVal(map[string]cty.Value{ 437 "foo": cty.UnknownVal(cty.String), 438 }), 439 Schema: &configschema.Block{ 440 Attributes: map[string]*configschema.Attribute{ 441 "foo": { 442 Type: cty.String, 443 Optional: true, 444 }, 445 }, 446 }, 447 Expected: &ResourceConfig{ 448 ComputedKeys: []string{"foo"}, 449 Raw: map[string]interface{}{ 450 "foo": hcl2shim.UnknownVariableValue, 451 }, 452 Config: map[string]interface{}{ 453 "foo": hcl2shim.UnknownVariableValue, 454 }, 455 }, 456 }, 457 { 458 Name: "unknown collections", 459 Val: cty.ObjectVal(map[string]cty.Value{ 460 "bar": cty.UnknownVal(cty.Map(cty.String)), 461 "baz": cty.UnknownVal(cty.List(cty.String)), 462 }), 463 Schema: &configschema.Block{ 464 Attributes: map[string]*configschema.Attribute{ 465 "bar": { 466 Type: cty.Map(cty.String), 467 Required: true, 468 }, 469 "baz": { 470 Type: cty.List(cty.String), 471 Optional: true, 472 }, 473 }, 474 }, 475 Expected: &ResourceConfig{ 476 ComputedKeys: []string{"bar", "baz"}, 477 Raw: map[string]interface{}{ 478 "bar": hcl2shim.UnknownVariableValue, 479 "baz": hcl2shim.UnknownVariableValue, 480 }, 481 Config: map[string]interface{}{ 482 "bar": hcl2shim.UnknownVariableValue, 483 "baz": hcl2shim.UnknownVariableValue, 484 }, 485 }, 486 }, 487 { 488 Name: "null collections", 489 Val: cty.ObjectVal(map[string]cty.Value{ 490 "bar": cty.NullVal(cty.Map(cty.String)), 491 "baz": cty.NullVal(cty.List(cty.String)), 492 }), 493 Schema: &configschema.Block{ 494 Attributes: map[string]*configschema.Attribute{ 495 "bar": { 496 Type: cty.Map(cty.String), 497 Required: true, 498 }, 499 "baz": { 500 Type: cty.List(cty.String), 501 Optional: true, 502 }, 503 }, 504 }, 505 Expected: &ResourceConfig{ 506 Raw: map[string]interface{}{}, 507 Config: map[string]interface{}{}, 508 }, 509 }, 510 { 511 Name: "unknown blocks", 512 Val: cty.ObjectVal(map[string]cty.Value{ 513 "bar": cty.UnknownVal(cty.Map(cty.String)), 514 "baz": cty.UnknownVal(cty.List(cty.String)), 515 }), 516 Schema: &configschema.Block{ 517 BlockTypes: map[string]*configschema.NestedBlock{ 518 "bar": { 519 Block: configschema.Block{}, 520 Nesting: configschema.NestingList, 521 }, 522 "baz": { 523 Block: configschema.Block{}, 524 Nesting: configschema.NestingSet, 525 }, 526 }, 527 }, 528 Expected: &ResourceConfig{ 529 ComputedKeys: []string{"bar", "baz"}, 530 Raw: map[string]interface{}{ 531 "bar": hcl2shim.UnknownVariableValue, 532 "baz": hcl2shim.UnknownVariableValue, 533 }, 534 Config: map[string]interface{}{ 535 "bar": hcl2shim.UnknownVariableValue, 536 "baz": hcl2shim.UnknownVariableValue, 537 }, 538 }, 539 }, 540 { 541 Name: "unknown in nested blocks", 542 Val: cty.ObjectVal(map[string]cty.Value{ 543 "bar": cty.ListVal([]cty.Value{ 544 cty.ObjectVal(map[string]cty.Value{ 545 "baz": cty.ListVal([]cty.Value{ 546 cty.ObjectVal(map[string]cty.Value{ 547 "list": cty.UnknownVal(cty.List(cty.String)), 548 }), 549 }), 550 }), 551 }), 552 }), 553 Schema: &configschema.Block{ 554 BlockTypes: map[string]*configschema.NestedBlock{ 555 "bar": { 556 Block: configschema.Block{ 557 BlockTypes: map[string]*configschema.NestedBlock{ 558 "baz": { 559 Block: configschema.Block{ 560 Attributes: map[string]*configschema.Attribute{ 561 "list": {Type: cty.List(cty.String), 562 Optional: true, 563 }, 564 }, 565 }, 566 Nesting: configschema.NestingList, 567 }, 568 }, 569 }, 570 Nesting: configschema.NestingList, 571 }, 572 }, 573 }, 574 Expected: &ResourceConfig{ 575 ComputedKeys: []string{"bar.0.baz.0.list"}, 576 Raw: map[string]interface{}{ 577 "bar": []interface{}{map[string]interface{}{ 578 "baz": []interface{}{map[string]interface{}{ 579 "list": "74D93920-ED26-11E3-AC10-0800200C9A66", 580 }}, 581 }}, 582 }, 583 Config: map[string]interface{}{ 584 "bar": []interface{}{map[string]interface{}{ 585 "baz": []interface{}{map[string]interface{}{ 586 "list": "74D93920-ED26-11E3-AC10-0800200C9A66", 587 }}, 588 }}, 589 }, 590 }, 591 }, 592 { 593 Name: "unknown in set", 594 Val: cty.ObjectVal(map[string]cty.Value{ 595 "bar": cty.SetVal([]cty.Value{ 596 cty.ObjectVal(map[string]cty.Value{ 597 "val": cty.UnknownVal(cty.String), 598 }), 599 }), 600 }), 601 Schema: &configschema.Block{ 602 BlockTypes: map[string]*configschema.NestedBlock{ 603 "bar": { 604 Block: configschema.Block{ 605 Attributes: map[string]*configschema.Attribute{ 606 "val": { 607 Type: cty.String, 608 Optional: true, 609 }, 610 }, 611 }, 612 Nesting: configschema.NestingSet, 613 }, 614 }, 615 }, 616 Expected: &ResourceConfig{ 617 ComputedKeys: []string{"bar.0.val"}, 618 Raw: map[string]interface{}{ 619 "bar": []interface{}{map[string]interface{}{ 620 "val": "74D93920-ED26-11E3-AC10-0800200C9A66", 621 }}, 622 }, 623 Config: map[string]interface{}{ 624 "bar": []interface{}{map[string]interface{}{ 625 "val": "74D93920-ED26-11E3-AC10-0800200C9A66", 626 }}, 627 }, 628 }, 629 }, 630 { 631 Name: "unknown in attribute sets", 632 Val: cty.ObjectVal(map[string]cty.Value{ 633 "bar": cty.SetVal([]cty.Value{ 634 cty.ObjectVal(map[string]cty.Value{ 635 "val": cty.UnknownVal(cty.String), 636 }), 637 }), 638 "baz": cty.SetVal([]cty.Value{ 639 cty.ObjectVal(map[string]cty.Value{ 640 "obj": cty.UnknownVal(cty.Object(map[string]cty.Type{ 641 "attr": cty.List(cty.String), 642 })), 643 }), 644 cty.ObjectVal(map[string]cty.Value{ 645 "obj": cty.ObjectVal(map[string]cty.Value{ 646 "attr": cty.UnknownVal(cty.List(cty.String)), 647 }), 648 }), 649 }), 650 }), 651 Schema: &configschema.Block{ 652 Attributes: map[string]*configschema.Attribute{ 653 "bar": { 654 Type: cty.Set(cty.Object(map[string]cty.Type{ 655 "val": cty.String, 656 })), 657 }, 658 "baz": { 659 Type: cty.Set(cty.Object(map[string]cty.Type{ 660 "obj": cty.Object(map[string]cty.Type{ 661 "attr": cty.List(cty.String), 662 }), 663 })), 664 }, 665 }, 666 }, 667 Expected: &ResourceConfig{ 668 ComputedKeys: []string{"bar.0.val", "baz.0.obj.attr", "baz.1.obj"}, 669 Raw: map[string]interface{}{ 670 "bar": []interface{}{map[string]interface{}{ 671 "val": "74D93920-ED26-11E3-AC10-0800200C9A66", 672 }}, 673 "baz": []interface{}{ 674 map[string]interface{}{ 675 "obj": map[string]interface{}{ 676 "attr": "74D93920-ED26-11E3-AC10-0800200C9A66", 677 }, 678 }, 679 map[string]interface{}{ 680 "obj": "74D93920-ED26-11E3-AC10-0800200C9A66", 681 }, 682 }, 683 }, 684 Config: map[string]interface{}{ 685 "bar": []interface{}{map[string]interface{}{ 686 "val": "74D93920-ED26-11E3-AC10-0800200C9A66", 687 }}, 688 "baz": []interface{}{ 689 map[string]interface{}{ 690 "obj": map[string]interface{}{ 691 "attr": "74D93920-ED26-11E3-AC10-0800200C9A66", 692 }, 693 }, 694 map[string]interface{}{ 695 "obj": "74D93920-ED26-11E3-AC10-0800200C9A66", 696 }, 697 }, 698 }, 699 }, 700 }, 701 { 702 Name: "null blocks", 703 Val: cty.ObjectVal(map[string]cty.Value{ 704 "bar": cty.NullVal(cty.Map(cty.String)), 705 "baz": cty.NullVal(cty.List(cty.String)), 706 }), 707 Schema: &configschema.Block{ 708 BlockTypes: map[string]*configschema.NestedBlock{ 709 "bar": { 710 Block: configschema.Block{}, 711 Nesting: configschema.NestingMap, 712 }, 713 "baz": { 714 Block: configschema.Block{}, 715 Nesting: configschema.NestingSingle, 716 }, 717 }, 718 }, 719 Expected: &ResourceConfig{ 720 Raw: map[string]interface{}{}, 721 Config: map[string]interface{}{}, 722 }, 723 }, 724 } { 725 t.Run(tc.Name, func(*testing.T) { 726 cfg := NewResourceConfigShimmed(tc.Val, tc.Schema) 727 if !tc.Expected.Equal(cfg) { 728 t.Fatalf("expected:\n%#v\ngot:\n%#v", tc.Expected, cfg) 729 } 730 }) 731 } 732 }