github.com/eliastor/durgaform@v0.0.0-20220816172711-d0ab2d17673e/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/eliastor/durgaform/internal/configs/configschema" 10 "github.com/eliastor/durgaform/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 // blocks can be unknown when using dynamic 393 "nested list, unknown nested dynamic": { 394 &configschema.Block{ 395 BlockTypes: map[string]*configschema.NestedBlock{ 396 "a": { 397 Nesting: configschema.NestingList, 398 Block: configschema.Block{ 399 BlockTypes: map[string]*configschema.NestedBlock{ 400 "b": { 401 Nesting: configschema.NestingList, 402 Block: configschema.Block{ 403 Attributes: map[string]*configschema.Attribute{ 404 "c": { 405 Type: cty.String, 406 Optional: true, 407 }, 408 "computed": { 409 Type: cty.String, 410 Computed: true, 411 }, 412 }, 413 }, 414 }, 415 }, 416 }, 417 }, 418 }, 419 }, 420 421 cty.ObjectVal(map[string]cty.Value{ 422 "a": cty.ListVal([]cty.Value{cty.ObjectVal(map[string]cty.Value{ 423 "computed": cty.NullVal(cty.String), 424 "b": cty.ListVal([]cty.Value{cty.ObjectVal(map[string]cty.Value{ 425 "c": cty.StringVal("x"), 426 })}), 427 })}), 428 }), 429 cty.ObjectVal(map[string]cty.Value{ 430 "a": cty.ListVal([]cty.Value{cty.ObjectVal(map[string]cty.Value{ 431 "b": cty.UnknownVal(cty.List(cty.Object(map[string]cty.Type{ 432 "c": cty.String, 433 "computed": cty.String, 434 }))), 435 })}), 436 }), 437 cty.ObjectVal(map[string]cty.Value{ 438 "a": cty.ListVal([]cty.Value{cty.ObjectVal(map[string]cty.Value{ 439 "b": cty.UnknownVal(cty.List(cty.Object(map[string]cty.Type{ 440 "c": cty.String, 441 "computed": cty.String, 442 }))), 443 })}), 444 }), 445 []string{}, 446 }, 447 448 "nested set, unknown dynamic cannot be planned": { 449 &configschema.Block{ 450 Attributes: map[string]*configschema.Attribute{ 451 "computed": { 452 Type: cty.String, 453 Computed: true, 454 }, 455 }, 456 BlockTypes: map[string]*configschema.NestedBlock{ 457 "b": { 458 Nesting: configschema.NestingSet, 459 Block: configschema.Block{ 460 Attributes: map[string]*configschema.Attribute{ 461 "c": { 462 Type: cty.String, 463 Optional: true, 464 }, 465 }, 466 }, 467 }, 468 }, 469 }, 470 471 cty.ObjectVal(map[string]cty.Value{ 472 "computed": cty.NullVal(cty.String), 473 "b": cty.SetVal([]cty.Value{cty.ObjectVal(map[string]cty.Value{ 474 "c": cty.StringVal("x"), 475 })}), 476 }), 477 cty.ObjectVal(map[string]cty.Value{ 478 "computed": cty.NullVal(cty.String), 479 "b": cty.UnknownVal(cty.Set(cty.Object(map[string]cty.Type{ 480 "c": cty.String, 481 }))), 482 }), 483 cty.ObjectVal(map[string]cty.Value{ 484 "computed": cty.StringVal("default"), 485 "b": cty.SetVal([]cty.Value{cty.ObjectVal(map[string]cty.Value{ 486 "c": cty.StringVal("oops"), 487 })}), 488 }), 489 490 []string{ 491 `.b: planned value cty.SetVal([]cty.Value{cty.ObjectVal(map[string]cty.Value{"c":cty.StringVal("oops")})}) for unknown dynamic block`, 492 }, 493 }, 494 495 "nested set, null in plan": { 496 &configschema.Block{ 497 BlockTypes: map[string]*configschema.NestedBlock{ 498 "b": { 499 Nesting: configschema.NestingSet, 500 Block: configschema.Block{ 501 Attributes: map[string]*configschema.Attribute{ 502 "c": { 503 Type: cty.String, 504 Optional: true, 505 }, 506 }, 507 }, 508 }, 509 }, 510 }, 511 cty.NullVal(cty.Object(map[string]cty.Type{ 512 "b": cty.Set(cty.Object(map[string]cty.Type{ 513 "c": cty.String, 514 })), 515 })), 516 cty.ObjectVal(map[string]cty.Value{ 517 "b": cty.SetValEmpty(cty.Object(map[string]cty.Type{ 518 "c": cty.String, 519 })), 520 }), 521 cty.ObjectVal(map[string]cty.Value{ 522 "b": cty.NullVal(cty.Set(cty.Object(map[string]cty.Type{ 523 "c": cty.String, 524 }))), 525 }), 526 []string{ 527 `.b: attribute representing a set of nested blocks must be empty to indicate no blocks, not null`, 528 }, 529 }, 530 "nested map, null in plan": { 531 &configschema.Block{ 532 BlockTypes: map[string]*configschema.NestedBlock{ 533 "b": { 534 Nesting: configschema.NestingMap, 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.Map(cty.Object(map[string]cty.Type{ 548 "c": cty.String, 549 })), 550 })), 551 cty.ObjectVal(map[string]cty.Value{ 552 "b": cty.MapValEmpty(cty.Object(map[string]cty.Type{ 553 "c": cty.String, 554 })), 555 }), 556 cty.ObjectVal(map[string]cty.Value{ 557 "b": cty.NullVal(cty.Map(cty.Object(map[string]cty.Type{ 558 "c": cty.String, 559 }))), 560 }), 561 []string{ 562 `.b: attribute representing a map of nested blocks must be empty to indicate no blocks, not null`, 563 }, 564 }, 565 566 // We don't actually do any validation for nested set blocks, and so 567 // the remaining cases here are just intending to ensure we don't 568 // inadvertently start generating errors incorrectly in future. 569 "nested set, no computed, no changes": { 570 &configschema.Block{ 571 BlockTypes: map[string]*configschema.NestedBlock{ 572 "b": { 573 Nesting: configschema.NestingSet, 574 Block: configschema.Block{ 575 Attributes: map[string]*configschema.Attribute{ 576 "c": { 577 Type: cty.String, 578 Optional: true, 579 }, 580 }, 581 }, 582 }, 583 }, 584 }, 585 cty.ObjectVal(map[string]cty.Value{ 586 "b": cty.SetVal([]cty.Value{ 587 cty.ObjectVal(map[string]cty.Value{ 588 "c": cty.StringVal("c value"), 589 }), 590 }), 591 }), 592 cty.ObjectVal(map[string]cty.Value{ 593 "b": cty.SetVal([]cty.Value{ 594 cty.ObjectVal(map[string]cty.Value{ 595 "c": cty.StringVal("c value"), 596 }), 597 }), 598 }), 599 cty.ObjectVal(map[string]cty.Value{ 600 "b": cty.SetVal([]cty.Value{ 601 cty.ObjectVal(map[string]cty.Value{ 602 "c": cty.StringVal("c value"), 603 }), 604 }), 605 }), 606 nil, 607 }, 608 "nested set, no computed, invalid change in plan": { 609 &configschema.Block{ 610 BlockTypes: map[string]*configschema.NestedBlock{ 611 "b": { 612 Nesting: configschema.NestingSet, 613 Block: configschema.Block{ 614 Attributes: map[string]*configschema.Attribute{ 615 "c": { 616 Type: cty.String, 617 Optional: true, 618 }, 619 }, 620 }, 621 }, 622 }, 623 }, 624 cty.ObjectVal(map[string]cty.Value{ 625 "b": cty.SetVal([]cty.Value{ 626 cty.ObjectVal(map[string]cty.Value{ 627 "c": cty.StringVal("c value"), 628 }), 629 }), 630 }), 631 cty.ObjectVal(map[string]cty.Value{ 632 "b": cty.SetVal([]cty.Value{ 633 cty.ObjectVal(map[string]cty.Value{ 634 "c": cty.StringVal("c value"), 635 }), 636 }), 637 }), 638 cty.ObjectVal(map[string]cty.Value{ 639 "b": cty.SetVal([]cty.Value{ 640 cty.ObjectVal(map[string]cty.Value{ 641 "c": cty.StringVal("new c value"), // matches neither prior nor config 642 }), 643 }), 644 }), 645 nil, 646 }, 647 "nested set, no computed, diff suppressed": { 648 &configschema.Block{ 649 BlockTypes: map[string]*configschema.NestedBlock{ 650 "b": { 651 Nesting: configschema.NestingSet, 652 Block: configschema.Block{ 653 Attributes: map[string]*configschema.Attribute{ 654 "c": { 655 Type: cty.String, 656 Optional: true, 657 }, 658 }, 659 }, 660 }, 661 }, 662 }, 663 cty.ObjectVal(map[string]cty.Value{ 664 "b": cty.SetVal([]cty.Value{ 665 cty.ObjectVal(map[string]cty.Value{ 666 "c": cty.StringVal("c value"), 667 }), 668 }), 669 }), 670 cty.ObjectVal(map[string]cty.Value{ 671 "b": cty.SetVal([]cty.Value{ 672 cty.ObjectVal(map[string]cty.Value{ 673 "c": cty.StringVal("new c value"), 674 }), 675 }), 676 }), 677 cty.ObjectVal(map[string]cty.Value{ 678 "b": cty.SetVal([]cty.Value{ 679 cty.ObjectVal(map[string]cty.Value{ 680 "c": cty.StringVal("c value"), // plan uses value from prior object 681 }), 682 }), 683 }), 684 nil, 685 }, 686 687 // Attributes with NestedTypes 688 "NestedType attr, no computed, all match": { 689 &configschema.Block{ 690 Attributes: map[string]*configschema.Attribute{ 691 "a": { 692 NestedType: &configschema.Object{ 693 Nesting: configschema.NestingList, 694 Attributes: map[string]*configschema.Attribute{ 695 "b": { 696 Type: cty.String, 697 Optional: true, 698 }, 699 }, 700 }, 701 Optional: true, 702 }, 703 }, 704 }, 705 cty.ObjectVal(map[string]cty.Value{ 706 "a": cty.ListVal([]cty.Value{ 707 cty.ObjectVal(map[string]cty.Value{ 708 "b": cty.StringVal("b value"), 709 }), 710 }), 711 }), 712 cty.ObjectVal(map[string]cty.Value{ 713 "a": cty.ListVal([]cty.Value{ 714 cty.ObjectVal(map[string]cty.Value{ 715 "b": cty.StringVal("b value"), 716 }), 717 }), 718 }), 719 cty.ObjectVal(map[string]cty.Value{ 720 "a": cty.ListVal([]cty.Value{ 721 cty.ObjectVal(map[string]cty.Value{ 722 "b": cty.StringVal("b value"), 723 }), 724 }), 725 }), 726 nil, 727 }, 728 "NestedType attr, no computed, plan matches, no prior": { 729 &configschema.Block{ 730 Attributes: map[string]*configschema.Attribute{ 731 "a": { 732 NestedType: &configschema.Object{ 733 Nesting: configschema.NestingList, 734 Attributes: map[string]*configschema.Attribute{ 735 "b": { 736 Type: cty.String, 737 Optional: true, 738 }, 739 }, 740 }, 741 Optional: true, 742 }, 743 }, 744 }, 745 cty.NullVal(cty.Object(map[string]cty.Type{ 746 "a": cty.List(cty.Object(map[string]cty.Type{ 747 "b": cty.String, 748 })), 749 })), 750 cty.ObjectVal(map[string]cty.Value{ 751 "a": cty.ListVal([]cty.Value{ 752 cty.ObjectVal(map[string]cty.Value{ 753 "b": cty.StringVal("c value"), 754 }), 755 }), 756 }), 757 cty.ObjectVal(map[string]cty.Value{ 758 "a": cty.ListVal([]cty.Value{ 759 cty.ObjectVal(map[string]cty.Value{ 760 "b": cty.StringVal("c value"), 761 }), 762 }), 763 }), 764 nil, 765 }, 766 "NestedType, no computed, invalid change in plan": { 767 &configschema.Block{ 768 Attributes: map[string]*configschema.Attribute{ 769 "a": { 770 NestedType: &configschema.Object{ 771 Nesting: configschema.NestingList, 772 Attributes: map[string]*configschema.Attribute{ 773 "b": { 774 Type: cty.String, 775 Optional: true, 776 }, 777 }, 778 }, 779 Optional: true, 780 }, 781 }, 782 }, 783 cty.NullVal(cty.Object(map[string]cty.Type{ 784 "a": cty.List(cty.Object(map[string]cty.Type{ 785 "b": cty.String, 786 })), 787 })), 788 cty.ObjectVal(map[string]cty.Value{ 789 "a": cty.ListVal([]cty.Value{ 790 cty.ObjectVal(map[string]cty.Value{ 791 "b": cty.StringVal("c value"), 792 }), 793 }), 794 }), 795 cty.ObjectVal(map[string]cty.Value{ 796 "a": cty.ListVal([]cty.Value{ 797 cty.ObjectVal(map[string]cty.Value{ 798 "b": cty.StringVal("new c value"), 799 }), 800 }), 801 }), 802 []string{ 803 `.a[0].b: planned value cty.StringVal("new c value") does not match config value cty.StringVal("c value")`, 804 }, 805 }, 806 "NestedType attr, no computed, invalid change in plan sensitive": { 807 &configschema.Block{ 808 Attributes: map[string]*configschema.Attribute{ 809 "a": { 810 NestedType: &configschema.Object{ 811 Nesting: configschema.NestingList, 812 Attributes: map[string]*configschema.Attribute{ 813 "b": { 814 Type: cty.String, 815 Optional: true, 816 Sensitive: true, 817 }, 818 }, 819 }, 820 Optional: true, 821 }, 822 }, 823 }, 824 cty.NullVal(cty.Object(map[string]cty.Type{ 825 "a": cty.List(cty.Object(map[string]cty.Type{ 826 "b": cty.String, 827 })), 828 })), 829 cty.ObjectVal(map[string]cty.Value{ 830 "a": cty.ListVal([]cty.Value{ 831 cty.ObjectVal(map[string]cty.Value{ 832 "b": cty.StringVal("b value"), 833 }), 834 }), 835 }), 836 cty.ObjectVal(map[string]cty.Value{ 837 "a": cty.ListVal([]cty.Value{ 838 cty.ObjectVal(map[string]cty.Value{ 839 "b": cty.StringVal("new b value"), 840 }), 841 }), 842 }), 843 []string{ 844 `.a[0].b: sensitive planned value does not match config value`, 845 }, 846 }, 847 "NestedType attr, no computed, diff suppression in plan": { 848 &configschema.Block{ 849 Attributes: map[string]*configschema.Attribute{ 850 "a": { 851 NestedType: &configschema.Object{ 852 Nesting: configschema.NestingList, 853 Attributes: map[string]*configschema.Attribute{ 854 "b": { 855 Type: cty.String, 856 Optional: true, 857 }, 858 }, 859 }, 860 Optional: true, 861 }, 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 cty.ObjectVal(map[string]cty.Value{ 879 "a": cty.ListVal([]cty.Value{ 880 cty.ObjectVal(map[string]cty.Value{ 881 "b": cty.StringVal("b value"), // plan uses value from prior object 882 }), 883 }), 884 }), 885 nil, 886 }, 887 "NestedType attr, no computed, all null": { 888 &configschema.Block{ 889 Attributes: map[string]*configschema.Attribute{ 890 "a": { 891 NestedType: &configschema.Object{ 892 Nesting: configschema.NestingList, 893 Attributes: map[string]*configschema.Attribute{ 894 "b": { 895 Type: cty.String, 896 Optional: true, 897 }, 898 }, 899 }, 900 Optional: true, 901 }, 902 }, 903 }, 904 cty.ObjectVal(map[string]cty.Value{ 905 "a": cty.NullVal(cty.DynamicPseudoType), 906 }), 907 cty.ObjectVal(map[string]cty.Value{ 908 "a": cty.NullVal(cty.DynamicPseudoType), 909 }), 910 cty.ObjectVal(map[string]cty.Value{ 911 "a": cty.NullVal(cty.DynamicPseudoType), 912 }), 913 nil, 914 }, 915 "NestedType attr, no computed, all zero value": { 916 &configschema.Block{ 917 Attributes: map[string]*configschema.Attribute{ 918 "a": { 919 NestedType: &configschema.Object{ 920 Nesting: configschema.NestingList, 921 Attributes: map[string]*configschema.Attribute{ 922 "b": { 923 Type: cty.String, 924 Optional: true, 925 }, 926 }, 927 }, 928 Optional: true, 929 }, 930 }, 931 }, 932 cty.ObjectVal(map[string]cty.Value{ 933 "a": cty.NullVal(cty.List(cty.Object(map[string]cty.Type{ 934 "b": cty.String, 935 }))), 936 }), 937 cty.ObjectVal(map[string]cty.Value{ 938 "a": cty.NullVal(cty.List(cty.Object(map[string]cty.Type{ 939 "b": cty.String, 940 }))), 941 }), 942 cty.ObjectVal(map[string]cty.Value{ 943 "a": cty.NullVal(cty.List(cty.Object(map[string]cty.Type{ 944 "b": cty.String, 945 }))), 946 }), 947 nil, 948 }, 949 "NestedType NestingSet attribute to null": { 950 &configschema.Block{ 951 Attributes: map[string]*configschema.Attribute{ 952 "bloop": { 953 NestedType: &configschema.Object{ 954 Nesting: configschema.NestingSet, 955 Attributes: map[string]*configschema.Attribute{ 956 "blop": { 957 Type: cty.String, 958 Required: true, 959 }, 960 }, 961 }, 962 Optional: true, 963 }, 964 }, 965 }, 966 cty.ObjectVal(map[string]cty.Value{ 967 "bloop": cty.SetVal([]cty.Value{ 968 cty.ObjectVal(map[string]cty.Value{ 969 "blop": cty.StringVal("ok"), 970 }), 971 }), 972 }), 973 cty.ObjectVal(map[string]cty.Value{ 974 "bloop": cty.NullVal(cty.Set(cty.Object(map[string]cty.Type{ 975 "blop": cty.String, 976 }))), 977 }), 978 cty.ObjectVal(map[string]cty.Value{ 979 "bloop": cty.NullVal(cty.Set(cty.Object(map[string]cty.Type{ 980 "blop": cty.String, 981 }))), 982 }), 983 nil, 984 }, 985 "NestedType deep nested optional set attribute to null": { 986 &configschema.Block{ 987 Attributes: map[string]*configschema.Attribute{ 988 "bleep": { 989 NestedType: &configschema.Object{ 990 Nesting: configschema.NestingList, 991 Attributes: map[string]*configschema.Attribute{ 992 "bloop": { 993 NestedType: &configschema.Object{ 994 Nesting: configschema.NestingSet, 995 Attributes: map[string]*configschema.Attribute{ 996 "blome": { 997 Type: cty.String, 998 Optional: true, 999 }, 1000 }, 1001 }, 1002 Optional: true, 1003 }, 1004 }, 1005 }, 1006 Optional: true, 1007 }, 1008 }, 1009 }, 1010 cty.ObjectVal(map[string]cty.Value{ 1011 "bleep": cty.ListVal([]cty.Value{ 1012 cty.ObjectVal(map[string]cty.Value{ 1013 "bloop": cty.SetVal([]cty.Value{ 1014 cty.ObjectVal(map[string]cty.Value{ 1015 "blome": cty.StringVal("ok"), 1016 }), 1017 }), 1018 }), 1019 }), 1020 }), 1021 cty.ObjectVal(map[string]cty.Value{ 1022 "bleep": cty.ListVal([]cty.Value{ 1023 cty.ObjectVal(map[string]cty.Value{ 1024 "bloop": cty.NullVal(cty.Set( 1025 cty.Object(map[string]cty.Type{ 1026 "blome": cty.String, 1027 }), 1028 )), 1029 }), 1030 }), 1031 }), 1032 cty.ObjectVal(map[string]cty.Value{ 1033 "bleep": cty.ListVal([]cty.Value{ 1034 cty.ObjectVal(map[string]cty.Value{ 1035 "bloop": cty.NullVal(cty.List( 1036 cty.Object(map[string]cty.Type{ 1037 "blome": cty.String, 1038 }), 1039 )), 1040 }), 1041 }), 1042 }), 1043 nil, 1044 }, 1045 "NestedType deep nested set": { 1046 &configschema.Block{ 1047 Attributes: map[string]*configschema.Attribute{ 1048 "bleep": { 1049 NestedType: &configschema.Object{ 1050 Nesting: configschema.NestingList, 1051 Attributes: map[string]*configschema.Attribute{ 1052 "bloop": { 1053 NestedType: &configschema.Object{ 1054 Nesting: configschema.NestingSet, 1055 Attributes: map[string]*configschema.Attribute{ 1056 "blome": { 1057 Type: cty.String, 1058 Optional: true, 1059 }, 1060 }, 1061 }, 1062 Optional: true, 1063 }, 1064 }, 1065 }, 1066 Optional: true, 1067 }, 1068 }, 1069 }, 1070 cty.ObjectVal(map[string]cty.Value{ 1071 "bleep": cty.ListVal([]cty.Value{ 1072 cty.ObjectVal(map[string]cty.Value{ 1073 "bloop": cty.SetVal([]cty.Value{ 1074 cty.ObjectVal(map[string]cty.Value{ 1075 "blome": cty.StringVal("ok"), 1076 }), 1077 }), 1078 }), 1079 }), 1080 }), 1081 // Note: bloop is null in the config 1082 cty.ObjectVal(map[string]cty.Value{ 1083 "bleep": cty.ListVal([]cty.Value{ 1084 cty.ObjectVal(map[string]cty.Value{ 1085 "bloop": cty.NullVal(cty.Set( 1086 cty.Object(map[string]cty.Type{ 1087 "blome": cty.String, 1088 }), 1089 )), 1090 }), 1091 }), 1092 }), 1093 // provider sends back the prior value, not matching the config 1094 cty.ObjectVal(map[string]cty.Value{ 1095 "bleep": cty.ListVal([]cty.Value{ 1096 cty.ObjectVal(map[string]cty.Value{ 1097 "bloop": cty.SetVal([]cty.Value{ 1098 cty.ObjectVal(map[string]cty.Value{ 1099 "blome": cty.StringVal("ok"), 1100 }), 1101 }), 1102 }), 1103 }), 1104 }), 1105 nil, // we cannot validate individual set elements, and trust the provider's response 1106 }, 1107 "NestedType nested computed list attribute": { 1108 &configschema.Block{ 1109 Attributes: map[string]*configschema.Attribute{ 1110 "bloop": { 1111 NestedType: &configschema.Object{ 1112 Nesting: configschema.NestingList, 1113 Attributes: map[string]*configschema.Attribute{ 1114 "blop": { 1115 Type: cty.String, 1116 Optional: true, 1117 }, 1118 }, 1119 }, 1120 Computed: true, 1121 }, 1122 }, 1123 }, 1124 cty.ObjectVal(map[string]cty.Value{ 1125 "bloop": cty.ListVal([]cty.Value{ 1126 cty.ObjectVal(map[string]cty.Value{ 1127 "blop": cty.StringVal("ok"), 1128 }), 1129 }), 1130 }), 1131 cty.ObjectVal(map[string]cty.Value{ 1132 "bloop": cty.NullVal(cty.List(cty.Object(map[string]cty.Type{ 1133 "blop": cty.String, 1134 }))), 1135 }), 1136 1137 cty.ObjectVal(map[string]cty.Value{ 1138 "bloop": cty.ListVal([]cty.Value{ 1139 cty.ObjectVal(map[string]cty.Value{ 1140 "blop": cty.StringVal("ok"), 1141 }), 1142 }), 1143 }), 1144 nil, 1145 }, 1146 "NestedType nested list attribute to null": { 1147 &configschema.Block{ 1148 Attributes: map[string]*configschema.Attribute{ 1149 "bloop": { 1150 NestedType: &configschema.Object{ 1151 Nesting: configschema.NestingList, 1152 Attributes: map[string]*configschema.Attribute{ 1153 "blop": { 1154 Type: cty.String, 1155 Optional: true, 1156 }, 1157 }, 1158 }, 1159 Optional: true, 1160 }, 1161 }, 1162 }, 1163 cty.ObjectVal(map[string]cty.Value{ 1164 "bloop": cty.ListVal([]cty.Value{ 1165 cty.ObjectVal(map[string]cty.Value{ 1166 "blop": cty.StringVal("ok"), 1167 }), 1168 }), 1169 }), 1170 cty.ObjectVal(map[string]cty.Value{ 1171 "bloop": cty.NullVal(cty.List(cty.Object(map[string]cty.Type{ 1172 "blop": cty.String, 1173 }))), 1174 }), 1175 1176 // provider returned the old value 1177 cty.ObjectVal(map[string]cty.Value{ 1178 "bloop": cty.ListVal([]cty.Value{ 1179 cty.ObjectVal(map[string]cty.Value{ 1180 "blop": cty.StringVal("ok"), 1181 }), 1182 }), 1183 }), 1184 []string{`.bloop: planned value cty.ListVal([]cty.Value{cty.ObjectVal(map[string]cty.Value{"blop":cty.StringVal("ok")})}) for a non-computed attribute`}, 1185 }, 1186 "NestedType nested set attribute to null": { 1187 &configschema.Block{ 1188 Attributes: map[string]*configschema.Attribute{ 1189 "bloop": { 1190 NestedType: &configschema.Object{ 1191 Nesting: configschema.NestingSet, 1192 Attributes: map[string]*configschema.Attribute{ 1193 "blop": { 1194 Type: cty.String, 1195 Optional: true, 1196 }, 1197 }, 1198 }, 1199 Optional: true, 1200 }, 1201 }, 1202 }, 1203 cty.ObjectVal(map[string]cty.Value{ 1204 "bloop": cty.SetVal([]cty.Value{ 1205 cty.ObjectVal(map[string]cty.Value{ 1206 "blop": cty.StringVal("ok"), 1207 }), 1208 }), 1209 }), 1210 cty.ObjectVal(map[string]cty.Value{ 1211 "bloop": cty.NullVal(cty.Set(cty.Object(map[string]cty.Type{ 1212 "blop": cty.String, 1213 }))), 1214 }), 1215 // provider returned the old value 1216 cty.ObjectVal(map[string]cty.Value{ 1217 "bloop": cty.ListVal([]cty.Value{ 1218 cty.ObjectVal(map[string]cty.Value{ 1219 "blop": cty.StringVal("ok"), 1220 }), 1221 }), 1222 }), 1223 []string{`.bloop: planned value cty.ListVal([]cty.Value{cty.ObjectVal(map[string]cty.Value{"blop":cty.StringVal("ok")})}) for a non-computed attribute`}, 1224 }, 1225 "computed within nested objects": { 1226 &configschema.Block{ 1227 Attributes: map[string]*configschema.Attribute{ 1228 "map": { 1229 NestedType: &configschema.Object{ 1230 Nesting: configschema.NestingMap, 1231 Attributes: map[string]*configschema.Attribute{ 1232 "name": { 1233 Type: cty.String, 1234 Computed: true, 1235 }, 1236 }, 1237 }, 1238 }, 1239 // When an object has dynamic attrs, the map may be 1240 // handled as an object. 1241 "map_as_obj": { 1242 NestedType: &configschema.Object{ 1243 Nesting: configschema.NestingMap, 1244 Attributes: map[string]*configschema.Attribute{ 1245 "name": { 1246 Type: cty.String, 1247 Computed: true, 1248 }, 1249 }, 1250 }, 1251 }, 1252 "list": { 1253 NestedType: &configschema.Object{ 1254 Nesting: configschema.NestingList, 1255 Attributes: map[string]*configschema.Attribute{ 1256 "name": { 1257 Type: cty.String, 1258 Computed: true, 1259 }, 1260 }, 1261 }, 1262 }, 1263 "set": { 1264 NestedType: &configschema.Object{ 1265 Nesting: configschema.NestingSet, 1266 Attributes: map[string]*configschema.Attribute{ 1267 "name": { 1268 Type: cty.String, 1269 Computed: true, 1270 }, 1271 }, 1272 }, 1273 }, 1274 "single": { 1275 NestedType: &configschema.Object{ 1276 Nesting: configschema.NestingSingle, 1277 Attributes: map[string]*configschema.Attribute{ 1278 "name": { 1279 Type: cty.DynamicPseudoType, 1280 Computed: true, 1281 }, 1282 }, 1283 }, 1284 }, 1285 }, 1286 }, 1287 cty.NullVal(cty.Object(map[string]cty.Type{ 1288 "map": cty.Map(cty.Object(map[string]cty.Type{ 1289 "name": cty.String, 1290 })), 1291 "map_as_obj": cty.Map(cty.Object(map[string]cty.Type{ 1292 "name": cty.DynamicPseudoType, 1293 })), 1294 "list": cty.List(cty.Object(map[string]cty.Type{ 1295 "name": cty.String, 1296 })), 1297 "set": cty.Set(cty.Object(map[string]cty.Type{ 1298 "name": cty.String, 1299 })), 1300 "single": cty.Object(map[string]cty.Type{ 1301 "name": cty.String, 1302 }), 1303 })), 1304 cty.ObjectVal(map[string]cty.Value{ 1305 "map": cty.MapVal(map[string]cty.Value{ 1306 "one": cty.ObjectVal(map[string]cty.Value{ 1307 "name": cty.NullVal(cty.String), 1308 }), 1309 }), 1310 "map_as_obj": cty.MapVal(map[string]cty.Value{ 1311 "one": cty.ObjectVal(map[string]cty.Value{ 1312 "name": cty.NullVal(cty.DynamicPseudoType), 1313 }), 1314 }), 1315 "list": cty.ListVal([]cty.Value{ 1316 cty.ObjectVal(map[string]cty.Value{ 1317 "name": cty.NullVal(cty.String), 1318 }), 1319 }), 1320 "set": cty.SetVal([]cty.Value{ 1321 cty.ObjectVal(map[string]cty.Value{ 1322 "name": cty.NullVal(cty.String), 1323 }), 1324 }), 1325 "single": cty.ObjectVal(map[string]cty.Value{ 1326 "name": cty.NullVal(cty.String), 1327 }), 1328 }), 1329 cty.ObjectVal(map[string]cty.Value{ 1330 "map": cty.MapVal(map[string]cty.Value{ 1331 "one": cty.ObjectVal(map[string]cty.Value{ 1332 "name": cty.NullVal(cty.String), 1333 }), 1334 }), 1335 "map_as_obj": cty.ObjectVal(map[string]cty.Value{ 1336 "one": cty.ObjectVal(map[string]cty.Value{ 1337 "name": cty.StringVal("computed"), 1338 }), 1339 }), 1340 "list": cty.ListVal([]cty.Value{ 1341 cty.ObjectVal(map[string]cty.Value{ 1342 "name": cty.NullVal(cty.String), 1343 }), 1344 }), 1345 "set": cty.SetVal([]cty.Value{ 1346 cty.ObjectVal(map[string]cty.Value{ 1347 "name": cty.NullVal(cty.String), 1348 }), 1349 }), 1350 "single": cty.ObjectVal(map[string]cty.Value{ 1351 "name": cty.NullVal(cty.String), 1352 }), 1353 }), 1354 nil, 1355 }, 1356 "computed nested objects": { 1357 &configschema.Block{ 1358 Attributes: map[string]*configschema.Attribute{ 1359 "map": { 1360 NestedType: &configschema.Object{ 1361 Nesting: configschema.NestingMap, 1362 Attributes: map[string]*configschema.Attribute{ 1363 "name": { 1364 Type: cty.String, 1365 }, 1366 }, 1367 }, 1368 Computed: true, 1369 }, 1370 "list": { 1371 NestedType: &configschema.Object{ 1372 Nesting: configschema.NestingList, 1373 Attributes: map[string]*configschema.Attribute{ 1374 "name": { 1375 Type: cty.String, 1376 }, 1377 }, 1378 }, 1379 Computed: true, 1380 }, 1381 "set": { 1382 NestedType: &configschema.Object{ 1383 Nesting: configschema.NestingSet, 1384 Attributes: map[string]*configschema.Attribute{ 1385 "name": { 1386 Type: cty.String, 1387 }, 1388 }, 1389 }, 1390 Optional: true, 1391 Computed: true, 1392 }, 1393 "single": { 1394 NestedType: &configschema.Object{ 1395 Nesting: configschema.NestingSingle, 1396 Attributes: map[string]*configschema.Attribute{ 1397 "name": { 1398 Type: cty.DynamicPseudoType, 1399 }, 1400 }, 1401 }, 1402 Computed: true, 1403 }, 1404 }, 1405 }, 1406 cty.NullVal(cty.Object(map[string]cty.Type{ 1407 "map": cty.Map(cty.Object(map[string]cty.Type{ 1408 "name": cty.String, 1409 })), 1410 "list": cty.List(cty.Object(map[string]cty.Type{ 1411 "name": cty.String, 1412 })), 1413 "set": cty.Set(cty.Object(map[string]cty.Type{ 1414 "name": cty.String, 1415 })), 1416 "single": cty.Object(map[string]cty.Type{ 1417 "name": cty.String, 1418 }), 1419 })), 1420 cty.ObjectVal(map[string]cty.Value{ 1421 "map": cty.NullVal(cty.Map(cty.Object(map[string]cty.Type{ 1422 "name": cty.String, 1423 }))), 1424 "list": cty.NullVal(cty.List(cty.Object(map[string]cty.Type{ 1425 "name": cty.String, 1426 }))), 1427 "set": cty.SetVal([]cty.Value{ 1428 cty.ObjectVal(map[string]cty.Value{ 1429 "name": cty.StringVal("from_config"), 1430 }), 1431 }), 1432 "single": cty.NullVal(cty.Object(map[string]cty.Type{ 1433 "name": cty.String, 1434 })), 1435 }), 1436 cty.ObjectVal(map[string]cty.Value{ 1437 "map": cty.MapVal(map[string]cty.Value{ 1438 "one": cty.UnknownVal(cty.Object(map[string]cty.Type{ 1439 "name": cty.String, 1440 })), 1441 }), 1442 "list": cty.ListVal([]cty.Value{ 1443 cty.ObjectVal(map[string]cty.Value{ 1444 "name": cty.StringVal("computed"), 1445 }), 1446 }), 1447 "set": cty.SetVal([]cty.Value{ 1448 cty.ObjectVal(map[string]cty.Value{ 1449 "name": cty.StringVal("from_config"), 1450 }), 1451 }), 1452 "single": cty.UnknownVal(cty.Object(map[string]cty.Type{ 1453 "name": cty.String, 1454 })), 1455 }), 1456 nil, 1457 }, 1458 "optional computed within nested objects": { 1459 &configschema.Block{ 1460 Attributes: map[string]*configschema.Attribute{ 1461 "map": { 1462 NestedType: &configschema.Object{ 1463 Nesting: configschema.NestingMap, 1464 Attributes: map[string]*configschema.Attribute{ 1465 "name": { 1466 Type: cty.String, 1467 Computed: true, 1468 }, 1469 }, 1470 }, 1471 }, 1472 // When an object has dynamic attrs, the map may be 1473 // handled as an object. 1474 "map_as_obj": { 1475 NestedType: &configschema.Object{ 1476 Nesting: configschema.NestingMap, 1477 Attributes: map[string]*configschema.Attribute{ 1478 "name": { 1479 Type: cty.String, 1480 Optional: true, 1481 Computed: true, 1482 }, 1483 }, 1484 }, 1485 }, 1486 "list": { 1487 NestedType: &configschema.Object{ 1488 Nesting: configschema.NestingList, 1489 Attributes: map[string]*configschema.Attribute{ 1490 "name": { 1491 Type: cty.String, 1492 Optional: true, 1493 Computed: true, 1494 }, 1495 }, 1496 }, 1497 }, 1498 "set": { 1499 NestedType: &configschema.Object{ 1500 Nesting: configschema.NestingSet, 1501 Attributes: map[string]*configschema.Attribute{ 1502 "name": { 1503 Type: cty.String, 1504 Optional: true, 1505 Computed: true, 1506 }, 1507 }, 1508 }, 1509 }, 1510 "single": { 1511 NestedType: &configschema.Object{ 1512 Nesting: configschema.NestingSingle, 1513 Attributes: map[string]*configschema.Attribute{ 1514 "name": { 1515 Type: cty.DynamicPseudoType, 1516 Optional: true, 1517 Computed: true, 1518 }, 1519 }, 1520 }, 1521 }, 1522 }, 1523 }, 1524 cty.NullVal(cty.Object(map[string]cty.Type{ 1525 "map": cty.Map(cty.Object(map[string]cty.Type{ 1526 "name": cty.String, 1527 })), 1528 "map_as_obj": cty.Map(cty.Object(map[string]cty.Type{ 1529 "name": cty.DynamicPseudoType, 1530 })), 1531 "list": cty.List(cty.Object(map[string]cty.Type{ 1532 "name": cty.String, 1533 })), 1534 "set": cty.Set(cty.Object(map[string]cty.Type{ 1535 "name": cty.String, 1536 })), 1537 "single": cty.Object(map[string]cty.Type{ 1538 "name": cty.String, 1539 }), 1540 })), 1541 cty.ObjectVal(map[string]cty.Value{ 1542 "map": cty.MapVal(map[string]cty.Value{ 1543 "one": cty.ObjectVal(map[string]cty.Value{ 1544 "name": cty.StringVal("from_config"), 1545 }), 1546 }), 1547 "map_as_obj": cty.MapVal(map[string]cty.Value{ 1548 "one": cty.ObjectVal(map[string]cty.Value{ 1549 "name": cty.NullVal(cty.DynamicPseudoType), 1550 }), 1551 }), 1552 "list": cty.ListVal([]cty.Value{ 1553 cty.ObjectVal(map[string]cty.Value{ 1554 "name": cty.NullVal(cty.String), 1555 }), 1556 }), 1557 "set": cty.SetVal([]cty.Value{ 1558 cty.ObjectVal(map[string]cty.Value{ 1559 "name": cty.NullVal(cty.String), 1560 }), 1561 }), 1562 "single": cty.ObjectVal(map[string]cty.Value{ 1563 "name": cty.StringVal("from_config"), 1564 }), 1565 }), 1566 cty.ObjectVal(map[string]cty.Value{ 1567 "map": cty.MapVal(map[string]cty.Value{ 1568 "one": cty.ObjectVal(map[string]cty.Value{ 1569 "name": cty.StringVal("from_config"), 1570 }), 1571 }), 1572 "map_as_obj": cty.ObjectVal(map[string]cty.Value{ 1573 "one": cty.ObjectVal(map[string]cty.Value{ 1574 "name": cty.StringVal("computed"), 1575 }), 1576 }), 1577 "list": cty.ListVal([]cty.Value{ 1578 cty.ObjectVal(map[string]cty.Value{ 1579 "name": cty.StringVal("computed"), 1580 }), 1581 }), 1582 "set": cty.SetVal([]cty.Value{ 1583 cty.ObjectVal(map[string]cty.Value{ 1584 "name": cty.NullVal(cty.String), 1585 }), 1586 }), 1587 "single": cty.ObjectVal(map[string]cty.Value{ 1588 "name": cty.StringVal("from_config"), 1589 }), 1590 }), 1591 nil, 1592 }, 1593 "cannot replace config nested attr": { 1594 &configschema.Block{ 1595 Attributes: map[string]*configschema.Attribute{ 1596 "map": { 1597 NestedType: &configschema.Object{ 1598 Nesting: configschema.NestingMap, 1599 Attributes: map[string]*configschema.Attribute{ 1600 "name": { 1601 Type: cty.String, 1602 Computed: true, 1603 Optional: true, 1604 }, 1605 }, 1606 }, 1607 }, 1608 }, 1609 }, 1610 cty.NullVal(cty.Object(map[string]cty.Type{ 1611 "map": cty.Map(cty.Object(map[string]cty.Type{ 1612 "name": cty.String, 1613 })), 1614 })), 1615 cty.ObjectVal(map[string]cty.Value{ 1616 "map": cty.MapVal(map[string]cty.Value{ 1617 "one": cty.ObjectVal(map[string]cty.Value{ 1618 "name": cty.StringVal("from_config"), 1619 }), 1620 }), 1621 }), 1622 cty.ObjectVal(map[string]cty.Value{ 1623 "map": cty.MapVal(map[string]cty.Value{ 1624 "one": cty.ObjectVal(map[string]cty.Value{ 1625 "name": cty.StringVal("from_provider"), 1626 }), 1627 }), 1628 }), 1629 []string{`.map.one.name: planned value cty.StringVal("from_provider") does not match config value cty.StringVal("from_config")`}, 1630 }, 1631 1632 // If a config value ended up in a computed-only attribute it can still 1633 // be a valid plan. We either got here because the user ignore warnings 1634 // about ignore_changes on computed attributes, or we failed to 1635 // validate a config with computed values. Either way, we don't want to 1636 // indicate an error with the provider. 1637 "computed only value with config": { 1638 &configschema.Block{ 1639 Attributes: map[string]*configschema.Attribute{ 1640 "a": { 1641 Type: cty.String, 1642 Computed: true, 1643 }, 1644 }, 1645 }, 1646 cty.ObjectVal(map[string]cty.Value{ 1647 "a": cty.StringVal("old"), 1648 }), 1649 cty.ObjectVal(map[string]cty.Value{ 1650 "a": cty.StringVal("old"), 1651 }), 1652 cty.ObjectVal(map[string]cty.Value{ 1653 "a": cty.UnknownVal(cty.String), 1654 }), 1655 nil, 1656 }, 1657 } 1658 1659 for name, test := range tests { 1660 t.Run(name, func(t *testing.T) { 1661 errs := AssertPlanValid(test.Schema, test.Prior, test.Config, test.Planned) 1662 1663 wantErrs := make(map[string]struct{}) 1664 gotErrs := make(map[string]struct{}) 1665 for _, err := range errs { 1666 gotErrs[tfdiags.FormatError(err)] = struct{}{} 1667 } 1668 for _, msg := range test.WantErrs { 1669 wantErrs[msg] = struct{}{} 1670 } 1671 1672 t.Logf( 1673 "\nprior: %sconfig: %splanned: %s", 1674 dump.Value(test.Planned), 1675 dump.Value(test.Config), 1676 dump.Value(test.Planned), 1677 ) 1678 for msg := range wantErrs { 1679 if _, ok := gotErrs[msg]; !ok { 1680 t.Errorf("missing expected error: %s", msg) 1681 } 1682 } 1683 for msg := range gotErrs { 1684 if _, ok := wantErrs[msg]; !ok { 1685 t.Errorf("unexpected extra error: %s", msg) 1686 } 1687 } 1688 }) 1689 } 1690 }