github.com/opentofu/opentofu@v1.7.1/internal/plans/objchange/objchange_test.go (about) 1 // Copyright (c) The OpenTofu Authors 2 // SPDX-License-Identifier: MPL-2.0 3 // Copyright (c) 2023 HashiCorp, Inc. 4 // SPDX-License-Identifier: MPL-2.0 5 6 package objchange 7 8 import ( 9 "testing" 10 11 "github.com/apparentlymart/go-dump/dump" 12 "github.com/zclconf/go-cty/cty" 13 14 "github.com/opentofu/opentofu/internal/configs/configschema" 15 ) 16 17 func TestProposedNew(t *testing.T) { 18 tests := map[string]struct { 19 Schema *configschema.Block 20 Prior cty.Value 21 Config cty.Value 22 Want cty.Value 23 }{ 24 "empty": { 25 &configschema.Block{}, 26 cty.EmptyObjectVal, 27 cty.EmptyObjectVal, 28 cty.EmptyObjectVal, 29 }, 30 "no prior": { 31 &configschema.Block{ 32 Attributes: map[string]*configschema.Attribute{ 33 "foo": { 34 Type: cty.String, 35 Optional: true, 36 }, 37 "bar": { 38 Type: cty.String, 39 Computed: true, 40 }, 41 "bloop": { 42 NestedType: &configschema.Object{ 43 Nesting: configschema.NestingSingle, 44 Attributes: map[string]*configschema.Attribute{ 45 "blop": { 46 Type: cty.String, 47 Required: true, 48 }, 49 }, 50 }, 51 Computed: true, 52 }, 53 }, 54 BlockTypes: map[string]*configschema.NestedBlock{ 55 "baz": { 56 Nesting: configschema.NestingSingle, 57 Block: configschema.Block{ 58 Attributes: map[string]*configschema.Attribute{ 59 "boz": { 60 Type: cty.String, 61 Optional: true, 62 Computed: true, 63 }, 64 "biz": { 65 Type: cty.String, 66 Optional: true, 67 Computed: true, 68 }, 69 }, 70 }, 71 }, 72 }, 73 }, 74 cty.NullVal(cty.DynamicPseudoType), 75 cty.ObjectVal(map[string]cty.Value{ 76 "foo": cty.StringVal("hello"), 77 "bloop": cty.NullVal(cty.Object(map[string]cty.Type{ 78 "blop": cty.String, 79 })), 80 "bar": cty.NullVal(cty.String), 81 "baz": cty.ObjectVal(map[string]cty.Value{ 82 "boz": cty.StringVal("world"), 83 84 // An unknown in the config represents a situation where 85 // an argument is explicitly set to an expression result 86 // that is derived from an unknown value. This is distinct 87 // from leaving it null, which allows the provider itself 88 // to decide the value during PlanResourceChange. 89 "biz": cty.UnknownVal(cty.String), 90 }), 91 }), 92 cty.ObjectVal(map[string]cty.Value{ 93 "foo": cty.StringVal("hello"), 94 95 // unset computed attributes are null in the proposal; provider 96 // usually changes them to "unknown" during PlanResourceChange, 97 // to indicate that the value will be decided during apply. 98 "bar": cty.NullVal(cty.String), 99 "bloop": cty.NullVal(cty.Object(map[string]cty.Type{ 100 "blop": cty.String, 101 })), 102 103 "baz": cty.ObjectVal(map[string]cty.Value{ 104 "boz": cty.StringVal("world"), 105 "biz": cty.UnknownVal(cty.String), // explicit unknown preserved from config 106 }), 107 }), 108 }, 109 "null block remains null": { 110 &configschema.Block{ 111 Attributes: map[string]*configschema.Attribute{ 112 "foo": { 113 Type: cty.String, 114 Optional: true, 115 }, 116 "bloop": { 117 NestedType: &configschema.Object{ 118 Nesting: configschema.NestingSingle, 119 Attributes: map[string]*configschema.Attribute{ 120 "blop": { 121 Type: cty.String, 122 Required: true, 123 }, 124 }, 125 }, 126 Computed: true, 127 }, 128 }, 129 BlockTypes: map[string]*configschema.NestedBlock{ 130 "baz": { 131 Nesting: configschema.NestingSingle, 132 Block: configschema.Block{ 133 Attributes: map[string]*configschema.Attribute{ 134 "boz": { 135 Type: cty.String, 136 Optional: true, 137 Computed: true, 138 }, 139 }, 140 }, 141 }, 142 }, 143 }, 144 cty.NullVal(cty.DynamicPseudoType), 145 cty.ObjectVal(map[string]cty.Value{ 146 "foo": cty.StringVal("bar"), 147 "bloop": cty.NullVal(cty.Object(map[string]cty.Type{ 148 "blop": cty.String, 149 })), 150 "baz": cty.NullVal(cty.Object(map[string]cty.Type{ 151 "boz": cty.String, 152 })), 153 }), 154 // The bloop attribue and baz block does not exist in the config, 155 // and therefore shouldn't be planned. 156 cty.ObjectVal(map[string]cty.Value{ 157 "foo": cty.StringVal("bar"), 158 "bloop": cty.NullVal(cty.Object(map[string]cty.Type{ 159 "blop": cty.String, 160 })), 161 "baz": cty.NullVal(cty.Object(map[string]cty.Type{ 162 "boz": cty.String, 163 })), 164 }), 165 }, 166 "no prior with set": { 167 // This one is here because our handling of sets is more complex 168 // than others (due to the fuzzy correlation heuristic) and 169 // historically that caused us some panic-related grief. 170 &configschema.Block{ 171 BlockTypes: map[string]*configschema.NestedBlock{ 172 "baz": { 173 Nesting: configschema.NestingSet, 174 Block: configschema.Block{ 175 Attributes: map[string]*configschema.Attribute{ 176 "boz": { 177 Type: cty.String, 178 Optional: true, 179 Computed: true, 180 }, 181 }, 182 }, 183 }, 184 }, 185 Attributes: map[string]*configschema.Attribute{ 186 "bloop": { 187 NestedType: &configschema.Object{ 188 Nesting: configschema.NestingSet, 189 Attributes: map[string]*configschema.Attribute{ 190 "blop": { 191 Type: cty.String, 192 Required: true, 193 }, 194 }, 195 }, 196 Computed: true, 197 Optional: true, 198 }, 199 }, 200 }, 201 cty.NullVal(cty.DynamicPseudoType), 202 cty.ObjectVal(map[string]cty.Value{ 203 "baz": cty.SetVal([]cty.Value{ 204 cty.ObjectVal(map[string]cty.Value{ 205 "boz": cty.StringVal("world"), 206 }), 207 }), 208 "bloop": cty.SetVal([]cty.Value{ 209 cty.ObjectVal(map[string]cty.Value{ 210 "blop": cty.StringVal("blub"), 211 }), 212 }), 213 }), 214 cty.ObjectVal(map[string]cty.Value{ 215 "baz": cty.SetVal([]cty.Value{ 216 cty.ObjectVal(map[string]cty.Value{ 217 "boz": cty.StringVal("world"), 218 }), 219 }), 220 "bloop": cty.SetVal([]cty.Value{ 221 cty.ObjectVal(map[string]cty.Value{ 222 "blop": cty.StringVal("blub"), 223 }), 224 }), 225 }), 226 }, 227 "prior attributes": { 228 &configschema.Block{ 229 Attributes: map[string]*configschema.Attribute{ 230 "foo": { 231 Type: cty.String, 232 Optional: true, 233 }, 234 "bar": { 235 Type: cty.String, 236 Computed: true, 237 }, 238 "baz": { 239 Type: cty.String, 240 Optional: true, 241 Computed: true, 242 }, 243 "boz": { 244 Type: cty.String, 245 Optional: true, 246 Computed: true, 247 }, 248 "bloop": { 249 NestedType: &configschema.Object{ 250 Nesting: configschema.NestingSingle, 251 Attributes: map[string]*configschema.Attribute{ 252 "blop": { 253 Type: cty.String, 254 Required: true, 255 }, 256 }, 257 }, 258 Optional: true, 259 }, 260 }, 261 }, 262 cty.ObjectVal(map[string]cty.Value{ 263 "foo": cty.StringVal("bonjour"), 264 "bar": cty.StringVal("petit dejeuner"), 265 "baz": cty.StringVal("grande dejeuner"), 266 "boz": cty.StringVal("a la monde"), 267 "bloop": cty.ObjectVal(map[string]cty.Value{ 268 "blop": cty.StringVal("glub"), 269 }), 270 }), 271 cty.ObjectVal(map[string]cty.Value{ 272 "foo": cty.StringVal("hello"), 273 "bar": cty.NullVal(cty.String), 274 "baz": cty.NullVal(cty.String), 275 "boz": cty.StringVal("world"), 276 "bloop": cty.ObjectVal(map[string]cty.Value{ 277 "blop": cty.StringVal("bleep"), 278 }), 279 }), 280 cty.ObjectVal(map[string]cty.Value{ 281 "foo": cty.StringVal("hello"), 282 "bar": cty.StringVal("petit dejeuner"), 283 "baz": cty.StringVal("grande dejeuner"), 284 "boz": cty.StringVal("world"), 285 "bloop": cty.ObjectVal(map[string]cty.Value{ 286 "blop": cty.StringVal("bleep"), 287 }), 288 }), 289 }, 290 "prior nested single": { 291 &configschema.Block{ 292 BlockTypes: map[string]*configschema.NestedBlock{ 293 "foo": { 294 Nesting: configschema.NestingSingle, 295 Block: configschema.Block{ 296 Attributes: map[string]*configschema.Attribute{ 297 "bar": { 298 Type: cty.String, 299 Optional: true, 300 Computed: true, 301 }, 302 "baz": { 303 Type: cty.String, 304 Optional: true, 305 Computed: true, 306 }, 307 }, 308 }, 309 }, 310 }, 311 Attributes: map[string]*configschema.Attribute{ 312 "bloop": { 313 NestedType: &configschema.Object{ 314 Nesting: configschema.NestingSingle, 315 Attributes: map[string]*configschema.Attribute{ 316 "blop": { 317 Type: cty.String, 318 Required: true, 319 }, 320 "bleep": { 321 Type: cty.String, 322 Optional: true, 323 }, 324 }, 325 }, 326 Optional: true, 327 }, 328 }, 329 }, 330 cty.ObjectVal(map[string]cty.Value{ 331 "foo": cty.ObjectVal(map[string]cty.Value{ 332 "bar": cty.StringVal("beep"), 333 "baz": cty.StringVal("boop"), 334 }), 335 "bloop": cty.ObjectVal(map[string]cty.Value{ 336 "blop": cty.StringVal("glub"), 337 "bleep": cty.NullVal(cty.String), 338 }), 339 }), 340 cty.ObjectVal(map[string]cty.Value{ 341 "foo": cty.ObjectVal(map[string]cty.Value{ 342 "bar": cty.StringVal("bap"), 343 "baz": cty.NullVal(cty.String), 344 }), 345 "bloop": cty.ObjectVal(map[string]cty.Value{ 346 "blop": cty.StringVal("glub"), 347 "bleep": cty.StringVal("beep"), 348 }), 349 }), 350 cty.ObjectVal(map[string]cty.Value{ 351 "foo": cty.ObjectVal(map[string]cty.Value{ 352 "bar": cty.StringVal("bap"), 353 "baz": cty.StringVal("boop"), 354 }), 355 "bloop": cty.ObjectVal(map[string]cty.Value{ 356 "blop": cty.StringVal("glub"), 357 "bleep": cty.StringVal("beep"), 358 }), 359 }), 360 }, 361 "prior nested single to null": { 362 &configschema.Block{ 363 BlockTypes: map[string]*configschema.NestedBlock{ 364 "foo": { 365 Nesting: configschema.NestingSingle, 366 Block: configschema.Block{ 367 Attributes: map[string]*configschema.Attribute{ 368 "bar": { 369 Type: cty.String, 370 Optional: true, 371 Computed: true, 372 }, 373 "baz": { 374 Type: cty.String, 375 Optional: true, 376 Computed: true, 377 }, 378 }, 379 }, 380 }, 381 }, 382 Attributes: map[string]*configschema.Attribute{ 383 "bloop": { 384 NestedType: &configschema.Object{ 385 Nesting: configschema.NestingSingle, 386 Attributes: map[string]*configschema.Attribute{ 387 "blop": { 388 Type: cty.String, 389 Required: true, 390 }, 391 "bleep": { 392 Type: cty.String, 393 Optional: true, 394 }, 395 }, 396 }, 397 Optional: true, 398 }, 399 }, 400 }, 401 cty.ObjectVal(map[string]cty.Value{ 402 "foo": cty.ObjectVal(map[string]cty.Value{ 403 "bar": cty.StringVal("beep"), 404 "baz": cty.StringVal("boop"), 405 }), 406 "bloop": cty.ObjectVal(map[string]cty.Value{ 407 "blop": cty.StringVal("glub"), 408 "bleep": cty.NullVal(cty.String), 409 }), 410 }), 411 cty.ObjectVal(map[string]cty.Value{ 412 "foo": cty.NullVal(cty.Object(map[string]cty.Type{ 413 "bar": cty.String, 414 "baz": cty.String, 415 })), 416 "bloop": cty.NullVal(cty.Object(map[string]cty.Type{ 417 "blop": cty.String, 418 "bleep": cty.String, 419 })), 420 }), 421 cty.ObjectVal(map[string]cty.Value{ 422 "foo": cty.NullVal(cty.Object(map[string]cty.Type{ 423 "bar": cty.String, 424 "baz": cty.String, 425 })), 426 "bloop": cty.NullVal(cty.Object(map[string]cty.Type{ 427 "blop": cty.String, 428 "bleep": cty.String, 429 })), 430 }), 431 }, 432 433 "prior optional computed nested single to null": { 434 &configschema.Block{ 435 Attributes: map[string]*configschema.Attribute{ 436 "bloop": { 437 NestedType: &configschema.Object{ 438 Nesting: configschema.NestingSingle, 439 Attributes: map[string]*configschema.Attribute{ 440 "blop": { 441 Type: cty.String, 442 Required: true, 443 }, 444 "bleep": { 445 Type: cty.String, 446 Optional: true, 447 }, 448 }, 449 }, 450 Optional: true, 451 Computed: true, 452 }, 453 }, 454 }, 455 cty.ObjectVal(map[string]cty.Value{ 456 "bloop": cty.ObjectVal(map[string]cty.Value{ 457 "blop": cty.StringVal("glub"), 458 "bleep": cty.NullVal(cty.String), 459 }), 460 }), 461 cty.ObjectVal(map[string]cty.Value{ 462 "bloop": cty.NullVal(cty.Object(map[string]cty.Type{ 463 "blop": cty.String, 464 "bleep": cty.String, 465 })), 466 }), 467 cty.ObjectVal(map[string]cty.Value{ 468 "bloop": cty.NullVal(cty.Object(map[string]cty.Type{ 469 "blop": cty.String, 470 "bleep": cty.String, 471 })), 472 }), 473 }, 474 475 "prior nested list": { 476 &configschema.Block{ 477 BlockTypes: map[string]*configschema.NestedBlock{ 478 "foo": { 479 Nesting: configschema.NestingList, 480 Block: configschema.Block{ 481 Attributes: map[string]*configschema.Attribute{ 482 "bar": { 483 Type: cty.String, 484 Optional: true, 485 Computed: true, 486 }, 487 "baz": { 488 Type: cty.String, 489 Optional: true, 490 Computed: true, 491 }, 492 }, 493 }, 494 }, 495 }, 496 Attributes: map[string]*configschema.Attribute{ 497 "bloop": { 498 NestedType: &configschema.Object{ 499 Nesting: configschema.NestingList, 500 Attributes: map[string]*configschema.Attribute{ 501 "blop": { 502 Type: cty.String, 503 Required: true, 504 }, 505 }, 506 }, 507 Optional: true, 508 }, 509 }, 510 }, 511 cty.ObjectVal(map[string]cty.Value{ 512 "foo": cty.ListVal([]cty.Value{ 513 cty.ObjectVal(map[string]cty.Value{ 514 "bar": cty.StringVal("beep"), 515 "baz": cty.StringVal("boop"), 516 }), 517 }), 518 "bloop": cty.ListVal([]cty.Value{ 519 cty.ObjectVal(map[string]cty.Value{ 520 "blop": cty.StringVal("bar"), 521 }), 522 cty.ObjectVal(map[string]cty.Value{ 523 "blop": cty.StringVal("baz"), 524 }), 525 }), 526 }), 527 cty.ObjectVal(map[string]cty.Value{ 528 "foo": cty.ListVal([]cty.Value{ 529 cty.ObjectVal(map[string]cty.Value{ 530 "bar": cty.StringVal("bap"), 531 "baz": cty.NullVal(cty.String), 532 }), 533 cty.ObjectVal(map[string]cty.Value{ 534 "bar": cty.StringVal("blep"), 535 "baz": cty.NullVal(cty.String), 536 }), 537 }), 538 "bloop": cty.ListVal([]cty.Value{ 539 cty.ObjectVal(map[string]cty.Value{ 540 "blop": cty.StringVal("bar"), 541 }), 542 cty.ObjectVal(map[string]cty.Value{ 543 "blop": cty.StringVal("baz"), 544 }), 545 }), 546 }), 547 cty.ObjectVal(map[string]cty.Value{ 548 "foo": cty.ListVal([]cty.Value{ 549 cty.ObjectVal(map[string]cty.Value{ 550 "bar": cty.StringVal("bap"), 551 "baz": cty.StringVal("boop"), 552 }), 553 cty.ObjectVal(map[string]cty.Value{ 554 "bar": cty.StringVal("blep"), 555 "baz": cty.NullVal(cty.String), 556 }), 557 }), 558 "bloop": cty.ListVal([]cty.Value{ 559 cty.ObjectVal(map[string]cty.Value{ 560 "blop": cty.StringVal("bar"), 561 }), 562 cty.ObjectVal(map[string]cty.Value{ 563 "blop": cty.StringVal("baz"), 564 }), 565 }), 566 }), 567 }, 568 "prior nested list with dynamic": { 569 &configschema.Block{ 570 BlockTypes: map[string]*configschema.NestedBlock{ 571 "foo": { 572 Nesting: configschema.NestingList, 573 Block: configschema.Block{ 574 Attributes: map[string]*configschema.Attribute{ 575 "bar": { 576 Type: cty.String, 577 Optional: true, 578 Computed: true, 579 }, 580 "baz": { 581 Type: cty.DynamicPseudoType, 582 Optional: true, 583 Computed: true, 584 }, 585 }, 586 }, 587 }, 588 }, 589 Attributes: map[string]*configschema.Attribute{ 590 "bloop": { 591 NestedType: &configschema.Object{ 592 Nesting: configschema.NestingList, 593 Attributes: map[string]*configschema.Attribute{ 594 "blop": { 595 Type: cty.DynamicPseudoType, 596 Required: true, 597 }, 598 "blub": { 599 Type: cty.DynamicPseudoType, 600 Optional: true, 601 }, 602 }, 603 }, 604 Optional: true, 605 }, 606 }, 607 }, 608 cty.ObjectVal(map[string]cty.Value{ 609 "foo": cty.TupleVal([]cty.Value{ 610 cty.ObjectVal(map[string]cty.Value{ 611 "bar": cty.StringVal("beep"), 612 "baz": cty.StringVal("boop"), 613 }), 614 }), 615 "bloop": cty.ListVal([]cty.Value{ 616 cty.ObjectVal(map[string]cty.Value{ 617 "blop": cty.StringVal("bar"), 618 "blub": cty.StringVal("glub"), 619 }), 620 cty.ObjectVal(map[string]cty.Value{ 621 "blop": cty.StringVal("baz"), 622 "blub": cty.NullVal(cty.String), 623 }), 624 }), 625 }), 626 cty.ObjectVal(map[string]cty.Value{ 627 "foo": cty.TupleVal([]cty.Value{ 628 cty.ObjectVal(map[string]cty.Value{ 629 "bar": cty.StringVal("bap"), 630 "baz": cty.NullVal(cty.String), 631 }), 632 cty.ObjectVal(map[string]cty.Value{ 633 "bar": cty.StringVal("blep"), 634 "baz": cty.NullVal(cty.String), 635 }), 636 }), 637 "bloop": cty.ListVal([]cty.Value{ 638 cty.ObjectVal(map[string]cty.Value{ 639 "blop": cty.StringVal("bar"), 640 "blub": cty.NullVal(cty.String), 641 }), 642 }), 643 }), 644 cty.ObjectVal(map[string]cty.Value{ 645 "foo": cty.TupleVal([]cty.Value{ 646 cty.ObjectVal(map[string]cty.Value{ 647 "bar": cty.StringVal("bap"), 648 "baz": cty.StringVal("boop"), 649 }), 650 cty.ObjectVal(map[string]cty.Value{ 651 "bar": cty.StringVal("blep"), 652 "baz": cty.NullVal(cty.String), 653 }), 654 }), 655 "bloop": cty.ListVal([]cty.Value{ 656 cty.ObjectVal(map[string]cty.Value{ 657 "blop": cty.StringVal("bar"), 658 "blub": cty.NullVal(cty.String), 659 }), 660 }), 661 }), 662 }, 663 "prior nested map": { 664 &configschema.Block{ 665 BlockTypes: map[string]*configschema.NestedBlock{ 666 "foo": { 667 Nesting: configschema.NestingMap, 668 Block: configschema.Block{ 669 Attributes: map[string]*configschema.Attribute{ 670 "bar": { 671 Type: cty.String, 672 Optional: true, 673 Computed: true, 674 }, 675 "baz": { 676 Type: cty.String, 677 Optional: true, 678 Computed: true, 679 }, 680 }, 681 }, 682 }, 683 }, 684 Attributes: map[string]*configschema.Attribute{ 685 "bloop": { 686 NestedType: &configschema.Object{ 687 Nesting: configschema.NestingMap, 688 Attributes: map[string]*configschema.Attribute{ 689 "blop": { 690 Type: cty.String, 691 Required: true, 692 }, 693 }, 694 }, 695 Optional: true, 696 }, 697 }, 698 }, 699 cty.ObjectVal(map[string]cty.Value{ 700 "foo": cty.MapVal(map[string]cty.Value{ 701 "a": cty.ObjectVal(map[string]cty.Value{ 702 "bar": cty.StringVal("beep"), 703 "baz": cty.StringVal("boop"), 704 }), 705 "b": cty.ObjectVal(map[string]cty.Value{ 706 "bar": cty.StringVal("blep"), 707 "baz": cty.StringVal("boot"), 708 }), 709 }), 710 "bloop": cty.MapVal(map[string]cty.Value{ 711 "a": cty.ObjectVal(map[string]cty.Value{ 712 "blop": cty.StringVal("glub"), 713 }), 714 "b": cty.ObjectVal(map[string]cty.Value{ 715 "blop": cty.StringVal("blub"), 716 }), 717 }), 718 }), 719 cty.ObjectVal(map[string]cty.Value{ 720 "foo": cty.MapVal(map[string]cty.Value{ 721 "a": cty.ObjectVal(map[string]cty.Value{ 722 "bar": cty.StringVal("bap"), 723 "baz": cty.NullVal(cty.String), 724 }), 725 "c": cty.ObjectVal(map[string]cty.Value{ 726 "bar": cty.StringVal("bosh"), 727 "baz": cty.NullVal(cty.String), 728 }), 729 }), 730 "bloop": cty.MapVal(map[string]cty.Value{ 731 "a": cty.ObjectVal(map[string]cty.Value{ 732 "blop": cty.StringVal("glub"), 733 }), 734 "c": cty.ObjectVal(map[string]cty.Value{ 735 "blop": cty.StringVal("blub"), 736 }), 737 }), 738 }), 739 cty.ObjectVal(map[string]cty.Value{ 740 "foo": cty.MapVal(map[string]cty.Value{ 741 "a": cty.ObjectVal(map[string]cty.Value{ 742 "bar": cty.StringVal("bap"), 743 "baz": cty.StringVal("boop"), 744 }), 745 "c": cty.ObjectVal(map[string]cty.Value{ 746 "bar": cty.StringVal("bosh"), 747 "baz": cty.NullVal(cty.String), 748 }), 749 }), 750 "bloop": cty.MapVal(map[string]cty.Value{ 751 "a": cty.ObjectVal(map[string]cty.Value{ 752 "blop": cty.StringVal("glub"), 753 }), 754 "c": cty.ObjectVal(map[string]cty.Value{ 755 "blop": cty.StringVal("blub"), 756 }), 757 }), 758 }), 759 }, 760 761 "prior optional computed nested map elem to null": { 762 &configschema.Block{ 763 Attributes: map[string]*configschema.Attribute{ 764 "bloop": { 765 NestedType: &configschema.Object{ 766 Nesting: configschema.NestingMap, 767 Attributes: map[string]*configschema.Attribute{ 768 "blop": { 769 Type: cty.String, 770 Optional: true, 771 }, 772 "bleep": { 773 Type: cty.String, 774 Optional: true, 775 Computed: true, 776 }, 777 }, 778 }, 779 Optional: true, 780 }, 781 }, 782 }, 783 cty.ObjectVal(map[string]cty.Value{ 784 "bloop": cty.MapVal(map[string]cty.Value{ 785 "a": cty.ObjectVal(map[string]cty.Value{ 786 "blop": cty.StringVal("glub"), 787 "bleep": cty.StringVal("computed"), 788 }), 789 "b": cty.ObjectVal(map[string]cty.Value{ 790 "blop": cty.StringVal("blub"), 791 "bleep": cty.StringVal("computed"), 792 }), 793 }), 794 }), 795 cty.ObjectVal(map[string]cty.Value{ 796 "bloop": cty.MapVal(map[string]cty.Value{ 797 "a": cty.NullVal(cty.Object(map[string]cty.Type{ 798 "blop": cty.String, 799 "bleep": cty.String, 800 })), 801 "c": cty.ObjectVal(map[string]cty.Value{ 802 "blop": cty.StringVal("blub"), 803 "bleep": cty.NullVal(cty.String), 804 }), 805 }), 806 }), 807 cty.ObjectVal(map[string]cty.Value{ 808 "bloop": cty.MapVal(map[string]cty.Value{ 809 "a": cty.NullVal(cty.Object(map[string]cty.Type{ 810 "blop": cty.String, 811 "bleep": cty.String, 812 })), 813 "c": cty.ObjectVal(map[string]cty.Value{ 814 "blop": cty.StringVal("blub"), 815 "bleep": cty.NullVal(cty.String), 816 }), 817 }), 818 }), 819 }, 820 821 "prior optional computed nested map to null": { 822 &configschema.Block{ 823 Attributes: map[string]*configschema.Attribute{ 824 "bloop": { 825 NestedType: &configschema.Object{ 826 Nesting: configschema.NestingMap, 827 Attributes: map[string]*configschema.Attribute{ 828 "blop": { 829 Type: cty.String, 830 Optional: true, 831 }, 832 "bleep": { 833 Type: cty.String, 834 Optional: true, 835 Computed: true, 836 }, 837 }, 838 }, 839 Optional: true, 840 Computed: true, 841 }, 842 }, 843 }, 844 cty.ObjectVal(map[string]cty.Value{ 845 "bloop": cty.MapVal(map[string]cty.Value{ 846 "a": cty.ObjectVal(map[string]cty.Value{ 847 "blop": cty.StringVal("glub"), 848 "bleep": cty.StringVal("computed"), 849 }), 850 "b": cty.ObjectVal(map[string]cty.Value{ 851 "blop": cty.StringVal("blub"), 852 "bleep": cty.StringVal("computed"), 853 }), 854 }), 855 }), 856 cty.ObjectVal(map[string]cty.Value{ 857 "bloop": cty.NullVal(cty.Map( 858 cty.Object(map[string]cty.Type{ 859 "blop": cty.String, 860 "bleep": cty.String, 861 }), 862 )), 863 }), 864 cty.ObjectVal(map[string]cty.Value{ 865 "bloop": cty.NullVal(cty.Map( 866 cty.Object(map[string]cty.Type{ 867 "blop": cty.String, 868 "bleep": cty.String, 869 }), 870 )), 871 }), 872 }, 873 874 "prior nested map with dynamic": { 875 &configschema.Block{ 876 BlockTypes: map[string]*configschema.NestedBlock{ 877 "foo": { 878 Nesting: configschema.NestingMap, 879 Block: configschema.Block{ 880 Attributes: map[string]*configschema.Attribute{ 881 "bar": { 882 Type: cty.String, 883 Optional: true, 884 Computed: true, 885 }, 886 "baz": { 887 Type: cty.DynamicPseudoType, 888 Optional: true, 889 Computed: true, 890 }, 891 }, 892 }, 893 }, 894 }, 895 Attributes: map[string]*configschema.Attribute{ 896 "bloop": { 897 NestedType: &configschema.Object{ 898 Nesting: configschema.NestingMap, 899 Attributes: map[string]*configschema.Attribute{ 900 "blop": { 901 Type: cty.DynamicPseudoType, 902 Required: true, 903 }, 904 }, 905 }, 906 Optional: true, 907 }, 908 }, 909 }, 910 cty.ObjectVal(map[string]cty.Value{ 911 "foo": cty.ObjectVal(map[string]cty.Value{ 912 "a": cty.ObjectVal(map[string]cty.Value{ 913 "bar": cty.StringVal("beep"), 914 "baz": cty.StringVal("boop"), 915 }), 916 "b": cty.ObjectVal(map[string]cty.Value{ 917 "bar": cty.StringVal("blep"), 918 "baz": cty.ListVal([]cty.Value{cty.StringVal("boot")}), 919 }), 920 }), 921 "bloop": cty.ObjectVal(map[string]cty.Value{ 922 "a": cty.ObjectVal(map[string]cty.Value{ 923 "blop": cty.StringVal("glub"), 924 }), 925 "b": cty.ObjectVal(map[string]cty.Value{ 926 "blop": cty.NumberIntVal(13), 927 }), 928 }), 929 }), 930 cty.ObjectVal(map[string]cty.Value{ 931 "foo": cty.ObjectVal(map[string]cty.Value{ 932 "a": cty.ObjectVal(map[string]cty.Value{ 933 "bar": cty.StringVal("bap"), 934 "baz": cty.NullVal(cty.String), 935 }), 936 "c": cty.ObjectVal(map[string]cty.Value{ 937 "bar": cty.StringVal("bosh"), 938 "baz": cty.NullVal(cty.List(cty.String)), 939 }), 940 }), 941 "bloop": cty.ObjectVal(map[string]cty.Value{ 942 "a": cty.ObjectVal(map[string]cty.Value{ 943 "blop": cty.StringVal("blep"), 944 }), 945 "c": cty.ObjectVal(map[string]cty.Value{ 946 "blop": cty.NumberIntVal(13), 947 }), 948 }), 949 }), 950 cty.ObjectVal(map[string]cty.Value{ 951 "foo": cty.ObjectVal(map[string]cty.Value{ 952 "a": cty.ObjectVal(map[string]cty.Value{ 953 "bar": cty.StringVal("bap"), 954 "baz": cty.StringVal("boop"), 955 }), 956 "c": cty.ObjectVal(map[string]cty.Value{ 957 "bar": cty.StringVal("bosh"), 958 "baz": cty.NullVal(cty.List(cty.String)), 959 }), 960 }), 961 "bloop": cty.ObjectVal(map[string]cty.Value{ 962 "a": cty.ObjectVal(map[string]cty.Value{ 963 "blop": cty.StringVal("blep"), 964 }), 965 "c": cty.ObjectVal(map[string]cty.Value{ 966 "blop": cty.NumberIntVal(13), 967 }), 968 }), 969 }), 970 }, 971 "prior nested set": { 972 &configschema.Block{ 973 BlockTypes: map[string]*configschema.NestedBlock{ 974 "foo": { 975 Nesting: configschema.NestingSet, 976 Block: configschema.Block{ 977 Attributes: map[string]*configschema.Attribute{ 978 "bar": { 979 // This non-computed attribute will serve 980 // as our matching key for propagating 981 // "baz" from elements in the prior value. 982 Type: cty.String, 983 Optional: true, 984 }, 985 "baz": { 986 Type: cty.String, 987 Optional: true, 988 Computed: true, 989 }, 990 }, 991 }, 992 }, 993 }, 994 Attributes: map[string]*configschema.Attribute{ 995 "bloop": { 996 NestedType: &configschema.Object{ 997 Nesting: configschema.NestingSet, 998 Attributes: map[string]*configschema.Attribute{ 999 "blop": { 1000 Type: cty.String, 1001 Required: true, 1002 }, 1003 "bleep": { 1004 Type: cty.String, 1005 Optional: true, 1006 }, 1007 }, 1008 }, 1009 Optional: true, 1010 }, 1011 }, 1012 }, 1013 cty.ObjectVal(map[string]cty.Value{ 1014 "foo": cty.SetVal([]cty.Value{ 1015 cty.ObjectVal(map[string]cty.Value{ 1016 "bar": cty.StringVal("beep"), 1017 "baz": cty.StringVal("boop"), 1018 }), 1019 cty.ObjectVal(map[string]cty.Value{ 1020 "bar": cty.StringVal("blep"), 1021 "baz": cty.StringVal("boot"), 1022 }), 1023 }), 1024 "bloop": cty.SetVal([]cty.Value{ 1025 cty.ObjectVal(map[string]cty.Value{ 1026 "blop": cty.StringVal("glubglub"), 1027 "bleep": cty.NullVal(cty.String), 1028 }), 1029 cty.ObjectVal(map[string]cty.Value{ 1030 "blop": cty.StringVal("glubglub"), 1031 "bleep": cty.StringVal("beep"), 1032 }), 1033 }), 1034 }), 1035 cty.ObjectVal(map[string]cty.Value{ 1036 "foo": cty.SetVal([]cty.Value{ 1037 cty.ObjectVal(map[string]cty.Value{ 1038 "bar": cty.StringVal("beep"), 1039 "baz": cty.NullVal(cty.String), 1040 }), 1041 cty.ObjectVal(map[string]cty.Value{ 1042 "bar": cty.StringVal("bosh"), 1043 "baz": cty.NullVal(cty.String), 1044 }), 1045 }), 1046 "bloop": cty.SetVal([]cty.Value{ 1047 cty.ObjectVal(map[string]cty.Value{ 1048 "blop": cty.StringVal("glubglub"), 1049 "bleep": cty.NullVal(cty.String), 1050 }), 1051 cty.ObjectVal(map[string]cty.Value{ 1052 "blop": cty.StringVal("glub"), 1053 "bleep": cty.NullVal(cty.String), 1054 }), 1055 }), 1056 }), 1057 cty.ObjectVal(map[string]cty.Value{ 1058 "foo": cty.SetVal([]cty.Value{ 1059 cty.ObjectVal(map[string]cty.Value{ 1060 "bar": cty.StringVal("beep"), 1061 "baz": cty.StringVal("boop"), 1062 }), 1063 cty.ObjectVal(map[string]cty.Value{ 1064 "bar": cty.StringVal("bosh"), 1065 "baz": cty.NullVal(cty.String), 1066 }), 1067 }), 1068 "bloop": cty.SetVal([]cty.Value{ 1069 cty.ObjectVal(map[string]cty.Value{ 1070 "blop": cty.StringVal("glubglub"), 1071 "bleep": cty.NullVal(cty.String), 1072 }), 1073 cty.ObjectVal(map[string]cty.Value{ 1074 "blop": cty.StringVal("glub"), 1075 "bleep": cty.NullVal(cty.String), 1076 }), 1077 }), 1078 }), 1079 }, 1080 1081 "set with partial optional computed change": { 1082 &configschema.Block{ 1083 BlockTypes: map[string]*configschema.NestedBlock{ 1084 "multi": { 1085 Nesting: configschema.NestingSet, 1086 Block: configschema.Block{ 1087 Attributes: map[string]*configschema.Attribute{ 1088 "opt": { 1089 Type: cty.String, 1090 Optional: true, 1091 }, 1092 "cmp": { 1093 Type: cty.String, 1094 Optional: true, 1095 Computed: true, 1096 }, 1097 }, 1098 }, 1099 }, 1100 }, 1101 }, 1102 cty.ObjectVal(map[string]cty.Value{ 1103 "multi": cty.SetVal([]cty.Value{ 1104 cty.ObjectVal(map[string]cty.Value{ 1105 "opt": cty.StringVal("one"), 1106 "cmp": cty.StringVal("OK"), 1107 }), 1108 cty.ObjectVal(map[string]cty.Value{ 1109 "opt": cty.StringVal("two"), 1110 "cmp": cty.StringVal("OK"), 1111 }), 1112 }), 1113 }), 1114 1115 cty.ObjectVal(map[string]cty.Value{ 1116 "multi": cty.SetVal([]cty.Value{ 1117 cty.ObjectVal(map[string]cty.Value{ 1118 "opt": cty.StringVal("one"), 1119 "cmp": cty.NullVal(cty.String), 1120 }), 1121 cty.ObjectVal(map[string]cty.Value{ 1122 "opt": cty.StringVal("replaced"), 1123 "cmp": cty.NullVal(cty.String), 1124 }), 1125 }), 1126 }), 1127 // "one" can be correlated because it is a non-computed value in 1128 // the configuration. 1129 cty.ObjectVal(map[string]cty.Value{ 1130 "multi": cty.SetVal([]cty.Value{ 1131 cty.ObjectVal(map[string]cty.Value{ 1132 "opt": cty.StringVal("one"), 1133 "cmp": cty.StringVal("OK"), 1134 }), 1135 cty.ObjectVal(map[string]cty.Value{ 1136 "opt": cty.StringVal("replaced"), 1137 "cmp": cty.NullVal(cty.String), 1138 }), 1139 }), 1140 }), 1141 }, 1142 1143 "set without partial optional computed change": { 1144 &configschema.Block{ 1145 BlockTypes: map[string]*configschema.NestedBlock{ 1146 "multi": { 1147 Nesting: configschema.NestingSet, 1148 Block: configschema.Block{ 1149 Attributes: map[string]*configschema.Attribute{ 1150 "opt": { 1151 Type: cty.String, 1152 Optional: true, 1153 Computed: true, 1154 }, 1155 "req": { 1156 Type: cty.String, 1157 Required: true, 1158 }, 1159 }, 1160 }, 1161 }, 1162 }, 1163 }, 1164 cty.ObjectVal(map[string]cty.Value{ 1165 "multi": cty.SetVal([]cty.Value{ 1166 cty.ObjectVal(map[string]cty.Value{ 1167 "opt": cty.StringVal("one"), 1168 "req": cty.StringVal("one"), 1169 }), 1170 cty.ObjectVal(map[string]cty.Value{ 1171 "opt": cty.StringVal("two"), 1172 "req": cty.StringVal("two"), 1173 }), 1174 }), 1175 }), 1176 cty.ObjectVal(map[string]cty.Value{ 1177 "multi": cty.SetVal([]cty.Value{ 1178 cty.ObjectVal(map[string]cty.Value{ 1179 "opt": cty.NullVal(cty.String), 1180 "req": cty.StringVal("one"), 1181 }), 1182 cty.ObjectVal(map[string]cty.Value{ 1183 "opt": cty.NullVal(cty.String), 1184 "req": cty.StringVal("two"), 1185 }), 1186 }), 1187 }), 1188 cty.ObjectVal(map[string]cty.Value{ 1189 "multi": cty.SetVal([]cty.Value{ 1190 cty.ObjectVal(map[string]cty.Value{ 1191 "opt": cty.StringVal("one"), 1192 "req": cty.StringVal("one"), 1193 }), 1194 cty.ObjectVal(map[string]cty.Value{ 1195 "opt": cty.StringVal("two"), 1196 "req": cty.StringVal("two"), 1197 }), 1198 }), 1199 }), 1200 }, 1201 1202 "sets differing only by unknown": { 1203 &configschema.Block{ 1204 BlockTypes: map[string]*configschema.NestedBlock{ 1205 "multi": { 1206 Nesting: configschema.NestingSet, 1207 Block: configschema.Block{ 1208 Attributes: map[string]*configschema.Attribute{ 1209 "optional": { 1210 Type: cty.String, 1211 Optional: true, 1212 Computed: true, 1213 }, 1214 }, 1215 }, 1216 }, 1217 }, 1218 Attributes: map[string]*configschema.Attribute{ 1219 "bloop": { 1220 NestedType: &configschema.Object{ 1221 Nesting: configschema.NestingSet, 1222 Attributes: map[string]*configschema.Attribute{ 1223 "blop": { 1224 Type: cty.String, 1225 Required: true, 1226 }, 1227 }, 1228 }, 1229 Optional: true, 1230 }, 1231 }, 1232 }, 1233 cty.NullVal(cty.DynamicPseudoType), 1234 cty.ObjectVal(map[string]cty.Value{ 1235 "multi": cty.SetVal([]cty.Value{ 1236 cty.ObjectVal(map[string]cty.Value{ 1237 "optional": cty.UnknownVal(cty.String), 1238 }), 1239 cty.ObjectVal(map[string]cty.Value{ 1240 "optional": cty.UnknownVal(cty.String), 1241 }), 1242 }), 1243 "bloop": cty.SetVal([]cty.Value{ 1244 cty.ObjectVal(map[string]cty.Value{ 1245 "blop": cty.UnknownVal(cty.String), 1246 }), 1247 cty.ObjectVal(map[string]cty.Value{ 1248 "blop": cty.UnknownVal(cty.String), 1249 }), 1250 }), 1251 }), 1252 cty.ObjectVal(map[string]cty.Value{ 1253 "multi": cty.SetVal([]cty.Value{ 1254 // These remain distinct because unknown values never 1255 // compare equal. They may be consolidated together once 1256 // the values become known, though. 1257 cty.ObjectVal(map[string]cty.Value{ 1258 "optional": cty.UnknownVal(cty.String), 1259 }), 1260 cty.ObjectVal(map[string]cty.Value{ 1261 "optional": cty.UnknownVal(cty.String), 1262 }), 1263 }), 1264 "bloop": cty.SetVal([]cty.Value{ 1265 cty.ObjectVal(map[string]cty.Value{ 1266 "blop": cty.UnknownVal(cty.String), 1267 }), 1268 cty.ObjectVal(map[string]cty.Value{ 1269 "blop": cty.UnknownVal(cty.String), 1270 }), 1271 }), 1272 }), 1273 }, 1274 "nested list in set": { 1275 &configschema.Block{ 1276 BlockTypes: map[string]*configschema.NestedBlock{ 1277 "foo": { 1278 Nesting: configschema.NestingSet, 1279 Block: configschema.Block{ 1280 BlockTypes: map[string]*configschema.NestedBlock{ 1281 "bar": { 1282 Nesting: configschema.NestingList, 1283 Block: configschema.Block{ 1284 Attributes: map[string]*configschema.Attribute{ 1285 "baz": { 1286 Type: cty.String, 1287 }, 1288 "qux": { 1289 Type: cty.String, 1290 Computed: true, 1291 Optional: true, 1292 }, 1293 }, 1294 }, 1295 }, 1296 }, 1297 }, 1298 }, 1299 }, 1300 }, 1301 cty.ObjectVal(map[string]cty.Value{ 1302 "foo": cty.SetVal([]cty.Value{ 1303 cty.ObjectVal(map[string]cty.Value{ 1304 "bar": cty.ListVal([]cty.Value{ 1305 cty.ObjectVal(map[string]cty.Value{ 1306 "baz": cty.StringVal("beep"), 1307 "qux": cty.StringVal("boop"), 1308 }), 1309 }), 1310 }), 1311 }), 1312 }), 1313 cty.ObjectVal(map[string]cty.Value{ 1314 "foo": cty.SetVal([]cty.Value{ 1315 cty.ObjectVal(map[string]cty.Value{ 1316 "bar": cty.ListVal([]cty.Value{ 1317 cty.ObjectVal(map[string]cty.Value{ 1318 "baz": cty.StringVal("beep"), 1319 "qux": cty.NullVal(cty.String), 1320 }), 1321 }), 1322 }), 1323 }), 1324 }), 1325 cty.ObjectVal(map[string]cty.Value{ 1326 "foo": cty.SetVal([]cty.Value{ 1327 cty.ObjectVal(map[string]cty.Value{ 1328 "bar": cty.ListVal([]cty.Value{ 1329 cty.ObjectVal(map[string]cty.Value{ 1330 "baz": cty.StringVal("beep"), 1331 "qux": cty.StringVal("boop"), 1332 }), 1333 }), 1334 }), 1335 }), 1336 }), 1337 }, 1338 "empty nested list in set": { 1339 &configschema.Block{ 1340 BlockTypes: map[string]*configschema.NestedBlock{ 1341 "foo": { 1342 Nesting: configschema.NestingSet, 1343 Block: configschema.Block{ 1344 BlockTypes: map[string]*configschema.NestedBlock{ 1345 "bar": { 1346 Nesting: configschema.NestingList, 1347 Block: configschema.Block{}, 1348 }, 1349 }, 1350 }, 1351 }, 1352 }, 1353 }, 1354 cty.ObjectVal(map[string]cty.Value{ 1355 "foo": cty.SetVal([]cty.Value{ 1356 cty.ObjectVal(map[string]cty.Value{ 1357 "bar": cty.ListValEmpty((&configschema.Block{}).ImpliedType()), 1358 }), 1359 }), 1360 }), 1361 cty.ObjectVal(map[string]cty.Value{ 1362 "foo": cty.SetVal([]cty.Value{ 1363 cty.ObjectVal(map[string]cty.Value{ 1364 "bar": cty.ListValEmpty((&configschema.Block{}).ImpliedType()), 1365 }), 1366 }), 1367 }), 1368 cty.ObjectVal(map[string]cty.Value{ 1369 "foo": cty.SetVal([]cty.Value{ 1370 cty.ObjectVal(map[string]cty.Value{ 1371 "bar": cty.ListValEmpty((&configschema.Block{}).ImpliedType()), 1372 }), 1373 }), 1374 }), 1375 }, 1376 "nested list with dynamic in set": { 1377 &configschema.Block{ 1378 BlockTypes: map[string]*configschema.NestedBlock{ 1379 "foo": { 1380 Nesting: configschema.NestingSet, 1381 Block: configschema.Block{ 1382 BlockTypes: map[string]*configschema.NestedBlock{ 1383 "bar": { 1384 Nesting: configschema.NestingList, 1385 Block: configschema.Block{ 1386 Attributes: map[string]*configschema.Attribute{ 1387 "baz": { 1388 Type: cty.DynamicPseudoType, 1389 }, 1390 }, 1391 }, 1392 }, 1393 }, 1394 }, 1395 }, 1396 }, 1397 }, 1398 cty.ObjectVal(map[string]cty.Value{ 1399 "foo": cty.SetVal([]cty.Value{ 1400 cty.ObjectVal(map[string]cty.Value{ 1401 "bar": cty.TupleVal([]cty.Value{ 1402 cty.ObjectVal(map[string]cty.Value{ 1403 "baz": cty.StringVal("true"), 1404 }), 1405 cty.ObjectVal(map[string]cty.Value{ 1406 "baz": cty.ListVal([]cty.Value{cty.StringVal("true")}), 1407 }), 1408 }), 1409 }), 1410 }), 1411 }), 1412 cty.ObjectVal(map[string]cty.Value{ 1413 "foo": cty.SetVal([]cty.Value{ 1414 cty.ObjectVal(map[string]cty.Value{ 1415 "bar": cty.TupleVal([]cty.Value{ 1416 cty.ObjectVal(map[string]cty.Value{ 1417 "baz": cty.StringVal("true"), 1418 }), 1419 cty.ObjectVal(map[string]cty.Value{ 1420 "baz": cty.ListVal([]cty.Value{cty.StringVal("true")}), 1421 }), 1422 }), 1423 }), 1424 }), 1425 }), 1426 cty.ObjectVal(map[string]cty.Value{ 1427 "foo": cty.SetVal([]cty.Value{ 1428 cty.ObjectVal(map[string]cty.Value{ 1429 "bar": cty.TupleVal([]cty.Value{ 1430 cty.ObjectVal(map[string]cty.Value{ 1431 "baz": cty.StringVal("true"), 1432 }), 1433 cty.ObjectVal(map[string]cty.Value{ 1434 "baz": cty.ListVal([]cty.Value{cty.StringVal("true")}), 1435 }), 1436 }), 1437 }), 1438 }), 1439 }), 1440 }, 1441 "nested map with dynamic in set": { 1442 &configschema.Block{ 1443 BlockTypes: map[string]*configschema.NestedBlock{ 1444 "foo": { 1445 Nesting: configschema.NestingSet, 1446 Block: configschema.Block{ 1447 BlockTypes: map[string]*configschema.NestedBlock{ 1448 "bar": { 1449 Nesting: configschema.NestingMap, 1450 Block: configschema.Block{ 1451 Attributes: map[string]*configschema.Attribute{ 1452 "baz": { 1453 Type: cty.DynamicPseudoType, 1454 Optional: true, 1455 }, 1456 }, 1457 }, 1458 }, 1459 }, 1460 }, 1461 }, 1462 }, 1463 }, 1464 cty.ObjectVal(map[string]cty.Value{ 1465 "foo": cty.SetVal([]cty.Value{ 1466 cty.ObjectVal(map[string]cty.Value{ 1467 "bar": cty.ObjectVal(map[string]cty.Value{ 1468 "bing": cty.ObjectVal(map[string]cty.Value{ 1469 "baz": cty.StringVal("true"), 1470 }), 1471 "bang": cty.ObjectVal(map[string]cty.Value{ 1472 "baz": cty.ListVal([]cty.Value{cty.StringVal("true")}), 1473 }), 1474 }), 1475 }), 1476 }), 1477 }), 1478 cty.ObjectVal(map[string]cty.Value{ 1479 "foo": cty.SetVal([]cty.Value{ 1480 cty.ObjectVal(map[string]cty.Value{ 1481 "bar": cty.ObjectVal(map[string]cty.Value{ 1482 "bing": cty.ObjectVal(map[string]cty.Value{ 1483 "baz": cty.ListVal([]cty.Value{cty.StringVal("true")}), 1484 }), 1485 }), 1486 }), 1487 }), 1488 }), 1489 cty.ObjectVal(map[string]cty.Value{ 1490 "foo": cty.SetVal([]cty.Value{ 1491 cty.ObjectVal(map[string]cty.Value{ 1492 "bar": cty.ObjectVal(map[string]cty.Value{ 1493 "bing": cty.ObjectVal(map[string]cty.Value{ 1494 "baz": cty.ListVal([]cty.Value{cty.StringVal("true")}), 1495 }), 1496 }), 1497 }), 1498 }), 1499 }), 1500 }, 1501 "empty nested map in set": { 1502 &configschema.Block{ 1503 BlockTypes: map[string]*configschema.NestedBlock{ 1504 "foo": { 1505 Nesting: configschema.NestingSet, 1506 Block: configschema.Block{ 1507 BlockTypes: map[string]*configschema.NestedBlock{ 1508 "bar": { 1509 Nesting: configschema.NestingMap, 1510 Block: configschema.Block{ 1511 Attributes: map[string]*configschema.Attribute{ 1512 "baz": { 1513 Type: cty.String, 1514 Optional: true, 1515 }, 1516 }, 1517 }, 1518 }, 1519 }, 1520 }, 1521 }, 1522 }, 1523 }, 1524 cty.ObjectVal(map[string]cty.Value{ 1525 "foo": cty.SetVal([]cty.Value{ 1526 cty.ObjectVal(map[string]cty.Value{ 1527 "bar": cty.MapValEmpty(cty.Object(map[string]cty.Type{ 1528 "baz": cty.String, 1529 })), 1530 }), 1531 }), 1532 }), 1533 cty.ObjectVal(map[string]cty.Value{ 1534 "foo": cty.SetVal([]cty.Value{ 1535 cty.ObjectVal(map[string]cty.Value{ 1536 "bar": cty.MapVal(map[string]cty.Value{ 1537 "bing": cty.ObjectVal(map[string]cty.Value{ 1538 "baz": cty.StringVal("true"), 1539 }), 1540 }), 1541 }), 1542 }), 1543 }), 1544 cty.ObjectVal(map[string]cty.Value{ 1545 "foo": cty.SetVal([]cty.Value{ 1546 cty.ObjectVal(map[string]cty.Value{ 1547 "bar": cty.MapVal(map[string]cty.Value{ 1548 "bing": cty.ObjectVal(map[string]cty.Value{ 1549 "baz": cty.StringVal("true"), 1550 }), 1551 }), 1552 }), 1553 }), 1554 }), 1555 }, 1556 // This example has a mixture of optional, computed and required in a deeply-nested NestedType attribute 1557 "deeply NestedType": { 1558 &configschema.Block{ 1559 Attributes: map[string]*configschema.Attribute{ 1560 "foo": { 1561 NestedType: &configschema.Object{ 1562 Nesting: configschema.NestingSingle, 1563 Attributes: map[string]*configschema.Attribute{ 1564 "bar": { 1565 NestedType: &configschema.Object{ 1566 Nesting: configschema.NestingSingle, 1567 Attributes: testAttributes, 1568 }, 1569 Required: true, 1570 }, 1571 "baz": { 1572 NestedType: &configschema.Object{ 1573 Nesting: configschema.NestingSingle, 1574 Attributes: testAttributes, 1575 }, 1576 Optional: true, 1577 }, 1578 }, 1579 }, 1580 Optional: true, 1581 }, 1582 }, 1583 }, 1584 // prior 1585 cty.ObjectVal(map[string]cty.Value{ 1586 "foo": cty.ObjectVal(map[string]cty.Value{ 1587 "bar": cty.NullVal(cty.DynamicPseudoType), 1588 "baz": cty.ObjectVal(map[string]cty.Value{ 1589 "optional": cty.NullVal(cty.String), 1590 "computed": cty.StringVal("hello"), 1591 "optional_computed": cty.StringVal("prior"), 1592 "required": cty.StringVal("present"), 1593 }), 1594 }), 1595 }), 1596 // config 1597 cty.ObjectVal(map[string]cty.Value{ 1598 "foo": cty.ObjectVal(map[string]cty.Value{ 1599 "bar": cty.UnknownVal(cty.Object(map[string]cty.Type{ // explicit unknown from the config 1600 "optional": cty.String, 1601 "computed": cty.String, 1602 "optional_computed": cty.String, 1603 "required": cty.String, 1604 })), 1605 "baz": cty.ObjectVal(map[string]cty.Value{ 1606 "optional": cty.NullVal(cty.String), 1607 "computed": cty.NullVal(cty.String), 1608 "optional_computed": cty.StringVal("hello"), 1609 "required": cty.StringVal("present"), 1610 }), 1611 }), 1612 }), 1613 // want 1614 cty.ObjectVal(map[string]cty.Value{ 1615 "foo": cty.ObjectVal(map[string]cty.Value{ 1616 "bar": cty.UnknownVal(cty.Object(map[string]cty.Type{ // explicit unknown preserved from the config 1617 "optional": cty.String, 1618 "computed": cty.String, 1619 "optional_computed": cty.String, 1620 "required": cty.String, 1621 })), 1622 "baz": cty.ObjectVal(map[string]cty.Value{ 1623 "optional": cty.NullVal(cty.String), // config is null 1624 "computed": cty.StringVal("hello"), // computed values come from prior 1625 "optional_computed": cty.StringVal("hello"), // config takes precedent over prior in opt+computed 1626 "required": cty.StringVal("present"), // value from config 1627 }), 1628 }), 1629 }), 1630 }, 1631 "deeply nested set": { 1632 &configschema.Block{ 1633 Attributes: map[string]*configschema.Attribute{ 1634 "foo": { 1635 NestedType: &configschema.Object{ 1636 Nesting: configschema.NestingSet, 1637 Attributes: map[string]*configschema.Attribute{ 1638 "bar": { 1639 NestedType: &configschema.Object{ 1640 Nesting: configschema.NestingSet, 1641 Attributes: testAttributes, 1642 }, 1643 Required: true, 1644 }, 1645 }, 1646 }, 1647 Optional: true, 1648 }, 1649 }, 1650 }, 1651 // prior values 1652 cty.ObjectVal(map[string]cty.Value{ 1653 "foo": cty.SetVal([]cty.Value{ 1654 cty.ObjectVal(map[string]cty.Value{ 1655 "bar": cty.SetVal([]cty.Value{ 1656 cty.ObjectVal(map[string]cty.Value{ 1657 "optional": cty.StringVal("prior"), 1658 "computed": cty.StringVal("prior"), 1659 "optional_computed": cty.StringVal("prior"), 1660 "required": cty.StringVal("prior"), 1661 }), 1662 }), 1663 }), 1664 cty.ObjectVal(map[string]cty.Value{ 1665 "bar": cty.SetVal([]cty.Value{ 1666 cty.ObjectVal(map[string]cty.Value{ 1667 "optional": cty.StringVal("other_prior"), 1668 "computed": cty.StringVal("other_prior"), 1669 "optional_computed": cty.StringVal("other_prior"), 1670 "required": cty.StringVal("other_prior"), 1671 }), 1672 }), 1673 }), 1674 }), 1675 }), 1676 // config differs from prior 1677 cty.ObjectVal(map[string]cty.Value{ 1678 "foo": cty.SetVal([]cty.Value{ 1679 cty.ObjectVal(map[string]cty.Value{ 1680 "bar": cty.SetVal([]cty.Value{cty.ObjectVal(map[string]cty.Value{ 1681 "optional": cty.StringVal("configured"), 1682 "computed": cty.NullVal(cty.String), // computed attrs are null in config 1683 "optional_computed": cty.StringVal("configured"), 1684 "required": cty.StringVal("configured"), 1685 })}), 1686 }), 1687 cty.ObjectVal(map[string]cty.Value{ 1688 "bar": cty.SetVal([]cty.Value{cty.ObjectVal(map[string]cty.Value{ 1689 "optional": cty.NullVal(cty.String), // explicit null in config 1690 "computed": cty.NullVal(cty.String), // computed attrs are null in config 1691 "optional_computed": cty.StringVal("other_configured"), 1692 "required": cty.StringVal("other_configured"), 1693 })}), 1694 }), 1695 }), 1696 }), 1697 // want: 1698 cty.ObjectVal(map[string]cty.Value{ 1699 "foo": cty.SetVal([]cty.Value{ 1700 cty.ObjectVal(map[string]cty.Value{ 1701 "bar": cty.SetVal([]cty.Value{cty.ObjectVal(map[string]cty.Value{ 1702 "optional": cty.StringVal("configured"), 1703 "computed": cty.NullVal(cty.String), 1704 "optional_computed": cty.StringVal("configured"), 1705 "required": cty.StringVal("configured"), 1706 })}), 1707 }), 1708 cty.ObjectVal(map[string]cty.Value{ 1709 "bar": cty.SetVal([]cty.Value{cty.ObjectVal(map[string]cty.Value{ 1710 "optional": cty.NullVal(cty.String), // explicit null in config is preserved 1711 "computed": cty.NullVal(cty.String), 1712 "optional_computed": cty.StringVal("other_configured"), 1713 "required": cty.StringVal("other_configured"), 1714 })}), 1715 }), 1716 }), 1717 }), 1718 }, 1719 "expected null NestedTypes": { 1720 &configschema.Block{ 1721 Attributes: map[string]*configschema.Attribute{ 1722 "single": { 1723 NestedType: &configschema.Object{ 1724 Nesting: configschema.NestingSingle, 1725 Attributes: map[string]*configschema.Attribute{ 1726 "bar": {Type: cty.String}, 1727 }, 1728 }, 1729 Optional: true, 1730 }, 1731 "list": { 1732 NestedType: &configschema.Object{ 1733 Nesting: configschema.NestingList, 1734 Attributes: map[string]*configschema.Attribute{ 1735 "bar": {Type: cty.String}, 1736 }, 1737 }, 1738 Optional: true, 1739 }, 1740 "set": { 1741 NestedType: &configschema.Object{ 1742 Nesting: configschema.NestingSet, 1743 Attributes: map[string]*configschema.Attribute{ 1744 "bar": {Type: cty.String}, 1745 }, 1746 }, 1747 Optional: true, 1748 }, 1749 "map": { 1750 NestedType: &configschema.Object{ 1751 Nesting: configschema.NestingMap, 1752 Attributes: map[string]*configschema.Attribute{ 1753 "bar": {Type: cty.String}, 1754 }, 1755 }, 1756 Optional: true, 1757 }, 1758 "nested_map": { 1759 NestedType: &configschema.Object{ 1760 Nesting: configschema.NestingMap, 1761 Attributes: map[string]*configschema.Attribute{ 1762 "inner": { 1763 NestedType: &configschema.Object{ 1764 Nesting: configschema.NestingSingle, 1765 Attributes: testAttributes, 1766 }, 1767 }, 1768 }, 1769 }, 1770 Optional: true, 1771 }, 1772 }, 1773 }, 1774 cty.ObjectVal(map[string]cty.Value{ 1775 "single": cty.ObjectVal(map[string]cty.Value{"bar": cty.StringVal("baz")}), 1776 "list": cty.ListVal([]cty.Value{cty.ObjectVal(map[string]cty.Value{"bar": cty.StringVal("baz")})}), 1777 "map": cty.MapVal(map[string]cty.Value{ 1778 "map_entry": cty.ObjectVal(map[string]cty.Value{"bar": cty.StringVal("baz")}), 1779 }), 1780 "set": cty.SetVal([]cty.Value{cty.ObjectVal(map[string]cty.Value{"bar": cty.StringVal("baz")})}), 1781 "nested_map": cty.MapVal(map[string]cty.Value{ 1782 "a": cty.ObjectVal(map[string]cty.Value{ 1783 "inner": cty.ObjectVal(map[string]cty.Value{ 1784 "optional": cty.StringVal("foo"), 1785 "computed": cty.StringVal("foo"), 1786 "optional_computed": cty.StringVal("foo"), 1787 "required": cty.StringVal("foo"), 1788 }), 1789 }), 1790 }), 1791 }), 1792 cty.ObjectVal(map[string]cty.Value{ 1793 "single": cty.NullVal(cty.Object(map[string]cty.Type{"bar": cty.String})), 1794 "list": cty.NullVal(cty.List(cty.Object(map[string]cty.Type{"bar": cty.String}))), 1795 "map": cty.NullVal(cty.Map(cty.Object(map[string]cty.Type{"bar": cty.String}))), 1796 "set": cty.NullVal(cty.Set(cty.Object(map[string]cty.Type{"bar": cty.String}))), 1797 "nested_map": cty.NullVal(cty.Map(cty.Object(map[string]cty.Type{ 1798 "inner": cty.Object(map[string]cty.Type{ 1799 "optional": cty.String, 1800 "computed": cty.String, 1801 "optional_computed": cty.String, 1802 "required": cty.String, 1803 }), 1804 }))), 1805 }), 1806 cty.ObjectVal(map[string]cty.Value{ 1807 "single": cty.NullVal(cty.Object(map[string]cty.Type{"bar": cty.String})), 1808 "list": cty.NullVal(cty.List(cty.Object(map[string]cty.Type{"bar": cty.String}))), 1809 "map": cty.NullVal(cty.Map(cty.Object(map[string]cty.Type{"bar": cty.String}))), 1810 "set": cty.NullVal(cty.Set(cty.Object(map[string]cty.Type{"bar": cty.String}))), 1811 "nested_map": cty.NullVal(cty.Map(cty.Object(map[string]cty.Type{ 1812 "inner": cty.Object(map[string]cty.Type{ 1813 "optional": cty.String, 1814 "computed": cty.String, 1815 "optional_computed": cty.String, 1816 "required": cty.String, 1817 }), 1818 }))), 1819 }), 1820 }, 1821 "expected empty NestedTypes": { 1822 &configschema.Block{ 1823 Attributes: map[string]*configschema.Attribute{ 1824 "set": { 1825 NestedType: &configschema.Object{ 1826 Nesting: configschema.NestingSet, 1827 Attributes: map[string]*configschema.Attribute{ 1828 "bar": {Type: cty.String}, 1829 }, 1830 }, 1831 Optional: true, 1832 }, 1833 "map": { 1834 NestedType: &configschema.Object{ 1835 Nesting: configschema.NestingMap, 1836 Attributes: map[string]*configschema.Attribute{ 1837 "bar": {Type: cty.String}, 1838 }, 1839 }, 1840 Optional: true, 1841 }, 1842 }, 1843 }, 1844 cty.ObjectVal(map[string]cty.Value{ 1845 "map": cty.MapValEmpty(cty.Object(map[string]cty.Type{"bar": cty.String})), 1846 "set": cty.SetValEmpty(cty.Object(map[string]cty.Type{"bar": cty.String})), 1847 }), 1848 cty.ObjectVal(map[string]cty.Value{ 1849 "map": cty.MapValEmpty(cty.Object(map[string]cty.Type{"bar": cty.String})), 1850 "set": cty.SetValEmpty(cty.Object(map[string]cty.Type{"bar": cty.String})), 1851 }), 1852 cty.ObjectVal(map[string]cty.Value{ 1853 "map": cty.MapValEmpty(cty.Object(map[string]cty.Type{"bar": cty.String})), 1854 "set": cty.SetValEmpty(cty.Object(map[string]cty.Type{"bar": cty.String})), 1855 }), 1856 }, 1857 "optional types set replacement": { 1858 &configschema.Block{ 1859 Attributes: map[string]*configschema.Attribute{ 1860 "set": { 1861 NestedType: &configschema.Object{ 1862 Nesting: configschema.NestingSet, 1863 Attributes: map[string]*configschema.Attribute{ 1864 "bar": { 1865 Type: cty.String, 1866 Required: true, 1867 }, 1868 }, 1869 }, 1870 Optional: true, 1871 }, 1872 }, 1873 }, 1874 cty.ObjectVal(map[string]cty.Value{ 1875 "set": cty.SetVal([]cty.Value{ 1876 cty.ObjectVal(map[string]cty.Value{ 1877 "bar": cty.StringVal("old"), 1878 }), 1879 }), 1880 }), 1881 cty.ObjectVal(map[string]cty.Value{ 1882 "set": cty.SetVal([]cty.Value{ 1883 cty.ObjectVal(map[string]cty.Value{ 1884 "bar": cty.StringVal("new"), 1885 }), 1886 }), 1887 }), 1888 cty.ObjectVal(map[string]cty.Value{ 1889 "set": cty.SetVal([]cty.Value{ 1890 cty.ObjectVal(map[string]cty.Value{ 1891 "bar": cty.StringVal("new"), 1892 }), 1893 }), 1894 }), 1895 }, 1896 "prior null nested objects": { 1897 &configschema.Block{ 1898 Attributes: map[string]*configschema.Attribute{ 1899 "single": { 1900 NestedType: &configschema.Object{ 1901 Nesting: configschema.NestingSingle, 1902 Attributes: map[string]*configschema.Attribute{ 1903 "list": { 1904 NestedType: &configschema.Object{ 1905 Nesting: configschema.NestingList, 1906 Attributes: map[string]*configschema.Attribute{ 1907 "foo": { 1908 Type: cty.String, 1909 }, 1910 }, 1911 }, 1912 Optional: true, 1913 }, 1914 }, 1915 }, 1916 Optional: true, 1917 }, 1918 "map": { 1919 NestedType: &configschema.Object{ 1920 Nesting: configschema.NestingMap, 1921 Attributes: map[string]*configschema.Attribute{ 1922 "map": { 1923 NestedType: &configschema.Object{ 1924 Nesting: configschema.NestingList, 1925 Attributes: map[string]*configschema.Attribute{ 1926 "foo": { 1927 Type: cty.String, 1928 }, 1929 }, 1930 }, 1931 Optional: true, 1932 }, 1933 }, 1934 }, 1935 Optional: true, 1936 }, 1937 }, 1938 }, 1939 cty.NullVal(cty.Object(map[string]cty.Type{ 1940 "single": cty.Object(map[string]cty.Type{ 1941 "list": cty.List(cty.Object(map[string]cty.Type{ 1942 "foo": cty.String, 1943 })), 1944 }), 1945 "map": cty.Map(cty.Object(map[string]cty.Type{ 1946 "list": cty.List(cty.Object(map[string]cty.Type{ 1947 "foo": cty.String, 1948 })), 1949 })), 1950 })), 1951 cty.ObjectVal(map[string]cty.Value{ 1952 "single": cty.ObjectVal(map[string]cty.Value{ 1953 "list": cty.ListVal([]cty.Value{ 1954 cty.ObjectVal(map[string]cty.Value{ 1955 "foo": cty.StringVal("a"), 1956 }), 1957 cty.ObjectVal(map[string]cty.Value{ 1958 "foo": cty.StringVal("b"), 1959 }), 1960 }), 1961 }), 1962 "map": cty.MapVal(map[string]cty.Value{ 1963 "one": cty.ObjectVal(map[string]cty.Value{ 1964 "list": cty.ListVal([]cty.Value{ 1965 cty.ObjectVal(map[string]cty.Value{ 1966 "foo": cty.StringVal("a"), 1967 }), 1968 cty.ObjectVal(map[string]cty.Value{ 1969 "foo": cty.StringVal("b"), 1970 }), 1971 }), 1972 }), 1973 }), 1974 }), 1975 cty.ObjectVal(map[string]cty.Value{ 1976 "single": cty.ObjectVal(map[string]cty.Value{ 1977 "list": cty.ListVal([]cty.Value{ 1978 cty.ObjectVal(map[string]cty.Value{ 1979 "foo": cty.StringVal("a"), 1980 }), 1981 cty.ObjectVal(map[string]cty.Value{ 1982 "foo": cty.StringVal("b"), 1983 }), 1984 }), 1985 }), 1986 "map": cty.MapVal(map[string]cty.Value{ 1987 "one": cty.ObjectVal(map[string]cty.Value{ 1988 "list": cty.ListVal([]cty.Value{ 1989 cty.ObjectVal(map[string]cty.Value{ 1990 "foo": cty.StringVal("a"), 1991 }), 1992 cty.ObjectVal(map[string]cty.Value{ 1993 "foo": cty.StringVal("b"), 1994 }), 1995 }), 1996 }), 1997 }), 1998 }), 1999 }, 2000 2001 // Data sources are planned with an unknown value. 2002 // Note that this plan fails AssertPlanValid, because for managed 2003 // resources an instance would never be completely unknown. 2004 "unknown prior nested objects": { 2005 &configschema.Block{ 2006 Attributes: map[string]*configschema.Attribute{ 2007 "list": { 2008 NestedType: &configschema.Object{ 2009 Nesting: configschema.NestingList, 2010 Attributes: map[string]*configschema.Attribute{ 2011 "list": { 2012 NestedType: &configschema.Object{ 2013 Nesting: configschema.NestingList, 2014 Attributes: map[string]*configschema.Attribute{ 2015 "foo": { 2016 Type: cty.String, 2017 }, 2018 }, 2019 }, 2020 Computed: true, 2021 }, 2022 }, 2023 }, 2024 Computed: true, 2025 }, 2026 }, 2027 }, 2028 cty.UnknownVal(cty.Object(map[string]cty.Type{ 2029 "list": cty.List(cty.Object(map[string]cty.Type{ 2030 "list": cty.List(cty.Object(map[string]cty.Type{ 2031 "foo": cty.String, 2032 })), 2033 })), 2034 })), 2035 cty.NullVal(cty.Object(map[string]cty.Type{ 2036 "list": cty.List(cty.Object(map[string]cty.Type{ 2037 "list": cty.List(cty.Object(map[string]cty.Type{ 2038 "foo": cty.String, 2039 })), 2040 })), 2041 })), 2042 cty.UnknownVal(cty.Object(map[string]cty.Type{ 2043 "list": cty.List(cty.Object(map[string]cty.Type{ 2044 "list": cty.List(cty.Object(map[string]cty.Type{ 2045 "foo": cty.String, 2046 })), 2047 })), 2048 })), 2049 }, 2050 2051 // A nested object with computed attributes, which is contained in an 2052 // optional+computed container. The nested computed values should be 2053 // represented in the proposed new object. 2054 "config within optional+computed": { 2055 &configschema.Block{ 2056 Attributes: map[string]*configschema.Attribute{ 2057 "list_obj": { 2058 Optional: true, 2059 Computed: true, 2060 NestedType: &configschema.Object{ 2061 Nesting: configschema.NestingList, 2062 Attributes: map[string]*configschema.Attribute{ 2063 "obj": { 2064 Optional: true, 2065 NestedType: &configschema.Object{ 2066 Nesting: configschema.NestingSingle, 2067 Attributes: map[string]*configschema.Attribute{ 2068 "optional": {Type: cty.String, Optional: true}, 2069 "computed": {Type: cty.String, Computed: true}, 2070 }, 2071 }, 2072 }, 2073 }, 2074 }, 2075 }, 2076 }, 2077 }, 2078 cty.ObjectVal(map[string]cty.Value{ 2079 "list_obj": cty.ListVal([]cty.Value{ 2080 cty.ObjectVal(map[string]cty.Value{ 2081 "obj": cty.ObjectVal(map[string]cty.Value{ 2082 "optional": cty.StringVal("prior"), 2083 "computed": cty.StringVal("prior computed"), 2084 }), 2085 }), 2086 }), 2087 }), 2088 cty.ObjectVal(map[string]cty.Value{ 2089 "list_obj": cty.ListVal([]cty.Value{ 2090 cty.ObjectVal(map[string]cty.Value{ 2091 "obj": cty.ObjectVal(map[string]cty.Value{ 2092 "optional": cty.StringVal("prior"), 2093 "computed": cty.NullVal(cty.String), 2094 }), 2095 }), 2096 }), 2097 }), 2098 cty.ObjectVal(map[string]cty.Value{ 2099 "list_obj": cty.ListVal([]cty.Value{ 2100 cty.ObjectVal(map[string]cty.Value{ 2101 "obj": cty.ObjectVal(map[string]cty.Value{ 2102 "optional": cty.StringVal("prior"), 2103 "computed": cty.StringVal("prior computed"), 2104 }), 2105 }), 2106 }), 2107 }), 2108 }, 2109 2110 // A nested object with computed attributes, which is contained in an 2111 // optional+computed container. The prior nested object contains values 2112 // which could not be computed, therefor the proposed new value must be 2113 // the null value from the configuration. 2114 "computed within optional+computed": { 2115 &configschema.Block{ 2116 Attributes: map[string]*configschema.Attribute{ 2117 "list_obj": { 2118 Optional: true, 2119 Computed: true, 2120 NestedType: &configschema.Object{ 2121 Nesting: configschema.NestingList, 2122 Attributes: map[string]*configschema.Attribute{ 2123 "obj": { 2124 Optional: true, 2125 NestedType: &configschema.Object{ 2126 Nesting: configschema.NestingSingle, 2127 Attributes: map[string]*configschema.Attribute{ 2128 "optional": {Type: cty.String, Optional: true}, 2129 "computed": {Type: cty.String, Computed: true}, 2130 }, 2131 }, 2132 }, 2133 }, 2134 }, 2135 }, 2136 }, 2137 }, 2138 cty.ObjectVal(map[string]cty.Value{ 2139 "list_obj": cty.ListVal([]cty.Value{ 2140 cty.ObjectVal(map[string]cty.Value{ 2141 "obj": cty.ObjectVal(map[string]cty.Value{ 2142 "optional": cty.StringVal("prior"), 2143 "computed": cty.StringVal("prior computed"), 2144 }), 2145 }), 2146 }), 2147 }), 2148 cty.ObjectVal(map[string]cty.Value{ 2149 "list_obj": cty.NullVal(cty.List( 2150 cty.Object(map[string]cty.Type{ 2151 "obj": cty.Object(map[string]cty.Type{ 2152 "optional": cty.String, 2153 "computed": cty.String, 2154 }), 2155 }), 2156 )), 2157 }), 2158 cty.ObjectVal(map[string]cty.Value{ 2159 "list_obj": cty.NullVal(cty.List( 2160 cty.Object(map[string]cty.Type{ 2161 "obj": cty.Object(map[string]cty.Type{ 2162 "optional": cty.String, 2163 "computed": cty.String, 2164 }), 2165 }), 2166 )), 2167 }), 2168 }, 2169 2170 // A nested object with computed attributes, which is contained in an 2171 // optional+computed set. The nested computed values should be 2172 // represented in the proposed new object, and correlated with state 2173 // via the non-computed attributes. 2174 "config add within optional+computed set": { 2175 &configschema.Block{ 2176 Attributes: map[string]*configschema.Attribute{ 2177 "set_obj": { 2178 Optional: true, 2179 Computed: true, 2180 NestedType: &configschema.Object{ 2181 Nesting: configschema.NestingSet, 2182 Attributes: map[string]*configschema.Attribute{ 2183 "obj": { 2184 Optional: true, 2185 NestedType: &configschema.Object{ 2186 Nesting: configschema.NestingSingle, 2187 Attributes: map[string]*configschema.Attribute{ 2188 "optional": {Type: cty.String, Optional: true}, 2189 "computed": {Type: cty.String, Computed: true}, 2190 }, 2191 }, 2192 }, 2193 }, 2194 }, 2195 }, 2196 }, 2197 }, 2198 cty.ObjectVal(map[string]cty.Value{ 2199 "set_obj": cty.SetVal([]cty.Value{ 2200 cty.ObjectVal(map[string]cty.Value{ 2201 "obj": cty.ObjectVal(map[string]cty.Value{ 2202 "optional": cty.StringVal("first"), 2203 "computed": cty.StringVal("first computed"), 2204 }), 2205 }), 2206 cty.ObjectVal(map[string]cty.Value{ 2207 "obj": cty.ObjectVal(map[string]cty.Value{ 2208 "optional": cty.StringVal("second"), 2209 "computed": cty.StringVal("second computed"), 2210 }), 2211 }), 2212 }), 2213 }), 2214 cty.ObjectVal(map[string]cty.Value{ 2215 "set_obj": cty.SetVal([]cty.Value{ 2216 cty.ObjectVal(map[string]cty.Value{ 2217 "obj": cty.ObjectVal(map[string]cty.Value{ 2218 "optional": cty.StringVal("first"), 2219 "computed": cty.NullVal(cty.String), 2220 }), 2221 }), 2222 cty.ObjectVal(map[string]cty.Value{ 2223 "obj": cty.ObjectVal(map[string]cty.Value{ 2224 "optional": cty.StringVal("second"), 2225 "computed": cty.NullVal(cty.String), 2226 }), 2227 }), 2228 cty.ObjectVal(map[string]cty.Value{ 2229 "obj": cty.ObjectVal(map[string]cty.Value{ 2230 "optional": cty.StringVal("third"), 2231 "computed": cty.NullVal(cty.String), 2232 }), 2233 }), 2234 }), 2235 }), 2236 cty.ObjectVal(map[string]cty.Value{ 2237 "set_obj": cty.SetVal([]cty.Value{ 2238 cty.ObjectVal(map[string]cty.Value{ 2239 "obj": cty.ObjectVal(map[string]cty.Value{ 2240 "optional": cty.StringVal("first"), 2241 "computed": cty.StringVal("first computed"), 2242 }), 2243 }), 2244 cty.ObjectVal(map[string]cty.Value{ 2245 "obj": cty.ObjectVal(map[string]cty.Value{ 2246 "optional": cty.StringVal("second"), 2247 "computed": cty.StringVal("second computed"), 2248 }), 2249 }), 2250 cty.ObjectVal(map[string]cty.Value{ 2251 "obj": cty.ObjectVal(map[string]cty.Value{ 2252 "optional": cty.StringVal("third"), 2253 "computed": cty.NullVal(cty.String), 2254 }), 2255 }), 2256 }), 2257 }), 2258 }, 2259 2260 // A nested object with computed attributes, which is contained in a 2261 // set. The nested computed values should be represented in the 2262 // proposed new object, and correlated with state via the non-computed 2263 // attributes. 2264 "config add within set block": { 2265 &configschema.Block{ 2266 BlockTypes: map[string]*configschema.NestedBlock{ 2267 "set_obj": { 2268 Nesting: configschema.NestingSet, 2269 Block: configschema.Block{ 2270 Attributes: map[string]*configschema.Attribute{ 2271 "obj": { 2272 Optional: true, 2273 NestedType: &configschema.Object{ 2274 Nesting: configschema.NestingSingle, 2275 Attributes: map[string]*configschema.Attribute{ 2276 "optional": {Type: cty.String, Optional: true}, 2277 "computed": {Type: cty.String, Optional: true, Computed: true}, 2278 }, 2279 }, 2280 }, 2281 }, 2282 }, 2283 }, 2284 }, 2285 }, 2286 cty.ObjectVal(map[string]cty.Value{ 2287 "set_obj": cty.SetVal([]cty.Value{ 2288 cty.ObjectVal(map[string]cty.Value{ 2289 "obj": cty.ObjectVal(map[string]cty.Value{ 2290 "optional": cty.StringVal("first"), 2291 "computed": cty.StringVal("first computed"), 2292 }), 2293 }), 2294 cty.ObjectVal(map[string]cty.Value{ 2295 "obj": cty.ObjectVal(map[string]cty.Value{ 2296 "optional": cty.StringVal("second"), 2297 "computed": cty.StringVal("second from config"), 2298 }), 2299 }), 2300 }), 2301 }), 2302 cty.ObjectVal(map[string]cty.Value{ 2303 "set_obj": cty.SetVal([]cty.Value{ 2304 cty.ObjectVal(map[string]cty.Value{ 2305 "obj": cty.ObjectVal(map[string]cty.Value{ 2306 "optional": cty.StringVal("first"), 2307 "computed": cty.NullVal(cty.String), 2308 }), 2309 }), 2310 cty.ObjectVal(map[string]cty.Value{ 2311 "obj": cty.ObjectVal(map[string]cty.Value{ 2312 "optional": cty.StringVal("second"), 2313 "computed": cty.StringVal("second from config"), 2314 }), 2315 }), 2316 // new "third" value added 2317 cty.ObjectVal(map[string]cty.Value{ 2318 "obj": cty.ObjectVal(map[string]cty.Value{ 2319 "optional": cty.StringVal("third"), 2320 "computed": cty.NullVal(cty.String), 2321 }), 2322 }), 2323 }), 2324 }), 2325 cty.ObjectVal(map[string]cty.Value{ 2326 "set_obj": cty.SetVal([]cty.Value{ 2327 cty.ObjectVal(map[string]cty.Value{ 2328 "obj": cty.ObjectVal(map[string]cty.Value{ 2329 "optional": cty.StringVal("first"), 2330 "computed": cty.StringVal("first computed"), 2331 }), 2332 }), 2333 cty.ObjectVal(map[string]cty.Value{ 2334 "obj": cty.ObjectVal(map[string]cty.Value{ 2335 "optional": cty.StringVal("second"), 2336 "computed": cty.StringVal("second from config"), 2337 }), 2338 }), 2339 cty.ObjectVal(map[string]cty.Value{ 2340 "obj": cty.ObjectVal(map[string]cty.Value{ 2341 "optional": cty.StringVal("third"), 2342 "computed": cty.NullVal(cty.String), 2343 }), 2344 }), 2345 }), 2346 }), 2347 }, 2348 2349 // A nested object with computed attributes, which is contained in a 2350 // set. The nested computed values should be represented in the 2351 // proposed new object, and correlated with state via the non-computed 2352 // attributes. 2353 "config change within set block": { 2354 &configschema.Block{ 2355 BlockTypes: map[string]*configschema.NestedBlock{ 2356 "set_obj": { 2357 Nesting: configschema.NestingSet, 2358 Block: configschema.Block{ 2359 Attributes: map[string]*configschema.Attribute{ 2360 "obj": { 2361 Optional: true, 2362 NestedType: &configschema.Object{ 2363 Nesting: configschema.NestingSingle, 2364 Attributes: map[string]*configschema.Attribute{ 2365 "optional": {Type: cty.String, Optional: true}, 2366 "computed": {Type: cty.String, Optional: true, Computed: true}, 2367 }, 2368 }, 2369 }, 2370 }, 2371 }, 2372 }, 2373 }, 2374 }, 2375 cty.ObjectVal(map[string]cty.Value{ 2376 "set_obj": cty.SetVal([]cty.Value{ 2377 cty.ObjectVal(map[string]cty.Value{ 2378 "obj": cty.ObjectVal(map[string]cty.Value{ 2379 "optional": cty.StringVal("first"), 2380 "computed": cty.StringVal("first computed"), 2381 }), 2382 }), 2383 cty.ObjectVal(map[string]cty.Value{ 2384 "obj": cty.ObjectVal(map[string]cty.Value{ 2385 "optional": cty.StringVal("second"), 2386 "computed": cty.StringVal("second computed"), 2387 }), 2388 }), 2389 }), 2390 }), 2391 cty.ObjectVal(map[string]cty.Value{ 2392 "set_obj": cty.SetVal([]cty.Value{ 2393 cty.ObjectVal(map[string]cty.Value{ 2394 "obj": cty.ObjectVal(map[string]cty.Value{ 2395 "optional": cty.StringVal("first"), 2396 "computed": cty.NullVal(cty.String), 2397 }), 2398 }), 2399 cty.ObjectVal(map[string]cty.Value{ 2400 "obj": cty.ObjectVal(map[string]cty.Value{ 2401 "optional": cty.StringVal("changed"), 2402 "computed": cty.NullVal(cty.String), 2403 }), 2404 }), 2405 }), 2406 }), 2407 cty.ObjectVal(map[string]cty.Value{ 2408 "set_obj": cty.SetVal([]cty.Value{ 2409 cty.ObjectVal(map[string]cty.Value{ 2410 "obj": cty.ObjectVal(map[string]cty.Value{ 2411 "optional": cty.StringVal("first"), 2412 "computed": cty.StringVal("first computed"), 2413 }), 2414 }), 2415 cty.ObjectVal(map[string]cty.Value{ 2416 "obj": cty.ObjectVal(map[string]cty.Value{ 2417 "optional": cty.StringVal("changed"), 2418 "computed": cty.NullVal(cty.String), 2419 }), 2420 }), 2421 }), 2422 }), 2423 }, 2424 2425 "set attr with partial optional computed change": { 2426 &configschema.Block{ 2427 Attributes: map[string]*configschema.Attribute{ 2428 "multi": { 2429 Optional: true, 2430 NestedType: &configschema.Object{ 2431 Nesting: configschema.NestingSet, 2432 Attributes: map[string]*configschema.Attribute{ 2433 "opt": { 2434 Type: cty.String, 2435 Optional: true, 2436 }, 2437 "oc": { 2438 Type: cty.String, 2439 Optional: true, 2440 Computed: true, 2441 }, 2442 }, 2443 }, 2444 }, 2445 }, 2446 }, 2447 cty.ObjectVal(map[string]cty.Value{ 2448 "multi": cty.SetVal([]cty.Value{ 2449 cty.ObjectVal(map[string]cty.Value{ 2450 "opt": cty.StringVal("one"), 2451 "oc": cty.StringVal("OK"), 2452 }), 2453 cty.ObjectVal(map[string]cty.Value{ 2454 "opt": cty.StringVal("two"), 2455 "oc": cty.StringVal("OK"), 2456 }), 2457 }), 2458 }), 2459 cty.ObjectVal(map[string]cty.Value{ 2460 "multi": cty.SetVal([]cty.Value{ 2461 cty.ObjectVal(map[string]cty.Value{ 2462 "opt": cty.StringVal("one"), 2463 "oc": cty.NullVal(cty.String), 2464 }), 2465 cty.ObjectVal(map[string]cty.Value{ 2466 "opt": cty.StringVal("replaced"), 2467 "oc": cty.NullVal(cty.String), 2468 }), 2469 }), 2470 }), 2471 cty.ObjectVal(map[string]cty.Value{ 2472 "multi": cty.SetVal([]cty.Value{ 2473 cty.ObjectVal(map[string]cty.Value{ 2474 "opt": cty.StringVal("one"), 2475 "oc": cty.StringVal("OK"), 2476 }), 2477 cty.ObjectVal(map[string]cty.Value{ 2478 "opt": cty.StringVal("replaced"), 2479 "oc": cty.NullVal(cty.String), 2480 }), 2481 }), 2482 }), 2483 }, 2484 2485 "set attr without optional computed change": { 2486 &configschema.Block{ 2487 Attributes: map[string]*configschema.Attribute{ 2488 "multi": { 2489 Optional: true, 2490 NestedType: &configschema.Object{ 2491 Nesting: configschema.NestingSet, 2492 Attributes: map[string]*configschema.Attribute{ 2493 "opt": { 2494 Type: cty.String, 2495 Optional: true, 2496 }, 2497 "oc": { 2498 Type: cty.String, 2499 Optional: true, 2500 Computed: true, 2501 }, 2502 }, 2503 }, 2504 }, 2505 }, 2506 }, 2507 cty.ObjectVal(map[string]cty.Value{ 2508 "multi": cty.SetVal([]cty.Value{ 2509 cty.ObjectVal(map[string]cty.Value{ 2510 "opt": cty.StringVal("one"), 2511 "oc": cty.StringVal("OK"), 2512 }), 2513 cty.ObjectVal(map[string]cty.Value{ 2514 "opt": cty.StringVal("two"), 2515 "oc": cty.StringVal("OK"), 2516 }), 2517 }), 2518 }), 2519 cty.ObjectVal(map[string]cty.Value{ 2520 "multi": cty.SetVal([]cty.Value{ 2521 cty.ObjectVal(map[string]cty.Value{ 2522 "opt": cty.StringVal("one"), 2523 "oc": cty.NullVal(cty.String), 2524 }), 2525 cty.ObjectVal(map[string]cty.Value{ 2526 "opt": cty.StringVal("two"), 2527 "oc": cty.NullVal(cty.String), 2528 }), 2529 }), 2530 }), 2531 cty.ObjectVal(map[string]cty.Value{ 2532 "multi": cty.SetVal([]cty.Value{ 2533 cty.ObjectVal(map[string]cty.Value{ 2534 "opt": cty.StringVal("one"), 2535 "oc": cty.StringVal("OK"), 2536 }), 2537 cty.ObjectVal(map[string]cty.Value{ 2538 "opt": cty.StringVal("two"), 2539 "oc": cty.StringVal("OK"), 2540 }), 2541 }), 2542 }), 2543 }, 2544 2545 "set attr with all optional computed": { 2546 &configschema.Block{ 2547 Attributes: map[string]*configschema.Attribute{ 2548 "multi": { 2549 Optional: true, 2550 NestedType: &configschema.Object{ 2551 Nesting: configschema.NestingSet, 2552 Attributes: map[string]*configschema.Attribute{ 2553 "opt": { 2554 Type: cty.String, 2555 Optional: true, 2556 Computed: true, 2557 }, 2558 "oc": { 2559 Type: cty.String, 2560 Optional: true, 2561 Computed: true, 2562 }, 2563 }, 2564 }, 2565 }, 2566 }, 2567 }, 2568 cty.ObjectVal(map[string]cty.Value{ 2569 "multi": cty.SetVal([]cty.Value{ 2570 cty.ObjectVal(map[string]cty.Value{ 2571 "opt": cty.StringVal("one"), 2572 "oc": cty.StringVal("OK"), 2573 }), 2574 cty.ObjectVal(map[string]cty.Value{ 2575 "opt": cty.StringVal("two"), 2576 "oc": cty.StringVal("OK"), 2577 }), 2578 }), 2579 }), 2580 // Each of these values can be correlated by the existence of the 2581 // optional config attribute. Because "one" and "two" are set in 2582 // the config, they must exist in the state regardless of 2583 // optional&computed. 2584 cty.ObjectVal(map[string]cty.Value{ 2585 "multi": cty.SetVal([]cty.Value{ 2586 cty.ObjectVal(map[string]cty.Value{ 2587 "opt": cty.StringVal("one"), 2588 "oc": cty.NullVal(cty.String), 2589 }), 2590 cty.ObjectVal(map[string]cty.Value{ 2591 "opt": cty.StringVal("two"), 2592 "oc": cty.NullVal(cty.String), 2593 }), 2594 }), 2595 }), 2596 cty.ObjectVal(map[string]cty.Value{ 2597 "multi": cty.SetVal([]cty.Value{ 2598 cty.ObjectVal(map[string]cty.Value{ 2599 "opt": cty.StringVal("one"), 2600 "oc": cty.StringVal("OK"), 2601 }), 2602 cty.ObjectVal(map[string]cty.Value{ 2603 "opt": cty.StringVal("two"), 2604 "oc": cty.StringVal("OK"), 2605 }), 2606 }), 2607 }), 2608 }, 2609 2610 "set block with all optional computed and nested object types": { 2611 &configschema.Block{ 2612 BlockTypes: map[string]*configschema.NestedBlock{ 2613 "multi": { 2614 Nesting: configschema.NestingSet, 2615 Block: configschema.Block{ 2616 Attributes: map[string]*configschema.Attribute{ 2617 "opt": { 2618 Type: cty.String, 2619 Optional: true, 2620 Computed: true, 2621 }, 2622 "oc": { 2623 Type: cty.String, 2624 Optional: true, 2625 Computed: true, 2626 }, 2627 "attr": { 2628 Optional: true, 2629 NestedType: &configschema.Object{ 2630 Nesting: configschema.NestingSet, 2631 Attributes: map[string]*configschema.Attribute{ 2632 "opt": { 2633 Type: cty.String, 2634 Optional: true, 2635 Computed: true, 2636 }, 2637 "oc": { 2638 Type: cty.String, 2639 Optional: true, 2640 Computed: true, 2641 }, 2642 }, 2643 }, 2644 }, 2645 }, 2646 }, 2647 }, 2648 }, 2649 }, 2650 cty.ObjectVal(map[string]cty.Value{ 2651 "multi": cty.SetVal([]cty.Value{ 2652 cty.ObjectVal(map[string]cty.Value{ 2653 "opt": cty.StringVal("one"), 2654 "oc": cty.StringVal("OK"), 2655 "attr": cty.SetVal([]cty.Value{cty.ObjectVal(map[string]cty.Value{ 2656 "opt": cty.StringVal("one"), 2657 "oc": cty.StringVal("OK"), 2658 })}), 2659 }), 2660 cty.ObjectVal(map[string]cty.Value{ 2661 "opt": cty.StringVal("two"), 2662 "oc": cty.StringVal("OK"), 2663 "attr": cty.SetVal([]cty.Value{cty.ObjectVal(map[string]cty.Value{ 2664 "opt": cty.StringVal("two"), 2665 "oc": cty.StringVal("OK"), 2666 })}), 2667 }), 2668 }), 2669 }), 2670 cty.ObjectVal(map[string]cty.Value{ 2671 "multi": cty.SetVal([]cty.Value{ 2672 cty.ObjectVal(map[string]cty.Value{ 2673 "opt": cty.StringVal("one"), 2674 "oc": cty.NullVal(cty.String), 2675 "attr": cty.SetVal([]cty.Value{cty.ObjectVal(map[string]cty.Value{ 2676 "opt": cty.StringVal("one"), 2677 "oc": cty.StringVal("OK"), 2678 })}), 2679 }), 2680 cty.ObjectVal(map[string]cty.Value{ 2681 "opt": cty.StringVal("two"), 2682 "oc": cty.StringVal("OK"), 2683 "attr": cty.SetVal([]cty.Value{cty.ObjectVal(map[string]cty.Value{ 2684 "opt": cty.StringVal("two"), 2685 "oc": cty.NullVal(cty.String), 2686 })}), 2687 }), 2688 cty.ObjectVal(map[string]cty.Value{ 2689 "opt": cty.StringVal("three"), 2690 "oc": cty.NullVal(cty.String), 2691 "attr": cty.NullVal(cty.Set(cty.Object(map[string]cty.Type{ 2692 "opt": cty.String, 2693 "oc": cty.String, 2694 }))), 2695 }), 2696 }), 2697 }), 2698 cty.ObjectVal(map[string]cty.Value{ 2699 "multi": cty.SetVal([]cty.Value{ 2700 // We can correlate this with prior from the outer object 2701 // attributes, and the equal nested set. 2702 cty.ObjectVal(map[string]cty.Value{ 2703 "opt": cty.StringVal("one"), 2704 "oc": cty.StringVal("OK"), 2705 "attr": cty.SetVal([]cty.Value{cty.ObjectVal(map[string]cty.Value{ 2706 "opt": cty.StringVal("one"), 2707 "oc": cty.StringVal("OK"), 2708 })}), 2709 }), 2710 // This value is overridden by config, because we can't 2711 // correlate optional+computed config values within nested 2712 // sets. 2713 cty.ObjectVal(map[string]cty.Value{ 2714 "opt": cty.StringVal("two"), 2715 "oc": cty.StringVal("OK"), 2716 "attr": cty.SetVal([]cty.Value{cty.ObjectVal(map[string]cty.Value{ 2717 "opt": cty.StringVal("two"), 2718 "oc": cty.NullVal(cty.String), 2719 })}), 2720 }), 2721 // This value was taken only from config 2722 cty.ObjectVal(map[string]cty.Value{ 2723 "opt": cty.StringVal("three"), 2724 "oc": cty.NullVal(cty.String), 2725 "attr": cty.NullVal(cty.Set(cty.Object(map[string]cty.Type{ 2726 "opt": cty.String, 2727 "oc": cty.String, 2728 }))), 2729 }), 2730 }), 2731 }), 2732 }, 2733 } 2734 2735 for name, test := range tests { 2736 t.Run(name, func(t *testing.T) { 2737 got := ProposedNew(test.Schema, test.Prior, test.Config) 2738 if !got.RawEquals(test.Want) { 2739 t.Errorf("wrong result\ngot: %swant: %s", dump.Value(got), dump.Value(test.Want)) 2740 } 2741 }) 2742 } 2743 } 2744 2745 var testAttributes = map[string]*configschema.Attribute{ 2746 "optional": { 2747 Type: cty.String, 2748 Optional: true, 2749 }, 2750 "computed": { 2751 Type: cty.String, 2752 Computed: true, 2753 }, 2754 "optional_computed": { 2755 Type: cty.String, 2756 Computed: true, 2757 Optional: true, 2758 }, 2759 "required": { 2760 Type: cty.String, 2761 Required: true, 2762 }, 2763 }