github.com/graywolf-at-work-2/terraform-vendor@v1.4.5/internal/plans/objchange/plan_valid_test.go (about) 1 package objchange 2 3 import ( 4 "testing" 5 6 "github.com/apparentlymart/go-dump/dump" 7 "github.com/zclconf/go-cty/cty" 8 9 "github.com/hashicorp/terraform/internal/configs/configschema" 10 "github.com/hashicorp/terraform/internal/tfdiags" 11 ) 12 13 func TestAssertPlanValid(t *testing.T) { 14 tests := map[string]struct { 15 Schema *configschema.Block 16 Prior cty.Value 17 Config cty.Value 18 Planned cty.Value 19 WantErrs []string 20 }{ 21 "all empty": { 22 &configschema.Block{}, 23 cty.EmptyObjectVal, 24 cty.EmptyObjectVal, 25 cty.EmptyObjectVal, 26 nil, 27 }, 28 "no computed, all match": { 29 &configschema.Block{ 30 Attributes: map[string]*configschema.Attribute{ 31 "a": { 32 Type: cty.String, 33 Optional: true, 34 }, 35 }, 36 BlockTypes: map[string]*configschema.NestedBlock{ 37 "b": { 38 Nesting: configschema.NestingList, 39 Block: configschema.Block{ 40 Attributes: map[string]*configschema.Attribute{ 41 "c": { 42 Type: cty.String, 43 Optional: true, 44 }, 45 }, 46 }, 47 }, 48 }, 49 }, 50 cty.ObjectVal(map[string]cty.Value{ 51 "a": cty.StringVal("a value"), 52 "b": cty.ListVal([]cty.Value{ 53 cty.ObjectVal(map[string]cty.Value{ 54 "c": cty.StringVal("c value"), 55 }), 56 }), 57 }), 58 cty.ObjectVal(map[string]cty.Value{ 59 "a": cty.StringVal("a value"), 60 "b": cty.ListVal([]cty.Value{ 61 cty.ObjectVal(map[string]cty.Value{ 62 "c": cty.StringVal("c value"), 63 }), 64 }), 65 }), 66 cty.ObjectVal(map[string]cty.Value{ 67 "a": cty.StringVal("a value"), 68 "b": cty.ListVal([]cty.Value{ 69 cty.ObjectVal(map[string]cty.Value{ 70 "c": cty.StringVal("c value"), 71 }), 72 }), 73 }), 74 nil, 75 }, 76 "no computed, plan matches, no prior": { 77 &configschema.Block{ 78 Attributes: map[string]*configschema.Attribute{ 79 "a": { 80 Type: cty.String, 81 Optional: true, 82 }, 83 }, 84 BlockTypes: map[string]*configschema.NestedBlock{ 85 "b": { 86 Nesting: configschema.NestingList, 87 Block: configschema.Block{ 88 Attributes: map[string]*configschema.Attribute{ 89 "c": { 90 Type: cty.String, 91 Optional: true, 92 }, 93 }, 94 }, 95 }, 96 }, 97 }, 98 cty.NullVal(cty.Object(map[string]cty.Type{ 99 "a": cty.String, 100 "b": cty.List(cty.Object(map[string]cty.Type{ 101 "c": cty.String, 102 })), 103 })), 104 cty.ObjectVal(map[string]cty.Value{ 105 "a": cty.StringVal("a value"), 106 "b": cty.ListVal([]cty.Value{ 107 cty.ObjectVal(map[string]cty.Value{ 108 "c": cty.StringVal("c value"), 109 }), 110 }), 111 }), 112 cty.ObjectVal(map[string]cty.Value{ 113 "a": cty.StringVal("a value"), 114 "b": cty.ListVal([]cty.Value{ 115 cty.ObjectVal(map[string]cty.Value{ 116 "c": cty.StringVal("c value"), 117 }), 118 }), 119 }), 120 nil, 121 }, 122 "no computed, invalid change in plan": { 123 &configschema.Block{ 124 Attributes: map[string]*configschema.Attribute{ 125 "a": { 126 Type: cty.String, 127 Optional: true, 128 }, 129 }, 130 BlockTypes: map[string]*configschema.NestedBlock{ 131 "b": { 132 Nesting: configschema.NestingList, 133 Block: configschema.Block{ 134 Attributes: map[string]*configschema.Attribute{ 135 "c": { 136 Type: cty.String, 137 Optional: true, 138 }, 139 }, 140 }, 141 }, 142 }, 143 }, 144 cty.NullVal(cty.Object(map[string]cty.Type{ 145 "a": cty.String, 146 "b": cty.List(cty.Object(map[string]cty.Type{ 147 "c": cty.String, 148 })), 149 })), 150 cty.ObjectVal(map[string]cty.Value{ 151 "a": cty.StringVal("a value"), 152 "b": cty.ListVal([]cty.Value{ 153 cty.ObjectVal(map[string]cty.Value{ 154 "c": cty.StringVal("c value"), 155 }), 156 }), 157 }), 158 cty.ObjectVal(map[string]cty.Value{ 159 "a": cty.StringVal("a value"), 160 "b": cty.ListVal([]cty.Value{ 161 cty.ObjectVal(map[string]cty.Value{ 162 "c": cty.StringVal("new c value"), 163 }), 164 }), 165 }), 166 []string{ 167 `.b[0].c: planned value cty.StringVal("new c value") does not match config value cty.StringVal("c value")`, 168 }, 169 }, 170 "no computed, invalid change in plan sensitive": { 171 &configschema.Block{ 172 Attributes: map[string]*configschema.Attribute{ 173 "a": { 174 Type: cty.String, 175 Optional: true, 176 }, 177 }, 178 BlockTypes: map[string]*configschema.NestedBlock{ 179 "b": { 180 Nesting: configschema.NestingList, 181 Block: configschema.Block{ 182 Attributes: map[string]*configschema.Attribute{ 183 "c": { 184 Type: cty.String, 185 Optional: true, 186 Sensitive: true, 187 }, 188 }, 189 }, 190 }, 191 }, 192 }, 193 cty.NullVal(cty.Object(map[string]cty.Type{ 194 "a": cty.String, 195 "b": cty.List(cty.Object(map[string]cty.Type{ 196 "c": cty.String, 197 })), 198 })), 199 cty.ObjectVal(map[string]cty.Value{ 200 "a": cty.StringVal("a value"), 201 "b": cty.ListVal([]cty.Value{ 202 cty.ObjectVal(map[string]cty.Value{ 203 "c": cty.StringVal("c value"), 204 }), 205 }), 206 }), 207 cty.ObjectVal(map[string]cty.Value{ 208 "a": cty.StringVal("a value"), 209 "b": cty.ListVal([]cty.Value{ 210 cty.ObjectVal(map[string]cty.Value{ 211 "c": cty.StringVal("new c value"), 212 }), 213 }), 214 }), 215 []string{ 216 `.b[0].c: sensitive planned value does not match config value`, 217 }, 218 }, 219 "no computed, diff suppression in plan": { 220 &configschema.Block{ 221 Attributes: map[string]*configschema.Attribute{ 222 "a": { 223 Type: cty.String, 224 Optional: true, 225 }, 226 }, 227 BlockTypes: map[string]*configschema.NestedBlock{ 228 "b": { 229 Nesting: configschema.NestingList, 230 Block: configschema.Block{ 231 Attributes: map[string]*configschema.Attribute{ 232 "c": { 233 Type: cty.String, 234 Optional: true, 235 }, 236 }, 237 }, 238 }, 239 }, 240 }, 241 cty.ObjectVal(map[string]cty.Value{ 242 "a": cty.StringVal("a value"), 243 "b": cty.ListVal([]cty.Value{ 244 cty.ObjectVal(map[string]cty.Value{ 245 "c": cty.StringVal("c value"), 246 }), 247 }), 248 }), 249 cty.ObjectVal(map[string]cty.Value{ 250 "a": cty.StringVal("a value"), 251 "b": cty.ListVal([]cty.Value{ 252 cty.ObjectVal(map[string]cty.Value{ 253 "c": cty.StringVal("new c value"), 254 }), 255 }), 256 }), 257 cty.ObjectVal(map[string]cty.Value{ 258 "a": cty.StringVal("a value"), 259 "b": cty.ListVal([]cty.Value{ 260 cty.ObjectVal(map[string]cty.Value{ 261 "c": cty.StringVal("c value"), // plan uses value from prior object 262 }), 263 }), 264 }), 265 nil, 266 }, 267 "no computed, all null": { 268 &configschema.Block{ 269 Attributes: map[string]*configschema.Attribute{ 270 "a": { 271 Type: cty.String, 272 Optional: true, 273 }, 274 }, 275 BlockTypes: map[string]*configschema.NestedBlock{ 276 "b": { 277 Nesting: configschema.NestingList, 278 Block: configschema.Block{ 279 Attributes: map[string]*configschema.Attribute{ 280 "c": { 281 Type: cty.String, 282 Optional: true, 283 }, 284 }, 285 }, 286 }, 287 }, 288 }, 289 cty.ObjectVal(map[string]cty.Value{ 290 "a": cty.NullVal(cty.String), 291 "b": cty.ListVal([]cty.Value{ 292 cty.ObjectVal(map[string]cty.Value{ 293 "c": cty.NullVal(cty.String), 294 }), 295 }), 296 }), 297 cty.ObjectVal(map[string]cty.Value{ 298 "a": cty.NullVal(cty.String), 299 "b": cty.ListVal([]cty.Value{ 300 cty.ObjectVal(map[string]cty.Value{ 301 "c": cty.NullVal(cty.String), 302 }), 303 }), 304 }), 305 cty.ObjectVal(map[string]cty.Value{ 306 "a": cty.NullVal(cty.String), 307 "b": cty.ListVal([]cty.Value{ 308 cty.ObjectVal(map[string]cty.Value{ 309 "c": cty.NullVal(cty.String), 310 }), 311 }), 312 }), 313 nil, 314 }, 315 "nested map, normal update": { 316 &configschema.Block{ 317 BlockTypes: map[string]*configschema.NestedBlock{ 318 "b": { 319 Nesting: configschema.NestingMap, 320 Block: configschema.Block{ 321 Attributes: map[string]*configschema.Attribute{ 322 "c": { 323 Type: cty.String, 324 Optional: true, 325 }, 326 }, 327 }, 328 }, 329 }, 330 }, 331 cty.ObjectVal(map[string]cty.Value{ 332 "b": cty.MapVal(map[string]cty.Value{ 333 "boop": cty.ObjectVal(map[string]cty.Value{ 334 "c": cty.StringVal("hello"), 335 }), 336 }), 337 }), 338 cty.ObjectVal(map[string]cty.Value{ 339 "b": cty.MapVal(map[string]cty.Value{ 340 "boop": cty.ObjectVal(map[string]cty.Value{ 341 "c": cty.StringVal("howdy"), 342 }), 343 }), 344 }), 345 cty.ObjectVal(map[string]cty.Value{ 346 "b": cty.MapVal(map[string]cty.Value{ 347 "boop": cty.ObjectVal(map[string]cty.Value{ 348 "c": cty.StringVal("howdy"), 349 }), 350 }), 351 }), 352 nil, 353 }, 354 355 // Nested block collections are never null 356 "nested list, null in plan": { 357 &configschema.Block{ 358 BlockTypes: map[string]*configschema.NestedBlock{ 359 "b": { 360 Nesting: configschema.NestingList, 361 Block: configschema.Block{ 362 Attributes: map[string]*configschema.Attribute{ 363 "c": { 364 Type: cty.String, 365 Optional: true, 366 }, 367 }, 368 }, 369 }, 370 }, 371 }, 372 cty.NullVal(cty.Object(map[string]cty.Type{ 373 "b": cty.List(cty.Object(map[string]cty.Type{ 374 "c": cty.String, 375 })), 376 })), 377 cty.ObjectVal(map[string]cty.Value{ 378 "b": cty.ListValEmpty(cty.Object(map[string]cty.Type{ 379 "c": cty.String, 380 })), 381 }), 382 cty.ObjectVal(map[string]cty.Value{ 383 "b": cty.NullVal(cty.List(cty.Object(map[string]cty.Type{ 384 "c": cty.String, 385 }))), 386 }), 387 []string{ 388 `.b: attribute representing a list of nested blocks must be empty to indicate no blocks, not null`, 389 }, 390 }, 391 392 // but don't panic on a null list just in case 393 "nested list, null in config": { 394 &configschema.Block{ 395 BlockTypes: map[string]*configschema.NestedBlock{ 396 "b": { 397 Nesting: configschema.NestingList, 398 Block: configschema.Block{ 399 Attributes: map[string]*configschema.Attribute{ 400 "c": { 401 Type: cty.String, 402 Optional: true, 403 }, 404 }, 405 }, 406 }, 407 }, 408 }, 409 cty.ObjectVal(map[string]cty.Value{ 410 "b": cty.ListValEmpty(cty.Object(map[string]cty.Type{ 411 "c": cty.String, 412 })), 413 }), 414 cty.ObjectVal(map[string]cty.Value{ 415 "b": cty.NullVal(cty.List(cty.Object(map[string]cty.Type{ 416 "c": cty.String, 417 }))), 418 }), 419 cty.ObjectVal(map[string]cty.Value{ 420 "b": cty.ListValEmpty(cty.Object(map[string]cty.Type{ 421 "c": cty.String, 422 })), 423 }), 424 nil, 425 }, 426 427 // blocks can be unknown when using dynamic 428 "nested list, unknown nested dynamic": { 429 &configschema.Block{ 430 BlockTypes: map[string]*configschema.NestedBlock{ 431 "a": { 432 Nesting: configschema.NestingList, 433 Block: configschema.Block{ 434 BlockTypes: map[string]*configschema.NestedBlock{ 435 "b": { 436 Nesting: configschema.NestingList, 437 Block: configschema.Block{ 438 Attributes: map[string]*configschema.Attribute{ 439 "c": { 440 Type: cty.String, 441 Optional: true, 442 }, 443 "computed": { 444 Type: cty.String, 445 Computed: true, 446 }, 447 }, 448 }, 449 }, 450 }, 451 }, 452 }, 453 }, 454 }, 455 456 cty.ObjectVal(map[string]cty.Value{ 457 "a": cty.ListVal([]cty.Value{cty.ObjectVal(map[string]cty.Value{ 458 "computed": cty.NullVal(cty.String), 459 "b": cty.ListVal([]cty.Value{cty.ObjectVal(map[string]cty.Value{ 460 "c": cty.StringVal("x"), 461 })}), 462 })}), 463 }), 464 cty.ObjectVal(map[string]cty.Value{ 465 "a": cty.ListVal([]cty.Value{cty.ObjectVal(map[string]cty.Value{ 466 "b": cty.UnknownVal(cty.List(cty.Object(map[string]cty.Type{ 467 "c": cty.String, 468 "computed": cty.String, 469 }))), 470 })}), 471 }), 472 cty.ObjectVal(map[string]cty.Value{ 473 "a": cty.ListVal([]cty.Value{cty.ObjectVal(map[string]cty.Value{ 474 "b": cty.UnknownVal(cty.List(cty.Object(map[string]cty.Type{ 475 "c": cty.String, 476 "computed": cty.String, 477 }))), 478 })}), 479 }), 480 []string{}, 481 }, 482 483 "nested set, unknown dynamic cannot be planned": { 484 &configschema.Block{ 485 Attributes: map[string]*configschema.Attribute{ 486 "computed": { 487 Type: cty.String, 488 Computed: true, 489 }, 490 }, 491 BlockTypes: map[string]*configschema.NestedBlock{ 492 "b": { 493 Nesting: configschema.NestingSet, 494 Block: configschema.Block{ 495 Attributes: map[string]*configschema.Attribute{ 496 "c": { 497 Type: cty.String, 498 Optional: true, 499 }, 500 }, 501 }, 502 }, 503 }, 504 }, 505 506 cty.ObjectVal(map[string]cty.Value{ 507 "computed": cty.NullVal(cty.String), 508 "b": cty.SetVal([]cty.Value{cty.ObjectVal(map[string]cty.Value{ 509 "c": cty.StringVal("x"), 510 })}), 511 }), 512 cty.ObjectVal(map[string]cty.Value{ 513 "computed": cty.NullVal(cty.String), 514 "b": cty.UnknownVal(cty.Set(cty.Object(map[string]cty.Type{ 515 "c": cty.String, 516 }))), 517 }), 518 cty.ObjectVal(map[string]cty.Value{ 519 "computed": cty.StringVal("default"), 520 "b": cty.SetVal([]cty.Value{cty.ObjectVal(map[string]cty.Value{ 521 "c": cty.StringVal("oops"), 522 })}), 523 }), 524 525 []string{ 526 `.b: planned value cty.SetVal([]cty.Value{cty.ObjectVal(map[string]cty.Value{"c":cty.StringVal("oops")})}) for unknown dynamic block`, 527 }, 528 }, 529 530 "nested set, null in plan": { 531 &configschema.Block{ 532 BlockTypes: map[string]*configschema.NestedBlock{ 533 "b": { 534 Nesting: configschema.NestingSet, 535 Block: configschema.Block{ 536 Attributes: map[string]*configschema.Attribute{ 537 "c": { 538 Type: cty.String, 539 Optional: true, 540 }, 541 }, 542 }, 543 }, 544 }, 545 }, 546 cty.NullVal(cty.Object(map[string]cty.Type{ 547 "b": cty.Set(cty.Object(map[string]cty.Type{ 548 "c": cty.String, 549 })), 550 })), 551 cty.ObjectVal(map[string]cty.Value{ 552 "b": cty.SetValEmpty(cty.Object(map[string]cty.Type{ 553 "c": cty.String, 554 })), 555 }), 556 cty.ObjectVal(map[string]cty.Value{ 557 "b": cty.NullVal(cty.Set(cty.Object(map[string]cty.Type{ 558 "c": cty.String, 559 }))), 560 }), 561 []string{ 562 `.b: attribute representing a set of nested blocks must be empty to indicate no blocks, not null`, 563 }, 564 }, 565 "nested map, null in plan": { 566 &configschema.Block{ 567 BlockTypes: map[string]*configschema.NestedBlock{ 568 "b": { 569 Nesting: configschema.NestingMap, 570 Block: configschema.Block{ 571 Attributes: map[string]*configschema.Attribute{ 572 "c": { 573 Type: cty.String, 574 Optional: true, 575 }, 576 }, 577 }, 578 }, 579 }, 580 }, 581 cty.NullVal(cty.Object(map[string]cty.Type{ 582 "b": cty.Map(cty.Object(map[string]cty.Type{ 583 "c": cty.String, 584 })), 585 })), 586 cty.ObjectVal(map[string]cty.Value{ 587 "b": cty.MapValEmpty(cty.Object(map[string]cty.Type{ 588 "c": cty.String, 589 })), 590 }), 591 cty.ObjectVal(map[string]cty.Value{ 592 "b": cty.NullVal(cty.Map(cty.Object(map[string]cty.Type{ 593 "c": cty.String, 594 }))), 595 }), 596 []string{ 597 `.b: attribute representing a map of nested blocks must be empty to indicate no blocks, not null`, 598 }, 599 }, 600 601 // We don't actually do any validation for nested set blocks, and so 602 // the remaining cases here are just intending to ensure we don't 603 // inadvertently start generating errors incorrectly in future. 604 "nested set, no computed, no changes": { 605 &configschema.Block{ 606 BlockTypes: map[string]*configschema.NestedBlock{ 607 "b": { 608 Nesting: configschema.NestingSet, 609 Block: configschema.Block{ 610 Attributes: map[string]*configschema.Attribute{ 611 "c": { 612 Type: cty.String, 613 Optional: true, 614 }, 615 }, 616 }, 617 }, 618 }, 619 }, 620 cty.ObjectVal(map[string]cty.Value{ 621 "b": cty.SetVal([]cty.Value{ 622 cty.ObjectVal(map[string]cty.Value{ 623 "c": cty.StringVal("c value"), 624 }), 625 }), 626 }), 627 cty.ObjectVal(map[string]cty.Value{ 628 "b": cty.SetVal([]cty.Value{ 629 cty.ObjectVal(map[string]cty.Value{ 630 "c": cty.StringVal("c value"), 631 }), 632 }), 633 }), 634 cty.ObjectVal(map[string]cty.Value{ 635 "b": cty.SetVal([]cty.Value{ 636 cty.ObjectVal(map[string]cty.Value{ 637 "c": cty.StringVal("c value"), 638 }), 639 }), 640 }), 641 nil, 642 }, 643 "nested set, no computed, invalid change in plan": { 644 &configschema.Block{ 645 BlockTypes: map[string]*configschema.NestedBlock{ 646 "b": { 647 Nesting: configschema.NestingSet, 648 Block: configschema.Block{ 649 Attributes: map[string]*configschema.Attribute{ 650 "c": { 651 Type: cty.String, 652 Optional: true, 653 }, 654 }, 655 }, 656 }, 657 }, 658 }, 659 cty.ObjectVal(map[string]cty.Value{ 660 "b": cty.SetVal([]cty.Value{ 661 cty.ObjectVal(map[string]cty.Value{ 662 "c": cty.StringVal("c value"), 663 }), 664 }), 665 }), 666 cty.ObjectVal(map[string]cty.Value{ 667 "b": cty.SetVal([]cty.Value{ 668 cty.ObjectVal(map[string]cty.Value{ 669 "c": cty.StringVal("c value"), 670 }), 671 }), 672 }), 673 cty.ObjectVal(map[string]cty.Value{ 674 "b": cty.SetVal([]cty.Value{ 675 cty.ObjectVal(map[string]cty.Value{ 676 "c": cty.StringVal("new c value"), // matches neither prior nor config 677 }), 678 }), 679 }), 680 nil, 681 }, 682 "nested set, no computed, diff suppressed": { 683 &configschema.Block{ 684 BlockTypes: map[string]*configschema.NestedBlock{ 685 "b": { 686 Nesting: configschema.NestingSet, 687 Block: configschema.Block{ 688 Attributes: map[string]*configschema.Attribute{ 689 "c": { 690 Type: cty.String, 691 Optional: true, 692 }, 693 }, 694 }, 695 }, 696 }, 697 }, 698 cty.ObjectVal(map[string]cty.Value{ 699 "b": cty.SetVal([]cty.Value{ 700 cty.ObjectVal(map[string]cty.Value{ 701 "c": cty.StringVal("c value"), 702 }), 703 }), 704 }), 705 cty.ObjectVal(map[string]cty.Value{ 706 "b": cty.SetVal([]cty.Value{ 707 cty.ObjectVal(map[string]cty.Value{ 708 "c": cty.StringVal("new c value"), 709 }), 710 }), 711 }), 712 cty.ObjectVal(map[string]cty.Value{ 713 "b": cty.SetVal([]cty.Value{ 714 cty.ObjectVal(map[string]cty.Value{ 715 "c": cty.StringVal("c value"), // plan uses value from prior object 716 }), 717 }), 718 }), 719 nil, 720 }, 721 722 // Attributes with NestedTypes 723 "NestedType attr, no computed, all match": { 724 &configschema.Block{ 725 Attributes: map[string]*configschema.Attribute{ 726 "a": { 727 NestedType: &configschema.Object{ 728 Nesting: configschema.NestingList, 729 Attributes: map[string]*configschema.Attribute{ 730 "b": { 731 Type: cty.String, 732 Optional: true, 733 }, 734 }, 735 }, 736 Optional: true, 737 }, 738 }, 739 }, 740 cty.ObjectVal(map[string]cty.Value{ 741 "a": cty.ListVal([]cty.Value{ 742 cty.ObjectVal(map[string]cty.Value{ 743 "b": cty.StringVal("b value"), 744 }), 745 }), 746 }), 747 cty.ObjectVal(map[string]cty.Value{ 748 "a": cty.ListVal([]cty.Value{ 749 cty.ObjectVal(map[string]cty.Value{ 750 "b": cty.StringVal("b value"), 751 }), 752 }), 753 }), 754 cty.ObjectVal(map[string]cty.Value{ 755 "a": cty.ListVal([]cty.Value{ 756 cty.ObjectVal(map[string]cty.Value{ 757 "b": cty.StringVal("b value"), 758 }), 759 }), 760 }), 761 nil, 762 }, 763 "NestedType attr, no computed, plan matches, no prior": { 764 &configschema.Block{ 765 Attributes: map[string]*configschema.Attribute{ 766 "a": { 767 NestedType: &configschema.Object{ 768 Nesting: configschema.NestingList, 769 Attributes: map[string]*configschema.Attribute{ 770 "b": { 771 Type: cty.String, 772 Optional: true, 773 }, 774 }, 775 }, 776 Optional: true, 777 }, 778 }, 779 }, 780 cty.NullVal(cty.Object(map[string]cty.Type{ 781 "a": cty.List(cty.Object(map[string]cty.Type{ 782 "b": cty.String, 783 })), 784 })), 785 cty.ObjectVal(map[string]cty.Value{ 786 "a": cty.ListVal([]cty.Value{ 787 cty.ObjectVal(map[string]cty.Value{ 788 "b": cty.StringVal("c value"), 789 }), 790 }), 791 }), 792 cty.ObjectVal(map[string]cty.Value{ 793 "a": cty.ListVal([]cty.Value{ 794 cty.ObjectVal(map[string]cty.Value{ 795 "b": cty.StringVal("c value"), 796 }), 797 }), 798 }), 799 nil, 800 }, 801 "NestedType, no computed, invalid change in plan": { 802 &configschema.Block{ 803 Attributes: map[string]*configschema.Attribute{ 804 "a": { 805 NestedType: &configschema.Object{ 806 Nesting: configschema.NestingList, 807 Attributes: map[string]*configschema.Attribute{ 808 "b": { 809 Type: cty.String, 810 Optional: true, 811 }, 812 }, 813 }, 814 Optional: true, 815 }, 816 }, 817 }, 818 cty.NullVal(cty.Object(map[string]cty.Type{ 819 "a": cty.List(cty.Object(map[string]cty.Type{ 820 "b": cty.String, 821 })), 822 })), 823 cty.ObjectVal(map[string]cty.Value{ 824 "a": cty.ListVal([]cty.Value{ 825 cty.ObjectVal(map[string]cty.Value{ 826 "b": cty.StringVal("c value"), 827 }), 828 }), 829 }), 830 cty.ObjectVal(map[string]cty.Value{ 831 "a": cty.ListVal([]cty.Value{ 832 cty.ObjectVal(map[string]cty.Value{ 833 "b": cty.StringVal("new c value"), 834 }), 835 }), 836 }), 837 []string{ 838 `.a[0].b: planned value cty.StringVal("new c value") does not match config value cty.StringVal("c value")`, 839 }, 840 }, 841 "NestedType attr, no computed, invalid change in plan sensitive": { 842 &configschema.Block{ 843 Attributes: map[string]*configschema.Attribute{ 844 "a": { 845 NestedType: &configschema.Object{ 846 Nesting: configschema.NestingList, 847 Attributes: map[string]*configschema.Attribute{ 848 "b": { 849 Type: cty.String, 850 Optional: true, 851 Sensitive: true, 852 }, 853 }, 854 }, 855 Optional: true, 856 }, 857 }, 858 }, 859 cty.NullVal(cty.Object(map[string]cty.Type{ 860 "a": cty.List(cty.Object(map[string]cty.Type{ 861 "b": cty.String, 862 })), 863 })), 864 cty.ObjectVal(map[string]cty.Value{ 865 "a": cty.ListVal([]cty.Value{ 866 cty.ObjectVal(map[string]cty.Value{ 867 "b": cty.StringVal("b value"), 868 }), 869 }), 870 }), 871 cty.ObjectVal(map[string]cty.Value{ 872 "a": cty.ListVal([]cty.Value{ 873 cty.ObjectVal(map[string]cty.Value{ 874 "b": cty.StringVal("new b value"), 875 }), 876 }), 877 }), 878 []string{ 879 `.a[0].b: sensitive planned value does not match config value`, 880 }, 881 }, 882 "NestedType attr, no computed, diff suppression in plan": { 883 &configschema.Block{ 884 Attributes: map[string]*configschema.Attribute{ 885 "a": { 886 NestedType: &configschema.Object{ 887 Nesting: configschema.NestingList, 888 Attributes: map[string]*configschema.Attribute{ 889 "b": { 890 Type: cty.String, 891 Optional: true, 892 }, 893 }, 894 }, 895 Optional: true, 896 }, 897 }, 898 }, 899 cty.ObjectVal(map[string]cty.Value{ 900 "a": cty.ListVal([]cty.Value{ 901 cty.ObjectVal(map[string]cty.Value{ 902 "b": cty.StringVal("b value"), 903 }), 904 }), 905 }), 906 cty.ObjectVal(map[string]cty.Value{ 907 "a": cty.ListVal([]cty.Value{ 908 cty.ObjectVal(map[string]cty.Value{ 909 "b": cty.StringVal("new b value"), 910 }), 911 }), 912 }), 913 cty.ObjectVal(map[string]cty.Value{ 914 "a": cty.ListVal([]cty.Value{ 915 cty.ObjectVal(map[string]cty.Value{ 916 "b": cty.StringVal("b value"), // plan uses value from prior object 917 }), 918 }), 919 }), 920 nil, 921 }, 922 "NestedType attr, no computed, all null": { 923 &configschema.Block{ 924 Attributes: map[string]*configschema.Attribute{ 925 "a": { 926 NestedType: &configschema.Object{ 927 Nesting: configschema.NestingList, 928 Attributes: map[string]*configschema.Attribute{ 929 "b": { 930 Type: cty.String, 931 Optional: true, 932 }, 933 }, 934 }, 935 Optional: true, 936 }, 937 }, 938 }, 939 cty.ObjectVal(map[string]cty.Value{ 940 "a": cty.NullVal(cty.DynamicPseudoType), 941 }), 942 cty.ObjectVal(map[string]cty.Value{ 943 "a": cty.NullVal(cty.DynamicPseudoType), 944 }), 945 cty.ObjectVal(map[string]cty.Value{ 946 "a": cty.NullVal(cty.DynamicPseudoType), 947 }), 948 nil, 949 }, 950 "NestedType attr, no computed, all zero value": { 951 &configschema.Block{ 952 Attributes: map[string]*configschema.Attribute{ 953 "a": { 954 NestedType: &configschema.Object{ 955 Nesting: configschema.NestingList, 956 Attributes: map[string]*configschema.Attribute{ 957 "b": { 958 Type: cty.String, 959 Optional: true, 960 }, 961 }, 962 }, 963 Optional: true, 964 }, 965 }, 966 }, 967 cty.ObjectVal(map[string]cty.Value{ 968 "a": cty.NullVal(cty.List(cty.Object(map[string]cty.Type{ 969 "b": cty.String, 970 }))), 971 }), 972 cty.ObjectVal(map[string]cty.Value{ 973 "a": cty.NullVal(cty.List(cty.Object(map[string]cty.Type{ 974 "b": cty.String, 975 }))), 976 }), 977 cty.ObjectVal(map[string]cty.Value{ 978 "a": cty.NullVal(cty.List(cty.Object(map[string]cty.Type{ 979 "b": cty.String, 980 }))), 981 }), 982 nil, 983 }, 984 "NestedType NestingSet attribute to null": { 985 &configschema.Block{ 986 Attributes: map[string]*configschema.Attribute{ 987 "bloop": { 988 NestedType: &configschema.Object{ 989 Nesting: configschema.NestingSet, 990 Attributes: map[string]*configschema.Attribute{ 991 "blop": { 992 Type: cty.String, 993 Required: true, 994 }, 995 }, 996 }, 997 Optional: true, 998 }, 999 }, 1000 }, 1001 cty.ObjectVal(map[string]cty.Value{ 1002 "bloop": cty.SetVal([]cty.Value{ 1003 cty.ObjectVal(map[string]cty.Value{ 1004 "blop": cty.StringVal("ok"), 1005 }), 1006 }), 1007 }), 1008 cty.ObjectVal(map[string]cty.Value{ 1009 "bloop": cty.NullVal(cty.Set(cty.Object(map[string]cty.Type{ 1010 "blop": cty.String, 1011 }))), 1012 }), 1013 cty.ObjectVal(map[string]cty.Value{ 1014 "bloop": cty.NullVal(cty.Set(cty.Object(map[string]cty.Type{ 1015 "blop": cty.String, 1016 }))), 1017 }), 1018 nil, 1019 }, 1020 "NestedType deep nested optional set attribute to null": { 1021 &configschema.Block{ 1022 Attributes: map[string]*configschema.Attribute{ 1023 "bleep": { 1024 NestedType: &configschema.Object{ 1025 Nesting: configschema.NestingList, 1026 Attributes: map[string]*configschema.Attribute{ 1027 "bloop": { 1028 NestedType: &configschema.Object{ 1029 Nesting: configschema.NestingSet, 1030 Attributes: map[string]*configschema.Attribute{ 1031 "blome": { 1032 Type: cty.String, 1033 Optional: true, 1034 }, 1035 }, 1036 }, 1037 Optional: true, 1038 }, 1039 }, 1040 }, 1041 Optional: true, 1042 }, 1043 }, 1044 }, 1045 cty.ObjectVal(map[string]cty.Value{ 1046 "bleep": cty.ListVal([]cty.Value{ 1047 cty.ObjectVal(map[string]cty.Value{ 1048 "bloop": cty.SetVal([]cty.Value{ 1049 cty.ObjectVal(map[string]cty.Value{ 1050 "blome": cty.StringVal("ok"), 1051 }), 1052 }), 1053 }), 1054 }), 1055 }), 1056 cty.ObjectVal(map[string]cty.Value{ 1057 "bleep": cty.ListVal([]cty.Value{ 1058 cty.ObjectVal(map[string]cty.Value{ 1059 "bloop": cty.NullVal(cty.Set( 1060 cty.Object(map[string]cty.Type{ 1061 "blome": cty.String, 1062 }), 1063 )), 1064 }), 1065 }), 1066 }), 1067 cty.ObjectVal(map[string]cty.Value{ 1068 "bleep": cty.ListVal([]cty.Value{ 1069 cty.ObjectVal(map[string]cty.Value{ 1070 "bloop": cty.NullVal(cty.List( 1071 cty.Object(map[string]cty.Type{ 1072 "blome": cty.String, 1073 }), 1074 )), 1075 }), 1076 }), 1077 }), 1078 nil, 1079 }, 1080 "NestedType deep nested set": { 1081 &configschema.Block{ 1082 Attributes: map[string]*configschema.Attribute{ 1083 "bleep": { 1084 NestedType: &configschema.Object{ 1085 Nesting: configschema.NestingList, 1086 Attributes: map[string]*configschema.Attribute{ 1087 "bloop": { 1088 NestedType: &configschema.Object{ 1089 Nesting: configschema.NestingSet, 1090 Attributes: map[string]*configschema.Attribute{ 1091 "blome": { 1092 Type: cty.String, 1093 Optional: true, 1094 }, 1095 }, 1096 }, 1097 Optional: true, 1098 }, 1099 }, 1100 }, 1101 Optional: true, 1102 }, 1103 }, 1104 }, 1105 cty.ObjectVal(map[string]cty.Value{ 1106 "bleep": cty.ListVal([]cty.Value{ 1107 cty.ObjectVal(map[string]cty.Value{ 1108 "bloop": cty.SetVal([]cty.Value{ 1109 cty.ObjectVal(map[string]cty.Value{ 1110 "blome": cty.StringVal("ok"), 1111 }), 1112 }), 1113 }), 1114 }), 1115 }), 1116 // Note: bloop is null in the config 1117 cty.ObjectVal(map[string]cty.Value{ 1118 "bleep": cty.ListVal([]cty.Value{ 1119 cty.ObjectVal(map[string]cty.Value{ 1120 "bloop": cty.NullVal(cty.Set( 1121 cty.Object(map[string]cty.Type{ 1122 "blome": cty.String, 1123 }), 1124 )), 1125 }), 1126 }), 1127 }), 1128 // provider sends back the prior value, not matching the config 1129 cty.ObjectVal(map[string]cty.Value{ 1130 "bleep": cty.ListVal([]cty.Value{ 1131 cty.ObjectVal(map[string]cty.Value{ 1132 "bloop": cty.SetVal([]cty.Value{ 1133 cty.ObjectVal(map[string]cty.Value{ 1134 "blome": cty.StringVal("ok"), 1135 }), 1136 }), 1137 }), 1138 }), 1139 }), 1140 nil, // we cannot validate individual set elements, and trust the provider's response 1141 }, 1142 "NestedType nested computed list attribute": { 1143 &configschema.Block{ 1144 Attributes: map[string]*configschema.Attribute{ 1145 "bloop": { 1146 NestedType: &configschema.Object{ 1147 Nesting: configschema.NestingList, 1148 Attributes: map[string]*configschema.Attribute{ 1149 "blop": { 1150 Type: cty.String, 1151 Optional: true, 1152 }, 1153 }, 1154 }, 1155 Computed: true, 1156 }, 1157 }, 1158 }, 1159 cty.ObjectVal(map[string]cty.Value{ 1160 "bloop": cty.ListVal([]cty.Value{ 1161 cty.ObjectVal(map[string]cty.Value{ 1162 "blop": cty.StringVal("ok"), 1163 }), 1164 }), 1165 }), 1166 cty.ObjectVal(map[string]cty.Value{ 1167 "bloop": cty.NullVal(cty.List(cty.Object(map[string]cty.Type{ 1168 "blop": cty.String, 1169 }))), 1170 }), 1171 1172 cty.ObjectVal(map[string]cty.Value{ 1173 "bloop": cty.ListVal([]cty.Value{ 1174 cty.ObjectVal(map[string]cty.Value{ 1175 "blop": cty.StringVal("ok"), 1176 }), 1177 }), 1178 }), 1179 nil, 1180 }, 1181 "NestedType nested list attribute to null": { 1182 &configschema.Block{ 1183 Attributes: map[string]*configschema.Attribute{ 1184 "bloop": { 1185 NestedType: &configschema.Object{ 1186 Nesting: configschema.NestingList, 1187 Attributes: map[string]*configschema.Attribute{ 1188 "blop": { 1189 Type: cty.String, 1190 Optional: true, 1191 }, 1192 }, 1193 }, 1194 Optional: true, 1195 }, 1196 }, 1197 }, 1198 cty.ObjectVal(map[string]cty.Value{ 1199 "bloop": cty.ListVal([]cty.Value{ 1200 cty.ObjectVal(map[string]cty.Value{ 1201 "blop": cty.StringVal("ok"), 1202 }), 1203 }), 1204 }), 1205 cty.ObjectVal(map[string]cty.Value{ 1206 "bloop": cty.NullVal(cty.List(cty.Object(map[string]cty.Type{ 1207 "blop": cty.String, 1208 }))), 1209 }), 1210 1211 // provider returned the old value 1212 cty.ObjectVal(map[string]cty.Value{ 1213 "bloop": cty.ListVal([]cty.Value{ 1214 cty.ObjectVal(map[string]cty.Value{ 1215 "blop": cty.StringVal("ok"), 1216 }), 1217 }), 1218 }), 1219 []string{`.bloop: planned value cty.ListVal([]cty.Value{cty.ObjectVal(map[string]cty.Value{"blop":cty.StringVal("ok")})}) for a non-computed attribute`}, 1220 }, 1221 "NestedType nested set attribute to null": { 1222 &configschema.Block{ 1223 Attributes: map[string]*configschema.Attribute{ 1224 "bloop": { 1225 NestedType: &configschema.Object{ 1226 Nesting: configschema.NestingSet, 1227 Attributes: map[string]*configschema.Attribute{ 1228 "blop": { 1229 Type: cty.String, 1230 Optional: true, 1231 }, 1232 }, 1233 }, 1234 Optional: true, 1235 }, 1236 }, 1237 }, 1238 cty.ObjectVal(map[string]cty.Value{ 1239 "bloop": cty.SetVal([]cty.Value{ 1240 cty.ObjectVal(map[string]cty.Value{ 1241 "blop": cty.StringVal("ok"), 1242 }), 1243 }), 1244 }), 1245 cty.ObjectVal(map[string]cty.Value{ 1246 "bloop": cty.NullVal(cty.Set(cty.Object(map[string]cty.Type{ 1247 "blop": cty.String, 1248 }))), 1249 }), 1250 // provider returned the old value 1251 cty.ObjectVal(map[string]cty.Value{ 1252 "bloop": cty.ListVal([]cty.Value{ 1253 cty.ObjectVal(map[string]cty.Value{ 1254 "blop": cty.StringVal("ok"), 1255 }), 1256 }), 1257 }), 1258 []string{`.bloop: planned value cty.ListVal([]cty.Value{cty.ObjectVal(map[string]cty.Value{"blop":cty.StringVal("ok")})}) for a non-computed attribute`}, 1259 }, 1260 "computed within nested objects": { 1261 &configschema.Block{ 1262 Attributes: map[string]*configschema.Attribute{ 1263 "map": { 1264 NestedType: &configschema.Object{ 1265 Nesting: configschema.NestingMap, 1266 Attributes: map[string]*configschema.Attribute{ 1267 "name": { 1268 Type: cty.String, 1269 Computed: true, 1270 }, 1271 }, 1272 }, 1273 }, 1274 // When an object has dynamic attrs, the map may be 1275 // handled as an object. 1276 "map_as_obj": { 1277 NestedType: &configschema.Object{ 1278 Nesting: configschema.NestingMap, 1279 Attributes: map[string]*configschema.Attribute{ 1280 "name": { 1281 Type: cty.String, 1282 Computed: true, 1283 }, 1284 }, 1285 }, 1286 }, 1287 "list": { 1288 NestedType: &configschema.Object{ 1289 Nesting: configschema.NestingList, 1290 Attributes: map[string]*configschema.Attribute{ 1291 "name": { 1292 Type: cty.String, 1293 Computed: true, 1294 }, 1295 }, 1296 }, 1297 }, 1298 "set": { 1299 NestedType: &configschema.Object{ 1300 Nesting: configschema.NestingSet, 1301 Attributes: map[string]*configschema.Attribute{ 1302 "name": { 1303 Type: cty.String, 1304 Computed: true, 1305 }, 1306 }, 1307 }, 1308 }, 1309 "single": { 1310 NestedType: &configschema.Object{ 1311 Nesting: configschema.NestingSingle, 1312 Attributes: map[string]*configschema.Attribute{ 1313 "name": { 1314 Type: cty.DynamicPseudoType, 1315 Computed: true, 1316 }, 1317 }, 1318 }, 1319 }, 1320 }, 1321 }, 1322 cty.NullVal(cty.Object(map[string]cty.Type{ 1323 "map": cty.Map(cty.Object(map[string]cty.Type{ 1324 "name": cty.String, 1325 })), 1326 "map_as_obj": cty.Map(cty.Object(map[string]cty.Type{ 1327 "name": cty.DynamicPseudoType, 1328 })), 1329 "list": cty.List(cty.Object(map[string]cty.Type{ 1330 "name": cty.String, 1331 })), 1332 "set": cty.Set(cty.Object(map[string]cty.Type{ 1333 "name": cty.String, 1334 })), 1335 "single": cty.Object(map[string]cty.Type{ 1336 "name": cty.String, 1337 }), 1338 })), 1339 cty.ObjectVal(map[string]cty.Value{ 1340 "map": cty.MapVal(map[string]cty.Value{ 1341 "one": cty.ObjectVal(map[string]cty.Value{ 1342 "name": cty.NullVal(cty.String), 1343 }), 1344 }), 1345 "map_as_obj": cty.MapVal(map[string]cty.Value{ 1346 "one": cty.ObjectVal(map[string]cty.Value{ 1347 "name": cty.NullVal(cty.DynamicPseudoType), 1348 }), 1349 }), 1350 "list": cty.ListVal([]cty.Value{ 1351 cty.ObjectVal(map[string]cty.Value{ 1352 "name": cty.NullVal(cty.String), 1353 }), 1354 }), 1355 "set": cty.SetVal([]cty.Value{ 1356 cty.ObjectVal(map[string]cty.Value{ 1357 "name": cty.NullVal(cty.String), 1358 }), 1359 }), 1360 "single": cty.ObjectVal(map[string]cty.Value{ 1361 "name": cty.NullVal(cty.String), 1362 }), 1363 }), 1364 cty.ObjectVal(map[string]cty.Value{ 1365 "map": cty.MapVal(map[string]cty.Value{ 1366 "one": cty.ObjectVal(map[string]cty.Value{ 1367 "name": cty.NullVal(cty.String), 1368 }), 1369 }), 1370 "map_as_obj": cty.ObjectVal(map[string]cty.Value{ 1371 "one": cty.ObjectVal(map[string]cty.Value{ 1372 "name": cty.StringVal("computed"), 1373 }), 1374 }), 1375 "list": cty.ListVal([]cty.Value{ 1376 cty.ObjectVal(map[string]cty.Value{ 1377 "name": cty.NullVal(cty.String), 1378 }), 1379 }), 1380 "set": cty.SetVal([]cty.Value{ 1381 cty.ObjectVal(map[string]cty.Value{ 1382 "name": cty.NullVal(cty.String), 1383 }), 1384 }), 1385 "single": cty.ObjectVal(map[string]cty.Value{ 1386 "name": cty.NullVal(cty.String), 1387 }), 1388 }), 1389 nil, 1390 }, 1391 "computed nested objects": { 1392 &configschema.Block{ 1393 Attributes: map[string]*configschema.Attribute{ 1394 "map": { 1395 NestedType: &configschema.Object{ 1396 Nesting: configschema.NestingMap, 1397 Attributes: map[string]*configschema.Attribute{ 1398 "name": { 1399 Type: cty.String, 1400 }, 1401 }, 1402 }, 1403 Computed: true, 1404 }, 1405 "list": { 1406 NestedType: &configschema.Object{ 1407 Nesting: configschema.NestingList, 1408 Attributes: map[string]*configschema.Attribute{ 1409 "name": { 1410 Type: cty.String, 1411 }, 1412 }, 1413 }, 1414 Computed: true, 1415 }, 1416 "set": { 1417 NestedType: &configschema.Object{ 1418 Nesting: configschema.NestingSet, 1419 Attributes: map[string]*configschema.Attribute{ 1420 "name": { 1421 Type: cty.String, 1422 }, 1423 }, 1424 }, 1425 Optional: true, 1426 Computed: true, 1427 }, 1428 "single": { 1429 NestedType: &configschema.Object{ 1430 Nesting: configschema.NestingSingle, 1431 Attributes: map[string]*configschema.Attribute{ 1432 "name": { 1433 Type: cty.DynamicPseudoType, 1434 }, 1435 }, 1436 }, 1437 Computed: true, 1438 }, 1439 }, 1440 }, 1441 cty.NullVal(cty.Object(map[string]cty.Type{ 1442 "map": cty.Map(cty.Object(map[string]cty.Type{ 1443 "name": cty.String, 1444 })), 1445 "list": cty.List(cty.Object(map[string]cty.Type{ 1446 "name": cty.String, 1447 })), 1448 "set": cty.Set(cty.Object(map[string]cty.Type{ 1449 "name": cty.String, 1450 })), 1451 "single": cty.Object(map[string]cty.Type{ 1452 "name": cty.String, 1453 }), 1454 })), 1455 cty.ObjectVal(map[string]cty.Value{ 1456 "map": cty.NullVal(cty.Map(cty.Object(map[string]cty.Type{ 1457 "name": cty.String, 1458 }))), 1459 "list": cty.NullVal(cty.List(cty.Object(map[string]cty.Type{ 1460 "name": cty.String, 1461 }))), 1462 "set": cty.SetVal([]cty.Value{ 1463 cty.ObjectVal(map[string]cty.Value{ 1464 "name": cty.StringVal("from_config"), 1465 }), 1466 }), 1467 "single": cty.NullVal(cty.Object(map[string]cty.Type{ 1468 "name": cty.String, 1469 })), 1470 }), 1471 cty.ObjectVal(map[string]cty.Value{ 1472 "map": cty.MapVal(map[string]cty.Value{ 1473 "one": cty.UnknownVal(cty.Object(map[string]cty.Type{ 1474 "name": cty.String, 1475 })), 1476 }), 1477 "list": cty.ListVal([]cty.Value{ 1478 cty.ObjectVal(map[string]cty.Value{ 1479 "name": cty.StringVal("computed"), 1480 }), 1481 }), 1482 "set": cty.SetVal([]cty.Value{ 1483 cty.ObjectVal(map[string]cty.Value{ 1484 "name": cty.StringVal("from_config"), 1485 }), 1486 }), 1487 "single": cty.UnknownVal(cty.Object(map[string]cty.Type{ 1488 "name": cty.String, 1489 })), 1490 }), 1491 nil, 1492 }, 1493 "optional computed within nested objects": { 1494 &configschema.Block{ 1495 Attributes: map[string]*configschema.Attribute{ 1496 "map": { 1497 NestedType: &configschema.Object{ 1498 Nesting: configschema.NestingMap, 1499 Attributes: map[string]*configschema.Attribute{ 1500 "name": { 1501 Type: cty.String, 1502 Computed: true, 1503 }, 1504 }, 1505 }, 1506 }, 1507 // When an object has dynamic attrs, the map may be 1508 // handled as an object. 1509 "map_as_obj": { 1510 NestedType: &configschema.Object{ 1511 Nesting: configschema.NestingMap, 1512 Attributes: map[string]*configschema.Attribute{ 1513 "name": { 1514 Type: cty.String, 1515 Optional: true, 1516 Computed: true, 1517 }, 1518 }, 1519 }, 1520 }, 1521 "list": { 1522 NestedType: &configschema.Object{ 1523 Nesting: configschema.NestingList, 1524 Attributes: map[string]*configschema.Attribute{ 1525 "name": { 1526 Type: cty.String, 1527 Optional: true, 1528 Computed: true, 1529 }, 1530 }, 1531 }, 1532 }, 1533 "set": { 1534 NestedType: &configschema.Object{ 1535 Nesting: configschema.NestingSet, 1536 Attributes: map[string]*configschema.Attribute{ 1537 "name": { 1538 Type: cty.String, 1539 Optional: true, 1540 Computed: true, 1541 }, 1542 }, 1543 }, 1544 }, 1545 "single": { 1546 NestedType: &configschema.Object{ 1547 Nesting: configschema.NestingSingle, 1548 Attributes: map[string]*configschema.Attribute{ 1549 "name": { 1550 Type: cty.DynamicPseudoType, 1551 Optional: true, 1552 Computed: true, 1553 }, 1554 }, 1555 }, 1556 }, 1557 }, 1558 }, 1559 cty.NullVal(cty.Object(map[string]cty.Type{ 1560 "map": cty.Map(cty.Object(map[string]cty.Type{ 1561 "name": cty.String, 1562 })), 1563 "map_as_obj": cty.Map(cty.Object(map[string]cty.Type{ 1564 "name": cty.DynamicPseudoType, 1565 })), 1566 "list": cty.List(cty.Object(map[string]cty.Type{ 1567 "name": cty.String, 1568 })), 1569 "set": cty.Set(cty.Object(map[string]cty.Type{ 1570 "name": cty.String, 1571 })), 1572 "single": cty.Object(map[string]cty.Type{ 1573 "name": cty.String, 1574 }), 1575 })), 1576 cty.ObjectVal(map[string]cty.Value{ 1577 "map": cty.MapVal(map[string]cty.Value{ 1578 "one": cty.ObjectVal(map[string]cty.Value{ 1579 "name": cty.StringVal("from_config"), 1580 }), 1581 }), 1582 "map_as_obj": cty.MapVal(map[string]cty.Value{ 1583 "one": cty.ObjectVal(map[string]cty.Value{ 1584 "name": cty.NullVal(cty.DynamicPseudoType), 1585 }), 1586 }), 1587 "list": cty.ListVal([]cty.Value{ 1588 cty.ObjectVal(map[string]cty.Value{ 1589 "name": cty.NullVal(cty.String), 1590 }), 1591 }), 1592 "set": cty.SetVal([]cty.Value{ 1593 cty.ObjectVal(map[string]cty.Value{ 1594 "name": cty.NullVal(cty.String), 1595 }), 1596 }), 1597 "single": cty.ObjectVal(map[string]cty.Value{ 1598 "name": cty.StringVal("from_config"), 1599 }), 1600 }), 1601 cty.ObjectVal(map[string]cty.Value{ 1602 "map": cty.MapVal(map[string]cty.Value{ 1603 "one": cty.ObjectVal(map[string]cty.Value{ 1604 "name": cty.StringVal("from_config"), 1605 }), 1606 }), 1607 "map_as_obj": cty.ObjectVal(map[string]cty.Value{ 1608 "one": cty.ObjectVal(map[string]cty.Value{ 1609 "name": cty.StringVal("computed"), 1610 }), 1611 }), 1612 "list": cty.ListVal([]cty.Value{ 1613 cty.ObjectVal(map[string]cty.Value{ 1614 "name": cty.StringVal("computed"), 1615 }), 1616 }), 1617 "set": cty.SetVal([]cty.Value{ 1618 cty.ObjectVal(map[string]cty.Value{ 1619 "name": cty.NullVal(cty.String), 1620 }), 1621 }), 1622 "single": cty.ObjectVal(map[string]cty.Value{ 1623 "name": cty.StringVal("from_config"), 1624 }), 1625 }), 1626 nil, 1627 }, 1628 "cannot replace config nested attr": { 1629 &configschema.Block{ 1630 Attributes: map[string]*configschema.Attribute{ 1631 "map": { 1632 NestedType: &configschema.Object{ 1633 Nesting: configschema.NestingMap, 1634 Attributes: map[string]*configschema.Attribute{ 1635 "name": { 1636 Type: cty.String, 1637 Computed: true, 1638 Optional: true, 1639 }, 1640 }, 1641 }, 1642 }, 1643 }, 1644 }, 1645 cty.NullVal(cty.Object(map[string]cty.Type{ 1646 "map": cty.Map(cty.Object(map[string]cty.Type{ 1647 "name": cty.String, 1648 })), 1649 })), 1650 cty.ObjectVal(map[string]cty.Value{ 1651 "map": cty.MapVal(map[string]cty.Value{ 1652 "one": cty.ObjectVal(map[string]cty.Value{ 1653 "name": cty.StringVal("from_config"), 1654 }), 1655 }), 1656 }), 1657 cty.ObjectVal(map[string]cty.Value{ 1658 "map": cty.MapVal(map[string]cty.Value{ 1659 "one": cty.ObjectVal(map[string]cty.Value{ 1660 "name": cty.StringVal("from_provider"), 1661 }), 1662 }), 1663 }), 1664 []string{`.map.one.name: planned value cty.StringVal("from_provider") does not match config value cty.StringVal("from_config")`}, 1665 }, 1666 1667 // If a config value ended up in a computed-only attribute it can still 1668 // be a valid plan. We either got here because the user ignore warnings 1669 // about ignore_changes on computed attributes, or we failed to 1670 // validate a config with computed values. Either way, we don't want to 1671 // indicate an error with the provider. 1672 "computed only value with config": { 1673 &configschema.Block{ 1674 Attributes: map[string]*configschema.Attribute{ 1675 "a": { 1676 Type: cty.String, 1677 Computed: true, 1678 }, 1679 }, 1680 }, 1681 cty.ObjectVal(map[string]cty.Value{ 1682 "a": cty.StringVal("old"), 1683 }), 1684 cty.ObjectVal(map[string]cty.Value{ 1685 "a": cty.StringVal("old"), 1686 }), 1687 cty.ObjectVal(map[string]cty.Value{ 1688 "a": cty.UnknownVal(cty.String), 1689 }), 1690 nil, 1691 }, 1692 } 1693 1694 for name, test := range tests { 1695 t.Run(name, func(t *testing.T) { 1696 errs := AssertPlanValid(test.Schema, test.Prior, test.Config, test.Planned) 1697 1698 wantErrs := make(map[string]struct{}) 1699 gotErrs := make(map[string]struct{}) 1700 for _, err := range errs { 1701 gotErrs[tfdiags.FormatError(err)] = struct{}{} 1702 } 1703 for _, msg := range test.WantErrs { 1704 wantErrs[msg] = struct{}{} 1705 } 1706 1707 t.Logf( 1708 "\nprior: %sconfig: %splanned: %s", 1709 dump.Value(test.Prior), 1710 dump.Value(test.Config), 1711 dump.Value(test.Planned), 1712 ) 1713 for msg := range wantErrs { 1714 if _, ok := gotErrs[msg]; !ok { 1715 t.Errorf("missing expected error: %s", msg) 1716 } 1717 } 1718 for msg := range gotErrs { 1719 if _, ok := wantErrs[msg]; !ok { 1720 t.Errorf("unexpected extra error: %s", msg) 1721 } 1722 } 1723 }) 1724 } 1725 }