github.com/jaredpalmer/terraform@v1.1.0-alpha20210908.0.20210911170307-88705c943a03/internal/command/views/add_test.go (about) 1 package views 2 3 import ( 4 "strings" 5 "testing" 6 7 "github.com/google/go-cmp/cmp" 8 "github.com/hashicorp/terraform/internal/addrs" 9 "github.com/hashicorp/terraform/internal/configs/configschema" 10 "github.com/hashicorp/terraform/internal/lang/marks" 11 "github.com/hashicorp/terraform/internal/terminal" 12 "github.com/zclconf/go-cty/cty" 13 ) 14 15 // The output is tested in greater detail in other tests; this suite focuses on 16 // details specific to the Resource function. 17 func TestAddResource(t *testing.T) { 18 t.Run("config only", func(t *testing.T) { 19 streams, done := terminal.StreamsForTesting(t) 20 v := addHuman{view: NewView(streams), optional: true} 21 err := v.Resource( 22 mustResourceInstanceAddr("test_instance.foo"), 23 addTestSchemaSensitive(configschema.NestingSingle), 24 addrs.NewDefaultLocalProviderConfig("mytest"), cty.NilVal, 25 ) 26 if err != nil { 27 t.Fatal(err.Error()) 28 } 29 30 expected := `# NOTE: The "terraform add" command is currently experimental and offers only a 31 # starting point for your resource configuration, with some limitations. 32 # 33 # The behavior of this command may change in future based on feedback, possibly 34 # in incompatible ways. We don't recommend building automation around this 35 # command at this time. If you have feedback about this command, please open 36 # a feature request issue in the Terraform GitHub repository. 37 resource "test_instance" "foo" { 38 provider = mytest 39 40 ami = null # OPTIONAL string 41 disks = { # OPTIONAL object 42 mount_point = null # OPTIONAL string 43 size = null # OPTIONAL string 44 } 45 id = null # OPTIONAL string 46 root_block_device { # OPTIONAL block 47 volume_type = null # OPTIONAL string 48 } 49 } 50 ` 51 output := done(t) 52 if output.Stdout() != expected { 53 t.Errorf("wrong result: %s", cmp.Diff(expected, output.Stdout())) 54 } 55 }) 56 57 t.Run("from state", func(t *testing.T) { 58 streams, done := terminal.StreamsForTesting(t) 59 v := addHuman{view: NewView(streams), optional: true} 60 val := cty.ObjectVal(map[string]cty.Value{ 61 "ami": cty.StringVal("ami-123456789"), 62 "disks": cty.ObjectVal(map[string]cty.Value{ 63 "mount_point": cty.StringVal("/mnt/foo"), 64 "size": cty.StringVal("50GB"), 65 }), 66 }) 67 68 err := v.Resource( 69 mustResourceInstanceAddr("test_instance.foo"), 70 addTestSchemaSensitive(configschema.NestingSingle), 71 addrs.NewDefaultLocalProviderConfig("mytest"), val, 72 ) 73 if err != nil { 74 t.Fatal(err.Error()) 75 } 76 77 expected := `# NOTE: The "terraform add" command is currently experimental and offers only a 78 # starting point for your resource configuration, with some limitations. 79 # 80 # The behavior of this command may change in future based on feedback, possibly 81 # in incompatible ways. We don't recommend building automation around this 82 # command at this time. If you have feedback about this command, please open 83 # a feature request issue in the Terraform GitHub repository. 84 resource "test_instance" "foo" { 85 provider = mytest 86 87 ami = "ami-123456789" 88 disks = {} # sensitive 89 id = null 90 } 91 ` 92 output := done(t) 93 if output.Stdout() != expected { 94 t.Errorf("wrong result: %s", cmp.Diff(expected, output.Stdout())) 95 } 96 }) 97 98 } 99 100 func TestAdd_writeConfigAttributes(t *testing.T) { 101 tests := map[string]struct { 102 attrs map[string]*configschema.Attribute 103 expected string 104 }{ 105 "empty returns nil": { 106 map[string]*configschema.Attribute{}, 107 "", 108 }, 109 "attributes": { 110 map[string]*configschema.Attribute{ 111 "ami": { 112 Type: cty.Number, 113 Required: true, 114 }, 115 "boot_disk": { 116 Type: cty.String, 117 Optional: true, 118 }, 119 "password": { 120 Type: cty.String, 121 Optional: true, 122 Sensitive: true, // sensitivity is ignored when printing blank templates 123 }, 124 }, 125 `ami = null # REQUIRED number 126 boot_disk = null # OPTIONAL string 127 password = null # OPTIONAL string 128 `, 129 }, 130 "attributes with nested types": { 131 map[string]*configschema.Attribute{ 132 "ami": { 133 Type: cty.Number, 134 Required: true, 135 }, 136 "disks": { 137 NestedType: &configschema.Object{ 138 Nesting: configschema.NestingSingle, 139 Attributes: map[string]*configschema.Attribute{ 140 "size": { 141 Type: cty.Number, 142 Optional: true, 143 }, 144 "mount_point": { 145 Type: cty.String, 146 Required: true, 147 }, 148 }, 149 }, 150 Optional: true, 151 }, 152 }, 153 `ami = null # REQUIRED number 154 disks = { # OPTIONAL object 155 mount_point = null # REQUIRED string 156 size = null # OPTIONAL number 157 } 158 `, 159 }, 160 } 161 162 v := addHuman{optional: true} 163 164 for name, test := range tests { 165 t.Run(name, func(t *testing.T) { 166 var buf strings.Builder 167 if err := v.writeConfigAttributes(&buf, test.attrs, 0); err != nil { 168 t.Errorf("unexpected error") 169 } 170 if buf.String() != test.expected { 171 t.Errorf("wrong result: %s", cmp.Diff(test.expected, buf.String())) 172 } 173 }) 174 } 175 } 176 177 func TestAdd_writeConfigAttributesFromExisting(t *testing.T) { 178 attrs := map[string]*configschema.Attribute{ 179 "ami": { 180 Type: cty.Number, 181 Required: true, 182 }, 183 "boot_disk": { 184 Type: cty.String, 185 Optional: true, 186 }, 187 "password": { 188 Type: cty.String, 189 Optional: true, 190 Sensitive: true, 191 }, 192 "disks": { 193 NestedType: &configschema.Object{ 194 Nesting: configschema.NestingSingle, 195 Attributes: map[string]*configschema.Attribute{ 196 "size": { 197 Type: cty.Number, 198 Optional: true, 199 }, 200 "mount_point": { 201 Type: cty.String, 202 Required: true, 203 }, 204 }, 205 }, 206 Optional: true, 207 }, 208 } 209 210 tests := map[string]struct { 211 attrs map[string]*configschema.Attribute 212 val cty.Value 213 expected string 214 }{ 215 "empty returns nil": { 216 map[string]*configschema.Attribute{}, 217 cty.NilVal, 218 "", 219 }, 220 "mixed attributes": { 221 attrs, 222 cty.ObjectVal(map[string]cty.Value{ 223 "ami": cty.NumberIntVal(123456), 224 "boot_disk": cty.NullVal(cty.String), 225 "password": cty.StringVal("i am secret"), 226 "disks": cty.ObjectVal(map[string]cty.Value{ 227 "size": cty.NumberIntVal(50), 228 "mount_point": cty.NullVal(cty.String), 229 }), 230 }), 231 `ami = 123456 232 boot_disk = null 233 disks = { 234 mount_point = null 235 size = 50 236 } 237 password = null # sensitive 238 `, 239 }, 240 } 241 242 v := addHuman{optional: true} 243 244 for name, test := range tests { 245 t.Run(name, func(t *testing.T) { 246 var buf strings.Builder 247 if err := v.writeConfigAttributesFromExisting(&buf, test.val, test.attrs, 0); err != nil { 248 t.Errorf("unexpected error") 249 } 250 if buf.String() != test.expected { 251 t.Errorf("wrong result: %s", cmp.Diff(test.expected, buf.String())) 252 } 253 }) 254 } 255 } 256 257 func TestAdd_writeConfigBlocks(t *testing.T) { 258 t.Run("NestingSingle", func(t *testing.T) { 259 v := addHuman{optional: true} 260 schema := addTestSchema(configschema.NestingSingle) 261 var buf strings.Builder 262 v.writeConfigBlocks(&buf, schema.BlockTypes, 0) 263 264 expected := `network_rules { # REQUIRED block 265 ip_address = null # OPTIONAL string 266 } 267 root_block_device { # OPTIONAL block 268 volume_type = null # OPTIONAL string 269 } 270 ` 271 272 if !cmp.Equal(buf.String(), expected) { 273 t.Errorf("wrong output:\n%s", cmp.Diff(expected, buf.String())) 274 } 275 }) 276 277 t.Run("NestingList", func(t *testing.T) { 278 v := addHuman{optional: true} 279 schema := addTestSchema(configschema.NestingList) 280 var buf strings.Builder 281 v.writeConfigBlocks(&buf, schema.BlockTypes, 0) 282 283 expected := `network_rules { # REQUIRED block 284 ip_address = null # OPTIONAL string 285 } 286 root_block_device { # OPTIONAL block 287 volume_type = null # OPTIONAL string 288 } 289 ` 290 291 if !cmp.Equal(buf.String(), expected) { 292 t.Fatalf("wrong output:\n%s", cmp.Diff(expected, buf.String())) 293 } 294 }) 295 296 t.Run("NestingSet", func(t *testing.T) { 297 v := addHuman{optional: true} 298 schema := addTestSchema(configschema.NestingSet) 299 var buf strings.Builder 300 v.writeConfigBlocks(&buf, schema.BlockTypes, 0) 301 302 expected := `network_rules { # REQUIRED block 303 ip_address = null # OPTIONAL string 304 } 305 root_block_device { # OPTIONAL block 306 volume_type = null # OPTIONAL string 307 } 308 ` 309 310 if !cmp.Equal(buf.String(), expected) { 311 t.Fatalf("wrong output:\n%s", cmp.Diff(expected, buf.String())) 312 } 313 }) 314 315 t.Run("NestingMap", func(t *testing.T) { 316 v := addHuman{optional: true} 317 schema := addTestSchema(configschema.NestingMap) 318 var buf strings.Builder 319 v.writeConfigBlocks(&buf, schema.BlockTypes, 0) 320 321 expected := `network_rules "key" { # REQUIRED block 322 ip_address = null # OPTIONAL string 323 } 324 root_block_device "key" { # OPTIONAL block 325 volume_type = null # OPTIONAL string 326 } 327 ` 328 329 if !cmp.Equal(buf.String(), expected) { 330 t.Fatalf("wrong output:\n%s", cmp.Diff(expected, buf.String())) 331 } 332 }) 333 } 334 335 func TestAdd_writeConfigBlocksFromExisting(t *testing.T) { 336 337 t.Run("NestingSingle", func(t *testing.T) { 338 v := addHuman{optional: true} 339 val := cty.ObjectVal(map[string]cty.Value{ 340 "root_block_device": cty.ObjectVal(map[string]cty.Value{ 341 "volume_type": cty.StringVal("foo"), 342 }), 343 }) 344 schema := addTestSchema(configschema.NestingSingle) 345 var buf strings.Builder 346 v.writeConfigBlocksFromExisting(&buf, val, schema.BlockTypes, 0) 347 348 expected := `root_block_device { 349 volume_type = "foo" 350 } 351 ` 352 353 if !cmp.Equal(buf.String(), expected) { 354 t.Errorf("wrong output:\n%s", cmp.Diff(expected, buf.String())) 355 } 356 }) 357 358 t.Run("NestingSingle_marked_attr", func(t *testing.T) { 359 v := addHuman{optional: true} 360 val := cty.ObjectVal(map[string]cty.Value{ 361 "root_block_device": cty.ObjectVal(map[string]cty.Value{ 362 "volume_type": cty.StringVal("foo").Mark(marks.Sensitive), 363 }), 364 }) 365 schema := addTestSchema(configschema.NestingSingle) 366 var buf strings.Builder 367 v.writeConfigBlocksFromExisting(&buf, val, schema.BlockTypes, 0) 368 369 expected := `root_block_device { 370 volume_type = null # sensitive 371 } 372 ` 373 374 if !cmp.Equal(buf.String(), expected) { 375 t.Errorf("wrong output:\n%s", cmp.Diff(expected, buf.String())) 376 } 377 }) 378 379 t.Run("NestingSingle_entirely_marked", func(t *testing.T) { 380 v := addHuman{optional: true} 381 val := cty.ObjectVal(map[string]cty.Value{ 382 "root_block_device": cty.ObjectVal(map[string]cty.Value{ 383 "volume_type": cty.StringVal("foo"), 384 }), 385 }).Mark(marks.Sensitive) 386 schema := addTestSchema(configschema.NestingSingle) 387 var buf strings.Builder 388 v.writeConfigBlocksFromExisting(&buf, val, schema.BlockTypes, 0) 389 390 expected := `root_block_device {} # sensitive 391 ` 392 393 if !cmp.Equal(buf.String(), expected) { 394 t.Errorf("wrong output:\n%s", cmp.Diff(expected, buf.String())) 395 } 396 }) 397 398 t.Run("NestingList", func(t *testing.T) { 399 v := addHuman{optional: true} 400 val := cty.ObjectVal(map[string]cty.Value{ 401 "root_block_device": cty.ListVal([]cty.Value{ 402 cty.ObjectVal(map[string]cty.Value{ 403 "volume_type": cty.StringVal("foo"), 404 }), 405 cty.ObjectVal(map[string]cty.Value{ 406 "volume_type": cty.StringVal("bar"), 407 }), 408 }), 409 }) 410 schema := addTestSchema(configschema.NestingList) 411 var buf strings.Builder 412 v.writeConfigBlocksFromExisting(&buf, val, schema.BlockTypes, 0) 413 414 expected := `root_block_device { 415 volume_type = "foo" 416 } 417 root_block_device { 418 volume_type = "bar" 419 } 420 ` 421 422 if !cmp.Equal(buf.String(), expected) { 423 t.Fatalf("wrong output:\n%s", cmp.Diff(expected, buf.String())) 424 } 425 }) 426 427 t.Run("NestingList_marked_attr", func(t *testing.T) { 428 v := addHuman{optional: true} 429 val := cty.ObjectVal(map[string]cty.Value{ 430 "root_block_device": cty.ListVal([]cty.Value{ 431 cty.ObjectVal(map[string]cty.Value{ 432 "volume_type": cty.StringVal("foo").Mark(marks.Sensitive), 433 }), 434 cty.ObjectVal(map[string]cty.Value{ 435 "volume_type": cty.StringVal("bar"), 436 }), 437 }), 438 }) 439 schema := addTestSchema(configschema.NestingList) 440 var buf strings.Builder 441 v.writeConfigBlocksFromExisting(&buf, val, schema.BlockTypes, 0) 442 443 expected := `root_block_device { 444 volume_type = null # sensitive 445 } 446 root_block_device { 447 volume_type = "bar" 448 } 449 ` 450 451 if !cmp.Equal(buf.String(), expected) { 452 t.Fatalf("wrong output:\n%s", cmp.Diff(expected, buf.String())) 453 } 454 }) 455 456 t.Run("NestingList_entirely_marked", func(t *testing.T) { 457 v := addHuman{optional: true} 458 val := cty.ObjectVal(map[string]cty.Value{ 459 "root_block_device": cty.ListVal([]cty.Value{ 460 cty.ObjectVal(map[string]cty.Value{ 461 "volume_type": cty.StringVal("foo"), 462 }), 463 cty.ObjectVal(map[string]cty.Value{ 464 "volume_type": cty.StringVal("bar"), 465 }), 466 }).Mark(marks.Sensitive), 467 }) 468 schema := addTestSchema(configschema.NestingList) 469 var buf strings.Builder 470 v.writeConfigBlocksFromExisting(&buf, val, schema.BlockTypes, 0) 471 472 expected := `root_block_device {} # sensitive 473 ` 474 475 if !cmp.Equal(buf.String(), expected) { 476 t.Fatalf("wrong output:\n%s", cmp.Diff(expected, buf.String())) 477 } 478 }) 479 480 t.Run("NestingSet", func(t *testing.T) { 481 v := addHuman{optional: true} 482 val := cty.ObjectVal(map[string]cty.Value{ 483 "root_block_device": cty.SetVal([]cty.Value{ 484 cty.ObjectVal(map[string]cty.Value{ 485 "volume_type": cty.StringVal("foo"), 486 }), 487 cty.ObjectVal(map[string]cty.Value{ 488 "volume_type": cty.StringVal("bar"), 489 }), 490 }), 491 }) 492 schema := addTestSchema(configschema.NestingSet) 493 var buf strings.Builder 494 v.writeConfigBlocksFromExisting(&buf, val, schema.BlockTypes, 0) 495 496 expected := `root_block_device { 497 volume_type = "bar" 498 } 499 root_block_device { 500 volume_type = "foo" 501 } 502 ` 503 504 if !cmp.Equal(buf.String(), expected) { 505 t.Fatalf("wrong output:\n%s", cmp.Diff(expected, buf.String())) 506 } 507 }) 508 509 t.Run("NestingSet_marked", func(t *testing.T) { 510 v := addHuman{optional: true} 511 // In cty.Sets, the entire set ends up marked if any element is marked. 512 val := cty.ObjectVal(map[string]cty.Value{ 513 "root_block_device": cty.SetVal([]cty.Value{ 514 cty.ObjectVal(map[string]cty.Value{ 515 "volume_type": cty.StringVal("foo"), 516 }), 517 cty.ObjectVal(map[string]cty.Value{ 518 "volume_type": cty.StringVal("bar"), 519 }), 520 }).Mark(marks.Sensitive), 521 }) 522 schema := addTestSchema(configschema.NestingSet) 523 var buf strings.Builder 524 v.writeConfigBlocksFromExisting(&buf, val, schema.BlockTypes, 0) 525 526 // When the entire set of blocks is sensitive, we only print one block. 527 expected := `root_block_device {} # sensitive 528 ` 529 530 if !cmp.Equal(buf.String(), expected) { 531 t.Fatalf("wrong output:\n%s", cmp.Diff(expected, buf.String())) 532 } 533 }) 534 535 t.Run("NestingMap", func(t *testing.T) { 536 v := addHuman{optional: true} 537 val := cty.ObjectVal(map[string]cty.Value{ 538 "root_block_device": cty.MapVal(map[string]cty.Value{ 539 "1": cty.ObjectVal(map[string]cty.Value{ 540 "volume_type": cty.StringVal("foo"), 541 }), 542 "2": cty.ObjectVal(map[string]cty.Value{ 543 "volume_type": cty.StringVal("bar"), 544 }), 545 }), 546 }) 547 schema := addTestSchema(configschema.NestingMap) 548 var buf strings.Builder 549 v.writeConfigBlocksFromExisting(&buf, val, schema.BlockTypes, 0) 550 551 expected := `root_block_device "1" { 552 volume_type = "foo" 553 } 554 root_block_device "2" { 555 volume_type = "bar" 556 } 557 ` 558 559 if !cmp.Equal(buf.String(), expected) { 560 t.Fatalf("wrong output:\n%s", cmp.Diff(expected, buf.String())) 561 } 562 }) 563 564 t.Run("NestingMap_marked", func(t *testing.T) { 565 v := addHuman{optional: true} 566 val := cty.ObjectVal(map[string]cty.Value{ 567 "root_block_device": cty.MapVal(map[string]cty.Value{ 568 "1": cty.ObjectVal(map[string]cty.Value{ 569 "volume_type": cty.StringVal("foo").Mark(marks.Sensitive), 570 }), 571 "2": cty.ObjectVal(map[string]cty.Value{ 572 "volume_type": cty.StringVal("bar"), 573 }), 574 }), 575 }) 576 schema := addTestSchema(configschema.NestingMap) 577 var buf strings.Builder 578 v.writeConfigBlocksFromExisting(&buf, val, schema.BlockTypes, 0) 579 580 expected := `root_block_device "1" { 581 volume_type = null # sensitive 582 } 583 root_block_device "2" { 584 volume_type = "bar" 585 } 586 ` 587 588 if !cmp.Equal(buf.String(), expected) { 589 t.Fatalf("wrong output:\n%s", cmp.Diff(expected, buf.String())) 590 } 591 }) 592 593 t.Run("NestingMap_entirely_marked", func(t *testing.T) { 594 v := addHuman{optional: true} 595 val := cty.ObjectVal(map[string]cty.Value{ 596 "root_block_device": cty.MapVal(map[string]cty.Value{ 597 "1": cty.ObjectVal(map[string]cty.Value{ 598 "volume_type": cty.StringVal("foo"), 599 }), 600 "2": cty.ObjectVal(map[string]cty.Value{ 601 "volume_type": cty.StringVal("bar"), 602 }), 603 }).Mark(marks.Sensitive), 604 }) 605 schema := addTestSchema(configschema.NestingMap) 606 var buf strings.Builder 607 v.writeConfigBlocksFromExisting(&buf, val, schema.BlockTypes, 0) 608 609 expected := `root_block_device {} # sensitive 610 ` 611 612 if !cmp.Equal(buf.String(), expected) { 613 t.Fatalf("wrong output:\n%s", cmp.Diff(expected, buf.String())) 614 } 615 }) 616 617 t.Run("NestingMap_marked_elem", func(t *testing.T) { 618 v := addHuman{optional: true} 619 val := cty.ObjectVal(map[string]cty.Value{ 620 "root_block_device": cty.MapVal(map[string]cty.Value{ 621 "1": cty.ObjectVal(map[string]cty.Value{ 622 "volume_type": cty.StringVal("foo"), 623 }), 624 "2": cty.ObjectVal(map[string]cty.Value{ 625 "volume_type": cty.StringVal("bar"), 626 }).Mark(marks.Sensitive), 627 }), 628 }) 629 schema := addTestSchema(configschema.NestingMap) 630 var buf strings.Builder 631 v.writeConfigBlocksFromExisting(&buf, val, schema.BlockTypes, 0) 632 633 expected := `root_block_device "1" { 634 volume_type = "foo" 635 } 636 root_block_device "2" {} # sensitive 637 ` 638 639 if !cmp.Equal(buf.String(), expected) { 640 t.Fatalf("wrong output:\n%s", cmp.Diff(expected, buf.String())) 641 } 642 }) 643 } 644 645 func TestAdd_writeConfigNestedTypeAttribute(t *testing.T) { 646 t.Run("NestingSingle", func(t *testing.T) { 647 v := addHuman{optional: true} 648 schema := addTestSchema(configschema.NestingSingle) 649 var buf strings.Builder 650 v.writeConfigNestedTypeAttribute(&buf, "disks", schema.Attributes["disks"], 0) 651 652 expected := `disks = { # OPTIONAL object 653 mount_point = null # OPTIONAL string 654 size = null # OPTIONAL string 655 } 656 ` 657 658 if !cmp.Equal(buf.String(), expected) { 659 t.Fatalf("wrong output:\n%s", cmp.Diff(expected, buf.String())) 660 } 661 }) 662 663 t.Run("NestingList", func(t *testing.T) { 664 v := addHuman{optional: true} 665 schema := addTestSchema(configschema.NestingList) 666 var buf strings.Builder 667 v.writeConfigNestedTypeAttribute(&buf, "disks", schema.Attributes["disks"], 0) 668 669 expected := `disks = [{ # OPTIONAL list of object 670 mount_point = null # OPTIONAL string 671 size = null # OPTIONAL string 672 }] 673 ` 674 675 if !cmp.Equal(buf.String(), expected) { 676 t.Fatalf("wrong output:\n%s", cmp.Diff(expected, buf.String())) 677 } 678 }) 679 680 t.Run("NestingSet", func(t *testing.T) { 681 v := addHuman{optional: true} 682 schema := addTestSchema(configschema.NestingSet) 683 var buf strings.Builder 684 v.writeConfigNestedTypeAttribute(&buf, "disks", schema.Attributes["disks"], 0) 685 686 expected := `disks = [{ # OPTIONAL set of object 687 mount_point = null # OPTIONAL string 688 size = null # OPTIONAL string 689 }] 690 ` 691 692 if !cmp.Equal(buf.String(), expected) { 693 t.Fatalf("wrong output:\n%s", cmp.Diff(expected, buf.String())) 694 } 695 }) 696 697 t.Run("NestingMap", func(t *testing.T) { 698 v := addHuman{optional: true} 699 schema := addTestSchema(configschema.NestingMap) 700 var buf strings.Builder 701 v.writeConfigNestedTypeAttribute(&buf, "disks", schema.Attributes["disks"], 0) 702 703 expected := `disks = { # OPTIONAL map of object 704 key = { 705 mount_point = null # OPTIONAL string 706 size = null # OPTIONAL string 707 } 708 } 709 ` 710 711 if !cmp.Equal(buf.String(), expected) { 712 t.Fatalf("wrong output:\n%s", cmp.Diff(expected, buf.String())) 713 } 714 }) 715 } 716 717 func TestAdd_WriteConfigNestedTypeAttributeFromExisting(t *testing.T) { 718 t.Run("NestingSingle", func(t *testing.T) { 719 v := addHuman{optional: true} 720 val := cty.ObjectVal(map[string]cty.Value{ 721 "disks": cty.ObjectVal(map[string]cty.Value{ 722 "mount_point": cty.StringVal("/mnt/foo"), 723 "size": cty.StringVal("50GB"), 724 }), 725 }) 726 schema := addTestSchema(configschema.NestingSingle) 727 var buf strings.Builder 728 v.writeConfigNestedTypeAttributeFromExisting(&buf, "disks", schema.Attributes["disks"], val, 0) 729 730 expected := `disks = { 731 mount_point = "/mnt/foo" 732 size = "50GB" 733 } 734 ` 735 736 if !cmp.Equal(buf.String(), expected) { 737 t.Fatalf("wrong output:\n%s", cmp.Diff(expected, buf.String())) 738 } 739 }) 740 741 t.Run("NestingSingle_sensitive", func(t *testing.T) { 742 v := addHuman{optional: true} 743 val := cty.ObjectVal(map[string]cty.Value{ 744 "disks": cty.ObjectVal(map[string]cty.Value{ 745 "mount_point": cty.StringVal("/mnt/foo"), 746 "size": cty.StringVal("50GB"), 747 }), 748 }) 749 schema := addTestSchemaSensitive(configschema.NestingSingle) 750 var buf strings.Builder 751 v.writeConfigNestedTypeAttributeFromExisting(&buf, "disks", schema.Attributes["disks"], val, 0) 752 753 expected := `disks = {} # sensitive 754 ` 755 756 if !cmp.Equal(buf.String(), expected) { 757 t.Fatalf("wrong output:\n%s", cmp.Diff(expected, buf.String())) 758 } 759 }) 760 761 t.Run("NestingList", func(t *testing.T) { 762 v := addHuman{optional: true} 763 val := cty.ObjectVal(map[string]cty.Value{ 764 "disks": cty.ListVal([]cty.Value{ 765 cty.ObjectVal(map[string]cty.Value{ 766 "mount_point": cty.StringVal("/mnt/foo"), 767 "size": cty.StringVal("50GB"), 768 }), 769 cty.ObjectVal(map[string]cty.Value{ 770 "mount_point": cty.StringVal("/mnt/bar"), 771 "size": cty.StringVal("250GB"), 772 }), 773 }), 774 }) 775 776 schema := addTestSchema(configschema.NestingList) 777 var buf strings.Builder 778 v.writeConfigNestedTypeAttributeFromExisting(&buf, "disks", schema.Attributes["disks"], val, 0) 779 780 expected := `disks = [ 781 { 782 mount_point = "/mnt/foo" 783 size = "50GB" 784 }, 785 { 786 mount_point = "/mnt/bar" 787 size = "250GB" 788 }, 789 ] 790 ` 791 792 if !cmp.Equal(buf.String(), expected) { 793 t.Fatalf("wrong output:\n%s", cmp.Diff(expected, buf.String())) 794 } 795 }) 796 797 t.Run("NestingList - marked", func(t *testing.T) { 798 v := addHuman{optional: true} 799 val := cty.ObjectVal(map[string]cty.Value{ 800 "disks": cty.ListVal([]cty.Value{ 801 cty.ObjectVal(map[string]cty.Value{ 802 "mount_point": cty.StringVal("/mnt/foo"), 803 "size": cty.StringVal("50GB").Mark(marks.Sensitive), 804 }), 805 // This is an odd example, where the entire element is marked. 806 cty.ObjectVal(map[string]cty.Value{ 807 "mount_point": cty.StringVal("/mnt/bar"), 808 "size": cty.StringVal("250GB"), 809 }).Mark(marks.Sensitive), 810 }), 811 }) 812 813 schema := addTestSchema(configschema.NestingList) 814 var buf strings.Builder 815 v.writeConfigNestedTypeAttributeFromExisting(&buf, "disks", schema.Attributes["disks"], val, 0) 816 817 expected := `disks = [ 818 { 819 mount_point = "/mnt/foo" 820 size = null # sensitive 821 }, 822 {}, # sensitive 823 ] 824 ` 825 826 if !cmp.Equal(buf.String(), expected) { 827 t.Fatalf("wrong output:\n%s", cmp.Diff(expected, buf.String())) 828 } 829 }) 830 831 t.Run("NestingList - entirely marked", func(t *testing.T) { 832 v := addHuman{optional: true} 833 val := cty.ObjectVal(map[string]cty.Value{ 834 "disks": cty.ListVal([]cty.Value{ 835 cty.ObjectVal(map[string]cty.Value{ 836 "mount_point": cty.StringVal("/mnt/foo"), 837 "size": cty.StringVal("50GB"), 838 }), 839 // This is an odd example, where the entire element is marked. 840 cty.ObjectVal(map[string]cty.Value{ 841 "mount_point": cty.StringVal("/mnt/bar"), 842 "size": cty.StringVal("250GB"), 843 }), 844 }), 845 }).Mark(marks.Sensitive) 846 847 schema := addTestSchema(configschema.NestingList) 848 var buf strings.Builder 849 v.writeConfigNestedTypeAttributeFromExisting(&buf, "disks", schema.Attributes["disks"], val, 0) 850 851 expected := `disks = [] # sensitive 852 ` 853 854 if !cmp.Equal(buf.String(), expected) { 855 t.Fatalf("wrong output:\n%s", cmp.Diff(expected, buf.String())) 856 } 857 }) 858 859 t.Run("NestingMap", func(t *testing.T) { 860 v := addHuman{optional: true} 861 val := cty.ObjectVal(map[string]cty.Value{ 862 "disks": cty.MapVal(map[string]cty.Value{ 863 "foo": cty.ObjectVal(map[string]cty.Value{ 864 "mount_point": cty.StringVal("/mnt/foo"), 865 "size": cty.StringVal("50GB"), 866 }), 867 "bar": cty.ObjectVal(map[string]cty.Value{ 868 "mount_point": cty.StringVal("/mnt/bar"), 869 "size": cty.StringVal("250GB"), 870 }), 871 }), 872 }) 873 schema := addTestSchema(configschema.NestingMap) 874 var buf strings.Builder 875 v.writeConfigNestedTypeAttributeFromExisting(&buf, "disks", schema.Attributes["disks"], val, 0) 876 877 expected := `disks = { 878 bar = { 879 mount_point = "/mnt/bar" 880 size = "250GB" 881 } 882 foo = { 883 mount_point = "/mnt/foo" 884 size = "50GB" 885 } 886 } 887 ` 888 889 if !cmp.Equal(buf.String(), expected) { 890 t.Fatalf("wrong output:\n%s", cmp.Diff(expected, buf.String())) 891 } 892 }) 893 894 t.Run("NestingMap - marked", func(t *testing.T) { 895 v := addHuman{optional: true} 896 val := cty.ObjectVal(map[string]cty.Value{ 897 "disks": cty.MapVal(map[string]cty.Value{ 898 "foo": cty.ObjectVal(map[string]cty.Value{ 899 "mount_point": cty.StringVal("/mnt/foo"), 900 "size": cty.StringVal("50GB").Mark(marks.Sensitive), 901 }), 902 "bar": cty.ObjectVal(map[string]cty.Value{ 903 "mount_point": cty.StringVal("/mnt/bar"), 904 "size": cty.StringVal("250GB"), 905 }).Mark(marks.Sensitive), 906 }), 907 }) 908 schema := addTestSchema(configschema.NestingMap) 909 var buf strings.Builder 910 v.writeConfigNestedTypeAttributeFromExisting(&buf, "disks", schema.Attributes["disks"], val, 0) 911 912 expected := `disks = { 913 bar = {} # sensitive 914 foo = { 915 mount_point = "/mnt/foo" 916 size = null # sensitive 917 } 918 } 919 ` 920 921 if !cmp.Equal(buf.String(), expected) { 922 t.Fatalf("wrong output:\n%s", cmp.Diff(expected, buf.String())) 923 } 924 }) 925 } 926 927 func addTestSchema(nesting configschema.NestingMode) *configschema.Block { 928 return &configschema.Block{ 929 Attributes: map[string]*configschema.Attribute{ 930 "id": {Type: cty.String, Optional: true, Computed: true}, 931 // Attributes which are neither optional nor required should not print. 932 "uuid": {Type: cty.String, Computed: true}, 933 "ami": {Type: cty.String, Optional: true}, 934 "disks": { 935 NestedType: &configschema.Object{ 936 Attributes: map[string]*configschema.Attribute{ 937 "mount_point": {Type: cty.String, Optional: true}, 938 "size": {Type: cty.String, Optional: true}, 939 }, 940 Nesting: nesting, 941 }, 942 }, 943 }, 944 BlockTypes: map[string]*configschema.NestedBlock{ 945 "root_block_device": { 946 Block: configschema.Block{ 947 Attributes: map[string]*configschema.Attribute{ 948 "volume_type": { 949 Type: cty.String, 950 Optional: true, 951 Computed: true, 952 }, 953 }, 954 }, 955 Nesting: nesting, 956 }, 957 "network_rules": { 958 Block: configschema.Block{ 959 Attributes: map[string]*configschema.Attribute{ 960 "ip_address": { 961 Type: cty.String, 962 Optional: true, 963 Computed: true, 964 }, 965 }, 966 }, 967 Nesting: nesting, 968 MinItems: 1, 969 }, 970 }, 971 } 972 } 973 974 // addTestSchemaSensitive returns a schema with a sensitive NestedType and a 975 // NestedBlock with sensitive attributes. 976 func addTestSchemaSensitive(nesting configschema.NestingMode) *configschema.Block { 977 return &configschema.Block{ 978 Attributes: map[string]*configschema.Attribute{ 979 "id": {Type: cty.String, Optional: true, Computed: true}, 980 // Attributes which are neither optional nor required should not print. 981 "uuid": {Type: cty.String, Computed: true}, 982 "ami": {Type: cty.String, Optional: true}, 983 "disks": { 984 NestedType: &configschema.Object{ 985 Attributes: map[string]*configschema.Attribute{ 986 "mount_point": {Type: cty.String, Optional: true}, 987 "size": {Type: cty.String, Optional: true}, 988 }, 989 Nesting: nesting, 990 }, 991 Sensitive: true, 992 }, 993 }, 994 BlockTypes: map[string]*configschema.NestedBlock{ 995 "root_block_device": { 996 Block: configschema.Block{ 997 Attributes: map[string]*configschema.Attribute{ 998 "volume_type": { 999 Type: cty.String, 1000 Optional: true, 1001 Computed: true, 1002 Sensitive: true, 1003 }, 1004 }, 1005 }, 1006 Nesting: nesting, 1007 }, 1008 }, 1009 } 1010 } 1011 1012 func mustResourceInstanceAddr(s string) addrs.AbsResourceInstance { 1013 addr, diags := addrs.ParseAbsResourceInstanceStr(s) 1014 if diags.HasErrors() { 1015 panic(diags.Err()) 1016 } 1017 return addr 1018 }