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