github.com/graywolf-at-work-2/terraform-vendor@v1.4.5/internal/plans/objchange/compatible_test.go (about) 1 package objchange 2 3 import ( 4 "fmt" 5 "testing" 6 7 "github.com/apparentlymart/go-dump/dump" 8 "github.com/zclconf/go-cty/cty" 9 10 "github.com/hashicorp/terraform/internal/configs/configschema" 11 "github.com/hashicorp/terraform/internal/lang/marks" 12 "github.com/hashicorp/terraform/internal/tfdiags" 13 ) 14 15 func TestAssertObjectCompatible(t *testing.T) { 16 schemaWithFoo := configschema.Block{ 17 Attributes: map[string]*configschema.Attribute{ 18 "foo": {Type: cty.String, Optional: true}, 19 }, 20 } 21 fooBlockValue := cty.ObjectVal(map[string]cty.Value{ 22 "foo": cty.StringVal("bar"), 23 }) 24 schemaWithFooBar := configschema.Block{ 25 Attributes: map[string]*configschema.Attribute{ 26 "foo": {Type: cty.String, Optional: true}, 27 "bar": {Type: cty.String, Optional: true}, 28 }, 29 } 30 fooBarBlockValue := cty.ObjectVal(map[string]cty.Value{ 31 "foo": cty.StringVal("bar"), 32 "bar": cty.NullVal(cty.String), // simulating the situation where bar isn't set in the config at all 33 }) 34 35 tests := []struct { 36 Schema *configschema.Block 37 Planned cty.Value 38 Actual cty.Value 39 WantErrs []string 40 }{ 41 { 42 &configschema.Block{}, 43 cty.EmptyObjectVal, 44 cty.EmptyObjectVal, 45 nil, 46 }, 47 { 48 &configschema.Block{ 49 Attributes: map[string]*configschema.Attribute{ 50 "id": { 51 Type: cty.String, 52 Computed: true, 53 }, 54 "name": { 55 Type: cty.String, 56 Required: true, 57 }, 58 }, 59 }, 60 cty.ObjectVal(map[string]cty.Value{ 61 "id": cty.UnknownVal(cty.String), 62 "name": cty.StringVal("thingy"), 63 }), 64 cty.ObjectVal(map[string]cty.Value{ 65 "id": cty.UnknownVal(cty.String), 66 "name": cty.StringVal("thingy"), 67 }), 68 nil, 69 }, 70 { 71 &configschema.Block{ 72 Attributes: map[string]*configschema.Attribute{ 73 "id": { 74 Type: cty.String, 75 Computed: true, 76 }, 77 "name": { 78 Type: cty.String, 79 Required: true, 80 }, 81 }, 82 }, 83 cty.ObjectVal(map[string]cty.Value{ 84 "id": cty.UnknownVal(cty.String), 85 "name": cty.UnknownVal(cty.String), 86 }), 87 cty.ObjectVal(map[string]cty.Value{ 88 "id": cty.UnknownVal(cty.String), 89 "name": cty.StringVal("thingy"), 90 }), 91 nil, 92 }, 93 { 94 &configschema.Block{ 95 Attributes: map[string]*configschema.Attribute{ 96 "id": { 97 Type: cty.String, 98 Computed: true, 99 }, 100 "name": { 101 Type: cty.String, 102 Required: true, 103 }, 104 }, 105 }, 106 cty.ObjectVal(map[string]cty.Value{ 107 "id": cty.UnknownVal(cty.String), 108 "name": cty.StringVal("wotsit"), 109 }), 110 cty.ObjectVal(map[string]cty.Value{ 111 "id": cty.UnknownVal(cty.String), 112 "name": cty.StringVal("thingy"), 113 }), 114 []string{ 115 `.name: was cty.StringVal("wotsit"), but now cty.StringVal("thingy")`, 116 }, 117 }, 118 { 119 &configschema.Block{ 120 Attributes: map[string]*configschema.Attribute{ 121 "id": { 122 Type: cty.String, 123 Computed: true, 124 }, 125 "name": { 126 Type: cty.String, 127 Required: true, 128 Sensitive: true, 129 }, 130 }, 131 }, 132 cty.ObjectVal(map[string]cty.Value{ 133 "id": cty.UnknownVal(cty.String), 134 "name": cty.StringVal("wotsit"), 135 }), 136 cty.ObjectVal(map[string]cty.Value{ 137 "id": cty.UnknownVal(cty.String), 138 "name": cty.StringVal("thingy"), 139 }), 140 []string{ 141 `.name: inconsistent values for sensitive attribute`, 142 }, 143 }, 144 { 145 &configschema.Block{ 146 Attributes: map[string]*configschema.Attribute{ 147 "id": { 148 Type: cty.String, 149 Computed: true, 150 }, 151 "name": { 152 Type: cty.String, 153 Required: true, 154 }, 155 }, 156 }, 157 cty.ObjectVal(map[string]cty.Value{ 158 "id": cty.UnknownVal(cty.String), 159 "name": cty.StringVal("wotsit").Mark(marks.Sensitive), 160 }), 161 cty.ObjectVal(map[string]cty.Value{ 162 "id": cty.UnknownVal(cty.String), 163 "name": cty.StringVal("thingy"), 164 }), 165 []string{ 166 `.name: inconsistent values for sensitive attribute`, 167 }, 168 }, 169 { 170 &configschema.Block{ 171 Attributes: map[string]*configschema.Attribute{ 172 "id": { 173 Type: cty.String, 174 Computed: true, 175 }, 176 "name": { 177 Type: cty.String, 178 Required: true, 179 }, 180 }, 181 }, 182 cty.ObjectVal(map[string]cty.Value{ 183 "id": cty.UnknownVal(cty.String), 184 "name": cty.StringVal("wotsit"), 185 }), 186 cty.ObjectVal(map[string]cty.Value{ 187 "id": cty.UnknownVal(cty.String), 188 "name": cty.StringVal("thingy").Mark(marks.Sensitive), 189 }), 190 []string{ 191 `.name: inconsistent values for sensitive attribute`, 192 }, 193 }, 194 { 195 // This tests the codepath that leads to couldHaveUnknownBlockPlaceholder, 196 // where a set may be sensitive and need to be unmarked before it 197 // is iterated upon 198 &configschema.Block{ 199 BlockTypes: map[string]*configschema.NestedBlock{ 200 "configuration": { 201 Nesting: configschema.NestingList, 202 Block: configschema.Block{ 203 BlockTypes: map[string]*configschema.NestedBlock{ 204 "sensitive_fields": { 205 Nesting: configschema.NestingSet, 206 Block: schemaWithFoo, 207 }, 208 }, 209 }, 210 }, 211 }, 212 }, 213 cty.ObjectVal(map[string]cty.Value{ 214 "configuration": cty.TupleVal([]cty.Value{ 215 cty.ObjectVal(map[string]cty.Value{ 216 "sensitive_fields": cty.SetVal([]cty.Value{ 217 cty.ObjectVal(map[string]cty.Value{ 218 "foo": cty.StringVal("secret"), 219 }), 220 }).Mark(marks.Sensitive), 221 }), 222 }), 223 }), 224 cty.ObjectVal(map[string]cty.Value{ 225 "configuration": cty.TupleVal([]cty.Value{ 226 cty.ObjectVal(map[string]cty.Value{ 227 "sensitive_fields": cty.SetVal([]cty.Value{ 228 cty.ObjectVal(map[string]cty.Value{ 229 "foo": cty.StringVal("secret"), 230 }), 231 }).Mark(marks.Sensitive), 232 }), 233 }), 234 }), 235 nil, 236 }, 237 { 238 &configschema.Block{ 239 Attributes: map[string]*configschema.Attribute{ 240 "id": { 241 Type: cty.String, 242 Computed: true, 243 }, 244 "stuff": { 245 Type: cty.DynamicPseudoType, 246 Required: true, 247 }, 248 }, 249 }, 250 cty.ObjectVal(map[string]cty.Value{ 251 "id": cty.UnknownVal(cty.String), 252 "stuff": cty.DynamicVal, 253 }), 254 cty.ObjectVal(map[string]cty.Value{ 255 "id": cty.UnknownVal(cty.String), 256 "stuff": cty.StringVal("thingy"), 257 }), 258 []string{}, 259 }, 260 { 261 &configschema.Block{ 262 Attributes: map[string]*configschema.Attribute{ 263 "obj": { 264 Type: cty.Object(map[string]cty.Type{ 265 "stuff": cty.DynamicPseudoType, 266 }), 267 }, 268 }, 269 }, 270 cty.ObjectVal(map[string]cty.Value{ 271 "obj": cty.ObjectVal(map[string]cty.Value{ 272 "stuff": cty.DynamicVal, 273 }), 274 }), 275 cty.ObjectVal(map[string]cty.Value{ 276 "obj": cty.ObjectVal(map[string]cty.Value{ 277 "stuff": cty.NumberIntVal(3), 278 }), 279 }), 280 []string{}, 281 }, 282 { 283 &configschema.Block{ 284 Attributes: map[string]*configschema.Attribute{ 285 "id": { 286 Type: cty.String, 287 Computed: true, 288 }, 289 "stuff": { 290 Type: cty.DynamicPseudoType, 291 Required: true, 292 }, 293 }, 294 }, 295 cty.ObjectVal(map[string]cty.Value{ 296 "id": cty.UnknownVal(cty.String), 297 "stuff": cty.StringVal("wotsit"), 298 }), 299 cty.ObjectVal(map[string]cty.Value{ 300 "id": cty.UnknownVal(cty.String), 301 "stuff": cty.StringVal("thingy"), 302 }), 303 []string{ 304 `.stuff: was cty.StringVal("wotsit"), but now cty.StringVal("thingy")`, 305 }, 306 }, 307 { 308 &configschema.Block{ 309 Attributes: map[string]*configschema.Attribute{ 310 "id": { 311 Type: cty.String, 312 Computed: true, 313 }, 314 "stuff": { 315 Type: cty.DynamicPseudoType, 316 Required: true, 317 }, 318 }, 319 }, 320 cty.ObjectVal(map[string]cty.Value{ 321 "id": cty.UnknownVal(cty.String), 322 "stuff": cty.StringVal("true"), 323 }), 324 cty.ObjectVal(map[string]cty.Value{ 325 "id": cty.UnknownVal(cty.String), 326 "stuff": cty.True, 327 }), 328 []string{ 329 `.stuff: wrong final value type: string required`, 330 }, 331 }, 332 { 333 &configschema.Block{ 334 Attributes: map[string]*configschema.Attribute{ 335 "id": { 336 Type: cty.String, 337 Computed: true, 338 }, 339 "stuff": { 340 Type: cty.DynamicPseudoType, 341 Required: true, 342 }, 343 }, 344 }, 345 cty.ObjectVal(map[string]cty.Value{ 346 "id": cty.UnknownVal(cty.String), 347 "stuff": cty.DynamicVal, 348 }), 349 cty.ObjectVal(map[string]cty.Value{ 350 "id": cty.UnknownVal(cty.String), 351 "stuff": cty.EmptyObjectVal, 352 }), 353 nil, 354 }, 355 { 356 &configschema.Block{ 357 Attributes: map[string]*configschema.Attribute{ 358 "id": { 359 Type: cty.String, 360 Computed: true, 361 }, 362 "stuff": { 363 Type: cty.DynamicPseudoType, 364 Required: true, 365 }, 366 }, 367 }, 368 cty.ObjectVal(map[string]cty.Value{ 369 "id": cty.UnknownVal(cty.String), 370 "stuff": cty.ObjectVal(map[string]cty.Value{ 371 "nonsense": cty.StringVal("yup"), 372 }), 373 }), 374 cty.ObjectVal(map[string]cty.Value{ 375 "id": cty.UnknownVal(cty.String), 376 "stuff": cty.EmptyObjectVal, 377 }), 378 []string{ 379 `.stuff: wrong final value type: attribute "nonsense" is required`, 380 }, 381 }, 382 { 383 &configschema.Block{ 384 Attributes: map[string]*configschema.Attribute{ 385 "id": { 386 Type: cty.String, 387 Computed: true, 388 }, 389 "tags": { 390 Type: cty.Map(cty.String), 391 Optional: true, 392 }, 393 }, 394 }, 395 cty.ObjectVal(map[string]cty.Value{ 396 "id": cty.UnknownVal(cty.String), 397 "tags": cty.MapVal(map[string]cty.Value{ 398 "Name": cty.StringVal("thingy"), 399 }), 400 }), 401 cty.ObjectVal(map[string]cty.Value{ 402 "id": cty.UnknownVal(cty.String), 403 "tags": cty.MapVal(map[string]cty.Value{ 404 "Name": cty.StringVal("thingy"), 405 }), 406 }), 407 nil, 408 }, 409 { 410 &configschema.Block{ 411 Attributes: map[string]*configschema.Attribute{ 412 "id": { 413 Type: cty.String, 414 Computed: true, 415 }, 416 "tags": { 417 Type: cty.Map(cty.String), 418 Optional: true, 419 }, 420 }, 421 }, 422 cty.ObjectVal(map[string]cty.Value{ 423 "id": cty.UnknownVal(cty.String), 424 "tags": cty.MapVal(map[string]cty.Value{ 425 "Name": cty.UnknownVal(cty.String), 426 }), 427 }), 428 cty.ObjectVal(map[string]cty.Value{ 429 "id": cty.UnknownVal(cty.String), 430 "tags": cty.MapVal(map[string]cty.Value{ 431 "Name": cty.StringVal("thingy"), 432 }), 433 }), 434 nil, 435 }, 436 { 437 &configschema.Block{ 438 Attributes: map[string]*configschema.Attribute{ 439 "id": { 440 Type: cty.String, 441 Computed: true, 442 }, 443 "tags": { 444 Type: cty.Map(cty.String), 445 Optional: true, 446 }, 447 }, 448 }, 449 cty.ObjectVal(map[string]cty.Value{ 450 "id": cty.UnknownVal(cty.String), 451 "tags": cty.MapVal(map[string]cty.Value{ 452 "Name": cty.StringVal("wotsit"), 453 }), 454 }), 455 cty.ObjectVal(map[string]cty.Value{ 456 "id": cty.UnknownVal(cty.String), 457 "tags": cty.MapVal(map[string]cty.Value{ 458 "Name": cty.StringVal("thingy"), 459 }), 460 }), 461 []string{ 462 `.tags["Name"]: was cty.StringVal("wotsit"), but now cty.StringVal("thingy")`, 463 }, 464 }, 465 { 466 &configschema.Block{ 467 Attributes: map[string]*configschema.Attribute{ 468 "id": { 469 Type: cty.String, 470 Computed: true, 471 }, 472 "tags": { 473 Type: cty.Map(cty.String), 474 Optional: true, 475 }, 476 }, 477 }, 478 cty.ObjectVal(map[string]cty.Value{ 479 "id": cty.UnknownVal(cty.String), 480 "tags": cty.MapVal(map[string]cty.Value{ 481 "Name": cty.StringVal("thingy"), 482 }), 483 }), 484 cty.ObjectVal(map[string]cty.Value{ 485 "id": cty.UnknownVal(cty.String), 486 "tags": cty.MapVal(map[string]cty.Value{ 487 "Name": cty.StringVal("thingy"), 488 "Env": cty.StringVal("production"), 489 }), 490 }), 491 []string{ 492 `.tags: new element "Env" has appeared`, 493 }, 494 }, 495 { 496 &configschema.Block{ 497 Attributes: map[string]*configschema.Attribute{ 498 "id": { 499 Type: cty.String, 500 Computed: true, 501 }, 502 "tags": { 503 Type: cty.Map(cty.String), 504 Optional: true, 505 }, 506 }, 507 }, 508 cty.ObjectVal(map[string]cty.Value{ 509 "id": cty.UnknownVal(cty.String), 510 "tags": cty.MapVal(map[string]cty.Value{ 511 "Name": cty.StringVal("thingy"), 512 }), 513 }), 514 cty.ObjectVal(map[string]cty.Value{ 515 "id": cty.UnknownVal(cty.String), 516 "tags": cty.MapValEmpty(cty.String), 517 }), 518 []string{ 519 `.tags: element "Name" has vanished`, 520 }, 521 }, 522 { 523 &configschema.Block{ 524 Attributes: map[string]*configschema.Attribute{ 525 "id": { 526 Type: cty.String, 527 Computed: true, 528 }, 529 "tags": { 530 Type: cty.Map(cty.String), 531 Optional: true, 532 }, 533 }, 534 }, 535 cty.ObjectVal(map[string]cty.Value{ 536 "id": cty.UnknownVal(cty.String), 537 "tags": cty.MapVal(map[string]cty.Value{ 538 "Name": cty.UnknownVal(cty.String), 539 }), 540 }), 541 cty.ObjectVal(map[string]cty.Value{ 542 "id": cty.UnknownVal(cty.String), 543 "tags": cty.MapVal(map[string]cty.Value{ 544 "Name": cty.NullVal(cty.String), 545 }), 546 }), 547 nil, 548 }, 549 { 550 &configschema.Block{ 551 Attributes: map[string]*configschema.Attribute{ 552 "id": { 553 Type: cty.String, 554 Computed: true, 555 }, 556 "zones": { 557 Type: cty.Set(cty.String), 558 Optional: true, 559 }, 560 }, 561 }, 562 cty.ObjectVal(map[string]cty.Value{ 563 "id": cty.UnknownVal(cty.String), 564 "zones": cty.SetVal([]cty.Value{ 565 cty.StringVal("thingy"), 566 }), 567 }), 568 cty.ObjectVal(map[string]cty.Value{ 569 "id": cty.UnknownVal(cty.String), 570 "zones": cty.SetVal([]cty.Value{ 571 cty.StringVal("thingy"), 572 }), 573 }), 574 nil, 575 }, 576 { 577 &configschema.Block{ 578 Attributes: map[string]*configschema.Attribute{ 579 "id": { 580 Type: cty.String, 581 Computed: true, 582 }, 583 "zones": { 584 Type: cty.Set(cty.String), 585 Optional: true, 586 }, 587 }, 588 }, 589 cty.ObjectVal(map[string]cty.Value{ 590 "id": cty.UnknownVal(cty.String), 591 "zones": cty.SetVal([]cty.Value{ 592 cty.StringVal("thingy"), 593 }), 594 }), 595 cty.ObjectVal(map[string]cty.Value{ 596 "id": cty.UnknownVal(cty.String), 597 "zones": cty.SetVal([]cty.Value{ 598 cty.StringVal("thingy"), 599 cty.StringVal("wotsit"), 600 }), 601 }), 602 []string{ 603 `.zones: actual set element cty.StringVal("wotsit") does not correlate with any element in plan`, 604 `.zones: length changed from 1 to 2`, 605 }, 606 }, 607 { 608 &configschema.Block{ 609 Attributes: map[string]*configschema.Attribute{ 610 "id": { 611 Type: cty.String, 612 Computed: true, 613 }, 614 "zones": { 615 Type: cty.Set(cty.String), 616 Optional: true, 617 }, 618 }, 619 }, 620 cty.ObjectVal(map[string]cty.Value{ 621 "id": cty.UnknownVal(cty.String), 622 "zones": cty.SetVal([]cty.Value{ 623 cty.UnknownVal(cty.String), 624 cty.UnknownVal(cty.String), 625 }), 626 }), 627 cty.ObjectVal(map[string]cty.Value{ 628 "id": cty.UnknownVal(cty.String), 629 "zones": cty.SetVal([]cty.Value{ 630 // Imagine that both of our unknown values ultimately resolved to "thingy", 631 // causing them to collapse into a single element. That's valid, 632 // even though it's also a little confusing and counter-intuitive. 633 cty.StringVal("thingy"), 634 }), 635 }), 636 nil, 637 }, 638 { 639 &configschema.Block{ 640 Attributes: map[string]*configschema.Attribute{ 641 "id": { 642 Type: cty.String, 643 Computed: true, 644 }, 645 "names": { 646 Type: cty.List(cty.String), 647 Optional: true, 648 }, 649 }, 650 }, 651 cty.ObjectVal(map[string]cty.Value{ 652 "id": cty.UnknownVal(cty.String), 653 "names": cty.ListVal([]cty.Value{ 654 cty.StringVal("thingy"), 655 }), 656 }), 657 cty.ObjectVal(map[string]cty.Value{ 658 "id": cty.UnknownVal(cty.String), 659 "names": cty.ListVal([]cty.Value{ 660 cty.StringVal("thingy"), 661 }), 662 }), 663 nil, 664 }, 665 { 666 &configschema.Block{ 667 Attributes: map[string]*configschema.Attribute{ 668 "id": { 669 Type: cty.String, 670 Computed: true, 671 }, 672 "names": { 673 Type: cty.List(cty.String), 674 Optional: true, 675 }, 676 }, 677 }, 678 cty.ObjectVal(map[string]cty.Value{ 679 "id": cty.UnknownVal(cty.String), 680 "names": cty.UnknownVal(cty.List(cty.String)), 681 }), 682 cty.ObjectVal(map[string]cty.Value{ 683 "id": cty.UnknownVal(cty.String), 684 "names": cty.ListVal([]cty.Value{ 685 cty.StringVal("thingy"), 686 }), 687 }), 688 nil, 689 }, 690 { 691 &configschema.Block{ 692 Attributes: map[string]*configschema.Attribute{ 693 "id": { 694 Type: cty.String, 695 Computed: true, 696 }, 697 "names": { 698 Type: cty.List(cty.String), 699 Optional: true, 700 }, 701 }, 702 }, 703 cty.ObjectVal(map[string]cty.Value{ 704 "id": cty.UnknownVal(cty.String), 705 "names": cty.ListVal([]cty.Value{ 706 cty.UnknownVal(cty.String), 707 }), 708 }), 709 cty.ObjectVal(map[string]cty.Value{ 710 "id": cty.UnknownVal(cty.String), 711 "names": cty.ListVal([]cty.Value{ 712 cty.StringVal("thingy"), 713 }), 714 }), 715 nil, 716 }, 717 { 718 &configschema.Block{ 719 Attributes: map[string]*configschema.Attribute{ 720 "id": { 721 Type: cty.String, 722 Computed: true, 723 }, 724 "names": { 725 Type: cty.List(cty.String), 726 Optional: true, 727 }, 728 }, 729 }, 730 cty.ObjectVal(map[string]cty.Value{ 731 "id": cty.UnknownVal(cty.String), 732 "names": cty.ListVal([]cty.Value{ 733 cty.StringVal("thingy"), 734 cty.UnknownVal(cty.String), 735 }), 736 }), 737 cty.ObjectVal(map[string]cty.Value{ 738 "id": cty.UnknownVal(cty.String), 739 "names": cty.ListVal([]cty.Value{ 740 cty.StringVal("thingy"), 741 cty.StringVal("wotsit"), 742 }), 743 }), 744 nil, 745 }, 746 { 747 &configschema.Block{ 748 Attributes: map[string]*configschema.Attribute{ 749 "id": { 750 Type: cty.String, 751 Computed: true, 752 }, 753 "names": { 754 Type: cty.List(cty.String), 755 Optional: true, 756 }, 757 }, 758 }, 759 cty.ObjectVal(map[string]cty.Value{ 760 "id": cty.UnknownVal(cty.String), 761 "names": cty.ListVal([]cty.Value{ 762 cty.UnknownVal(cty.String), 763 cty.StringVal("thingy"), 764 }), 765 }), 766 cty.ObjectVal(map[string]cty.Value{ 767 "id": cty.UnknownVal(cty.String), 768 "names": cty.ListVal([]cty.Value{ 769 cty.StringVal("thingy"), 770 cty.StringVal("wotsit"), 771 }), 772 }), 773 []string{ 774 `.names[1]: was cty.StringVal("thingy"), but now cty.StringVal("wotsit")`, 775 }, 776 }, 777 { 778 &configschema.Block{ 779 Attributes: map[string]*configschema.Attribute{ 780 "id": { 781 Type: cty.String, 782 Computed: true, 783 }, 784 "names": { 785 Type: cty.List(cty.String), 786 Optional: true, 787 }, 788 }, 789 }, 790 cty.ObjectVal(map[string]cty.Value{ 791 "id": cty.UnknownVal(cty.String), 792 "names": cty.ListVal([]cty.Value{ 793 cty.UnknownVal(cty.String), 794 }), 795 }), 796 cty.ObjectVal(map[string]cty.Value{ 797 "id": cty.UnknownVal(cty.String), 798 "names": cty.ListVal([]cty.Value{ 799 cty.StringVal("thingy"), 800 cty.StringVal("wotsit"), 801 }), 802 }), 803 []string{ 804 `.names: new element 1 has appeared`, 805 }, 806 }, 807 808 // NestingSingle blocks 809 { 810 &configschema.Block{ 811 BlockTypes: map[string]*configschema.NestedBlock{ 812 "key": { 813 Nesting: configschema.NestingSingle, 814 Block: configschema.Block{}, 815 }, 816 }, 817 }, 818 cty.ObjectVal(map[string]cty.Value{ 819 "key": cty.EmptyObjectVal, 820 }), 821 cty.ObjectVal(map[string]cty.Value{ 822 "key": cty.EmptyObjectVal, 823 }), 824 nil, 825 }, 826 { 827 &configschema.Block{ 828 BlockTypes: map[string]*configschema.NestedBlock{ 829 "key": { 830 Nesting: configschema.NestingSingle, 831 Block: configschema.Block{}, 832 }, 833 }, 834 }, 835 cty.ObjectVal(map[string]cty.Value{ 836 "key": cty.UnknownVal(cty.EmptyObject), 837 }), 838 cty.ObjectVal(map[string]cty.Value{ 839 "key": cty.EmptyObjectVal, 840 }), 841 nil, 842 }, 843 { 844 &configschema.Block{ 845 BlockTypes: map[string]*configschema.NestedBlock{ 846 "key": { 847 Nesting: configschema.NestingSingle, 848 Block: configschema.Block{ 849 Attributes: map[string]*configschema.Attribute{ 850 "foo": { 851 Type: cty.String, 852 Optional: true, 853 }, 854 }, 855 }, 856 }, 857 }, 858 }, 859 cty.ObjectVal(map[string]cty.Value{ 860 "key": cty.NullVal(cty.Object(map[string]cty.Type{ 861 "foo": cty.String, 862 })), 863 }), 864 cty.ObjectVal(map[string]cty.Value{ 865 "key": cty.ObjectVal(map[string]cty.Value{ 866 "foo": cty.StringVal("hello"), 867 }), 868 }), 869 []string{ 870 `.key: was absent, but now present`, 871 }, 872 }, 873 { 874 &configschema.Block{ 875 BlockTypes: map[string]*configschema.NestedBlock{ 876 "key": { 877 Nesting: configschema.NestingSingle, 878 Block: configschema.Block{ 879 Attributes: map[string]*configschema.Attribute{ 880 "foo": { 881 Type: cty.String, 882 Optional: true, 883 }, 884 }, 885 }, 886 }, 887 }, 888 }, 889 cty.ObjectVal(map[string]cty.Value{ 890 "key": cty.ObjectVal(map[string]cty.Value{ 891 "foo": cty.StringVal("hello"), 892 }), 893 }), 894 cty.ObjectVal(map[string]cty.Value{ 895 "key": cty.NullVal(cty.Object(map[string]cty.Type{ 896 "foo": cty.String, 897 })), 898 }), 899 []string{ 900 `.key: was present, but now absent`, 901 }, 902 }, 903 { 904 &configschema.Block{ 905 BlockTypes: map[string]*configschema.NestedBlock{ 906 "key": { 907 Nesting: configschema.NestingSingle, 908 Block: configschema.Block{ 909 Attributes: map[string]*configschema.Attribute{ 910 "foo": { 911 Type: cty.String, 912 Optional: true, 913 }, 914 }, 915 }, 916 }, 917 }, 918 }, 919 cty.UnknownVal(cty.Object(map[string]cty.Type{ 920 "key": cty.Object(map[string]cty.Type{ 921 "foo": cty.String, 922 }), 923 })), 924 cty.ObjectVal(map[string]cty.Value{ 925 "key": cty.NullVal(cty.Object(map[string]cty.Type{ 926 "foo": cty.String, 927 })), 928 }), 929 nil, 930 }, 931 932 // NestingList blocks 933 { 934 &configschema.Block{ 935 BlockTypes: map[string]*configschema.NestedBlock{ 936 "key": { 937 Nesting: configschema.NestingList, 938 Block: schemaWithFoo, 939 }, 940 }, 941 }, 942 cty.ObjectVal(map[string]cty.Value{ 943 "key": cty.ListVal([]cty.Value{ 944 fooBlockValue, 945 }), 946 }), 947 cty.ObjectVal(map[string]cty.Value{ 948 "key": cty.ListVal([]cty.Value{ 949 fooBlockValue, 950 }), 951 }), 952 nil, 953 }, 954 { 955 &configschema.Block{ 956 BlockTypes: map[string]*configschema.NestedBlock{ 957 "key": { 958 Nesting: configschema.NestingList, 959 Block: schemaWithFoo, 960 }, 961 }, 962 }, 963 cty.ObjectVal(map[string]cty.Value{ 964 "key": cty.TupleVal([]cty.Value{ 965 fooBlockValue, 966 fooBlockValue, 967 }), 968 }), 969 cty.ObjectVal(map[string]cty.Value{ 970 "key": cty.TupleVal([]cty.Value{ 971 fooBlockValue, 972 }), 973 }), 974 []string{ 975 `.key: block count changed from 2 to 1`, 976 }, 977 }, 978 { 979 &configschema.Block{ 980 BlockTypes: map[string]*configschema.NestedBlock{ 981 "key": { 982 Nesting: configschema.NestingList, 983 Block: schemaWithFoo, 984 }, 985 }, 986 }, 987 cty.ObjectVal(map[string]cty.Value{ 988 "key": cty.TupleVal([]cty.Value{}), 989 }), 990 cty.ObjectVal(map[string]cty.Value{ 991 "key": cty.TupleVal([]cty.Value{ 992 fooBlockValue, 993 fooBlockValue, 994 }), 995 }), 996 []string{ 997 `.key: block count changed from 0 to 2`, 998 }, 999 }, 1000 { 1001 &configschema.Block{ 1002 BlockTypes: map[string]*configschema.NestedBlock{ 1003 "key": { 1004 Nesting: configschema.NestingList, 1005 Block: schemaWithFooBar, 1006 }, 1007 }, 1008 }, 1009 cty.UnknownVal(cty.Object(map[string]cty.Type{ 1010 "key": cty.List(fooBarBlockValue.Type()), 1011 })), 1012 cty.ObjectVal(map[string]cty.Value{ 1013 "key": cty.ListVal([]cty.Value{ 1014 cty.ObjectVal(map[string]cty.Value{ 1015 "foo": cty.StringVal("hello"), 1016 }), 1017 cty.ObjectVal(map[string]cty.Value{ 1018 "foo": cty.StringVal("world"), 1019 }), 1020 }), 1021 }), 1022 nil, // an unknown block is allowed to expand into multiple, because that's how dynamic blocks behave when for_each is unknown 1023 }, 1024 { 1025 &configschema.Block{ 1026 BlockTypes: map[string]*configschema.NestedBlock{ 1027 "key": { 1028 Nesting: configschema.NestingList, 1029 Block: schemaWithFooBar, 1030 }, 1031 }, 1032 }, 1033 // While we must make an exception for empty strings in sets due to 1034 // the legacy SDK, lists should be compared more strictly. 1035 // This does not count as a dynamic block placeholder 1036 cty.ObjectVal(map[string]cty.Value{ 1037 "key": cty.ListVal([]cty.Value{ 1038 fooBarBlockValue, 1039 cty.ObjectVal(map[string]cty.Value{ 1040 "foo": cty.UnknownVal(cty.String), 1041 "bar": cty.StringVal(""), 1042 }), 1043 }), 1044 }), 1045 cty.ObjectVal(map[string]cty.Value{ 1046 "key": cty.ListVal([]cty.Value{ 1047 fooBlockValue, 1048 cty.ObjectVal(map[string]cty.Value{ 1049 "foo": cty.StringVal("hello"), 1050 }), 1051 cty.ObjectVal(map[string]cty.Value{ 1052 "foo": cty.StringVal("world"), 1053 }), 1054 }), 1055 }), 1056 []string{".key: block count changed from 2 to 3"}, 1057 }, 1058 1059 // NestingSet blocks 1060 { 1061 &configschema.Block{ 1062 BlockTypes: map[string]*configschema.NestedBlock{ 1063 "block": { 1064 Nesting: configschema.NestingSet, 1065 Block: schemaWithFoo, 1066 }, 1067 }, 1068 }, 1069 cty.ObjectVal(map[string]cty.Value{ 1070 "block": cty.SetVal([]cty.Value{ 1071 cty.ObjectVal(map[string]cty.Value{ 1072 "foo": cty.StringVal("hello"), 1073 }), 1074 cty.ObjectVal(map[string]cty.Value{ 1075 "foo": cty.StringVal("world"), 1076 }), 1077 }), 1078 }), 1079 cty.ObjectVal(map[string]cty.Value{ 1080 "block": cty.SetVal([]cty.Value{ 1081 cty.ObjectVal(map[string]cty.Value{ 1082 "foo": cty.StringVal("hello"), 1083 }), 1084 cty.ObjectVal(map[string]cty.Value{ 1085 "foo": cty.StringVal("world"), 1086 }), 1087 }), 1088 }), 1089 nil, 1090 }, 1091 { 1092 &configschema.Block{ 1093 BlockTypes: map[string]*configschema.NestedBlock{ 1094 "block": { 1095 Nesting: configschema.NestingSet, 1096 Block: schemaWithFoo, 1097 }, 1098 }, 1099 }, 1100 cty.ObjectVal(map[string]cty.Value{ 1101 "block": cty.SetVal([]cty.Value{ 1102 cty.ObjectVal(map[string]cty.Value{ 1103 "foo": cty.UnknownVal(cty.String), 1104 }), 1105 cty.ObjectVal(map[string]cty.Value{ 1106 "foo": cty.UnknownVal(cty.String), 1107 }), 1108 }), 1109 }), 1110 cty.ObjectVal(map[string]cty.Value{ 1111 "block": cty.SetVal([]cty.Value{ 1112 // This is testing the scenario where the two unknown values 1113 // turned out to be equal after we learned their values, 1114 // and so they coalesced together into a single element. 1115 cty.ObjectVal(map[string]cty.Value{ 1116 "foo": cty.StringVal("hello"), 1117 }), 1118 }), 1119 }), 1120 nil, 1121 }, 1122 { 1123 &configschema.Block{ 1124 BlockTypes: map[string]*configschema.NestedBlock{ 1125 "block": { 1126 Nesting: configschema.NestingSet, 1127 Block: schemaWithFoo, 1128 }, 1129 }, 1130 }, 1131 cty.ObjectVal(map[string]cty.Value{ 1132 "block": cty.SetVal([]cty.Value{ 1133 cty.ObjectVal(map[string]cty.Value{ 1134 "foo": cty.UnknownVal(cty.String), 1135 }), 1136 cty.ObjectVal(map[string]cty.Value{ 1137 "foo": cty.UnknownVal(cty.String), 1138 }), 1139 }), 1140 }), 1141 cty.ObjectVal(map[string]cty.Value{ 1142 "block": cty.SetVal([]cty.Value{ 1143 cty.ObjectVal(map[string]cty.Value{ 1144 "foo": cty.StringVal("hello"), 1145 }), 1146 cty.ObjectVal(map[string]cty.Value{ 1147 "foo": cty.StringVal("world"), 1148 }), 1149 }), 1150 }), 1151 nil, 1152 }, 1153 { 1154 &configschema.Block{ 1155 BlockTypes: map[string]*configschema.NestedBlock{ 1156 "block": { 1157 Nesting: configschema.NestingSet, 1158 Block: schemaWithFoo, 1159 }, 1160 }, 1161 }, 1162 cty.ObjectVal(map[string]cty.Value{ 1163 "block": cty.UnknownVal(cty.Set( 1164 cty.Object(map[string]cty.Type{ 1165 "foo": cty.String, 1166 }), 1167 )), 1168 }), 1169 cty.ObjectVal(map[string]cty.Value{ 1170 "block": cty.SetVal([]cty.Value{ 1171 cty.ObjectVal(map[string]cty.Value{ 1172 "foo": cty.StringVal("hello"), 1173 }), 1174 cty.ObjectVal(map[string]cty.Value{ 1175 "foo": cty.StringVal("world"), 1176 }), 1177 cty.ObjectVal(map[string]cty.Value{ 1178 "foo": cty.StringVal("nope"), 1179 }), 1180 }), 1181 }), 1182 // there is no error here, because the presence of unknowns 1183 // indicates this may be a dynamic block, and the length is unknown 1184 nil, 1185 }, 1186 { 1187 &configschema.Block{ 1188 BlockTypes: map[string]*configschema.NestedBlock{ 1189 "block": { 1190 Nesting: configschema.NestingSet, 1191 Block: schemaWithFoo, 1192 }, 1193 }, 1194 }, 1195 cty.ObjectVal(map[string]cty.Value{ 1196 "block": cty.SetVal([]cty.Value{ 1197 cty.ObjectVal(map[string]cty.Value{ 1198 "foo": cty.StringVal("hello"), 1199 }), 1200 cty.ObjectVal(map[string]cty.Value{ 1201 "foo": cty.StringVal("world"), 1202 }), 1203 }), 1204 }), 1205 cty.ObjectVal(map[string]cty.Value{ 1206 "block": cty.SetVal([]cty.Value{ 1207 cty.ObjectVal(map[string]cty.Value{ 1208 "foo": cty.StringVal("howdy"), 1209 }), 1210 cty.ObjectVal(map[string]cty.Value{ 1211 "foo": cty.StringVal("world"), 1212 }), 1213 }), 1214 }), 1215 []string{ 1216 `.block: planned set element cty.ObjectVal(map[string]cty.Value{"foo":cty.StringVal("hello")}) does not correlate with any element in actual`, 1217 }, 1218 }, 1219 { 1220 // This one is an odd situation where the value representing the 1221 // block itself is unknown. This is never supposed to be true, 1222 // but in legacy SDK mode we allow such things to pass through as 1223 // a warning, and so we must tolerate them for matching purposes. 1224 &configschema.Block{ 1225 BlockTypes: map[string]*configschema.NestedBlock{ 1226 "block": { 1227 Nesting: configschema.NestingSet, 1228 Block: schemaWithFoo, 1229 }, 1230 }, 1231 }, 1232 cty.ObjectVal(map[string]cty.Value{ 1233 "block": cty.SetVal([]cty.Value{ 1234 cty.ObjectVal(map[string]cty.Value{ 1235 "foo": cty.UnknownVal(cty.String), 1236 }), 1237 cty.ObjectVal(map[string]cty.Value{ 1238 "foo": cty.UnknownVal(cty.String), 1239 }), 1240 }), 1241 }), 1242 cty.ObjectVal(map[string]cty.Value{ 1243 "block": cty.UnknownVal(cty.Set(cty.Object(map[string]cty.Type{ 1244 "foo": cty.String, 1245 }))), 1246 }), 1247 nil, 1248 }, 1249 { 1250 &configschema.Block{ 1251 BlockTypes: map[string]*configschema.NestedBlock{ 1252 "block": { 1253 Nesting: configschema.NestingSet, 1254 Block: schemaWithFoo, 1255 }, 1256 }, 1257 }, 1258 cty.ObjectVal(map[string]cty.Value{ 1259 "block": cty.UnknownVal(cty.Set(fooBlockValue.Type())), 1260 }), 1261 cty.ObjectVal(map[string]cty.Value{ 1262 "block": cty.SetVal([]cty.Value{ 1263 cty.ObjectVal(map[string]cty.Value{ 1264 "foo": cty.StringVal("a"), 1265 }), 1266 cty.ObjectVal(map[string]cty.Value{ 1267 "foo": cty.StringVal("b"), 1268 }), 1269 }), 1270 }), 1271 nil, 1272 }, 1273 // test a set with an unknown dynamic count going to 0 values 1274 { 1275 &configschema.Block{ 1276 BlockTypes: map[string]*configschema.NestedBlock{ 1277 "block2": { 1278 Nesting: configschema.NestingSet, 1279 Block: schemaWithFoo, 1280 }, 1281 }, 1282 }, 1283 cty.ObjectVal(map[string]cty.Value{ 1284 "block2": cty.UnknownVal(cty.Set(fooBlockValue.Type())), 1285 }), 1286 cty.ObjectVal(map[string]cty.Value{ 1287 "block2": cty.SetValEmpty(cty.Object(map[string]cty.Type{ 1288 "foo": cty.String, 1289 })), 1290 }), 1291 nil, 1292 }, 1293 // test a set with a patially known dynamic count reducing it's values 1294 { 1295 &configschema.Block{ 1296 BlockTypes: map[string]*configschema.NestedBlock{ 1297 "block3": { 1298 Nesting: configschema.NestingSet, 1299 Block: schemaWithFoo, 1300 }, 1301 }, 1302 }, 1303 cty.ObjectVal(map[string]cty.Value{ 1304 "block3": cty.SetVal([]cty.Value{ 1305 cty.ObjectVal(map[string]cty.Value{ 1306 "foo": cty.StringVal("a"), 1307 }), 1308 cty.ObjectVal(map[string]cty.Value{ 1309 "foo": cty.UnknownVal(cty.String), 1310 }), 1311 }), 1312 }), 1313 cty.ObjectVal(map[string]cty.Value{ 1314 "block3": cty.SetVal([]cty.Value{ 1315 cty.ObjectVal(map[string]cty.Value{ 1316 "foo": cty.StringVal("a"), 1317 }), 1318 }), 1319 }), 1320 nil, 1321 }, 1322 { 1323 &configschema.Block{ 1324 BlockTypes: map[string]*configschema.NestedBlock{ 1325 "block": { 1326 Nesting: configschema.NestingList, 1327 Block: configschema.Block{ 1328 Attributes: map[string]*configschema.Attribute{ 1329 "foo": { 1330 Type: cty.String, 1331 Required: true, 1332 }, 1333 }, 1334 }, 1335 }, 1336 }, 1337 }, 1338 cty.ObjectVal(map[string]cty.Value{ 1339 "block": cty.EmptyObjectVal, 1340 }), 1341 cty.ObjectVal(map[string]cty.Value{ 1342 "block": cty.UnknownVal(cty.List(cty.Object(map[string]cty.Type{ 1343 "foo": cty.String, 1344 }))), 1345 }), 1346 nil, 1347 }, 1348 } 1349 1350 for i, test := range tests { 1351 t.Run(fmt.Sprintf("%02d: %#v and %#v", i, test.Planned, test.Actual), func(t *testing.T) { 1352 errs := AssertObjectCompatible(test.Schema, test.Planned, test.Actual) 1353 1354 wantErrs := make(map[string]struct{}) 1355 gotErrs := make(map[string]struct{}) 1356 for _, err := range errs { 1357 gotErrs[tfdiags.FormatError(err)] = struct{}{} 1358 } 1359 for _, msg := range test.WantErrs { 1360 wantErrs[msg] = struct{}{} 1361 } 1362 1363 t.Logf("\nplanned: %sactual: %s", dump.Value(test.Planned), dump.Value(test.Actual)) 1364 for msg := range wantErrs { 1365 if _, ok := gotErrs[msg]; !ok { 1366 t.Errorf("missing expected error: %s", msg) 1367 } 1368 } 1369 for msg := range gotErrs { 1370 if _, ok := wantErrs[msg]; !ok { 1371 t.Errorf("unexpected extra error: %s", msg) 1372 } 1373 } 1374 }) 1375 } 1376 }