github.com/meteor/terraform@v0.6.15-0.20210412225145-79ec4bc057c6/terraform/interpolate_test.go (about) 1 package terraform 2 3 import ( 4 "fmt" 5 "os" 6 "reflect" 7 "sync" 8 "testing" 9 10 "github.com/davecgh/go-spew/spew" 11 "github.com/hashicorp/hil" 12 "github.com/hashicorp/hil/ast" 13 "github.com/hashicorp/terraform/config" 14 ) 15 16 func TestInterpolater_simpleVar(t *testing.T) { 17 i := &Interpolater{} 18 scope := &InterpolationScope{} 19 testInterpolateErr(t, i, scope, "simple") 20 } 21 22 func TestInterpolater_countIndex(t *testing.T) { 23 i := &Interpolater{} 24 25 scope := &InterpolationScope{ 26 Path: rootModulePath, 27 Resource: &Resource{CountIndex: 42}, 28 } 29 30 testInterpolate(t, i, scope, "count.index", ast.Variable{ 31 Value: 42, 32 Type: ast.TypeInt, 33 }) 34 } 35 36 func TestInterpolater_countIndexInWrongContext(t *testing.T) { 37 i := &Interpolater{} 38 39 scope := &InterpolationScope{ 40 Path: rootModulePath, 41 } 42 43 n := "count.index" 44 45 v, err := config.NewInterpolatedVariable(n) 46 if err != nil { 47 t.Fatalf("err: %s", err) 48 } 49 50 expectedErr := fmt.Errorf("foo: count.index is only valid within resources") 51 52 _, err = i.Values(scope, map[string]config.InterpolatedVariable{ 53 "foo": v, 54 }) 55 56 if !reflect.DeepEqual(expectedErr, err) { 57 t.Fatalf("expected: %#v, got %#v", expectedErr, err) 58 } 59 } 60 61 func TestInterpolater_moduleVariable(t *testing.T) { 62 lock := new(sync.RWMutex) 63 state := &State{ 64 Modules: []*ModuleState{ 65 &ModuleState{ 66 Path: rootModulePath, 67 Resources: map[string]*ResourceState{ 68 "aws_instance.web": &ResourceState{ 69 Type: "aws_instance", 70 Primary: &InstanceState{ 71 ID: "bar", 72 }, 73 }, 74 }, 75 }, 76 &ModuleState{ 77 Path: []string{RootModuleName, "child"}, 78 Outputs: map[string]*OutputState{ 79 "foo": &OutputState{ 80 Type: "string", 81 Value: "bar", 82 }, 83 }, 84 }, 85 }, 86 } 87 88 i := &Interpolater{ 89 State: state, 90 StateLock: lock, 91 } 92 93 scope := &InterpolationScope{ 94 Path: rootModulePath, 95 } 96 97 testInterpolate(t, i, scope, "module.child.foo", ast.Variable{ 98 Value: "bar", 99 Type: ast.TypeString, 100 }) 101 } 102 103 func TestInterpolater_pathCwd(t *testing.T) { 104 i := &Interpolater{} 105 scope := &InterpolationScope{} 106 107 expected, err := os.Getwd() 108 if err != nil { 109 t.Fatalf("err: %s", err) 110 } 111 112 testInterpolate(t, i, scope, "path.cwd", ast.Variable{ 113 Value: expected, 114 Type: ast.TypeString, 115 }) 116 } 117 118 func TestInterpolater_pathModule(t *testing.T) { 119 mod := testModule(t, "interpolate-path-module") 120 i := &Interpolater{ 121 Module: mod, 122 } 123 scope := &InterpolationScope{ 124 Path: []string{RootModuleName, "child"}, 125 } 126 127 path := mod.Child([]string{"child"}).Config().Dir 128 testInterpolate(t, i, scope, "path.module", ast.Variable{ 129 Value: path, 130 Type: ast.TypeString, 131 }) 132 } 133 134 func TestInterpolater_pathRoot(t *testing.T) { 135 mod := testModule(t, "interpolate-path-module") 136 i := &Interpolater{ 137 Module: mod, 138 } 139 scope := &InterpolationScope{ 140 Path: []string{RootModuleName, "child"}, 141 } 142 143 path := mod.Config().Dir 144 testInterpolate(t, i, scope, "path.root", ast.Variable{ 145 Value: path, 146 Type: ast.TypeString, 147 }) 148 } 149 150 func TestInterpolater_resourceVariableMap(t *testing.T) { 151 lock := new(sync.RWMutex) 152 state := &State{ 153 Modules: []*ModuleState{ 154 &ModuleState{ 155 Path: rootModulePath, 156 Resources: map[string]*ResourceState{ 157 "aws_instance.web": &ResourceState{ 158 Type: "aws_instance", 159 Primary: &InstanceState{ 160 ID: "bar", 161 Attributes: map[string]string{ 162 "amap.%": "3", 163 "amap.key1": "value1", 164 "amap.key2": "value2", 165 "amap.key3": "value3", 166 }, 167 }, 168 }, 169 }, 170 }, 171 }, 172 } 173 174 i := &Interpolater{ 175 Module: testModule(t, "interpolate-resource-variable"), 176 State: state, 177 StateLock: lock, 178 } 179 180 scope := &InterpolationScope{ 181 Path: rootModulePath, 182 } 183 184 expected := map[string]interface{}{ 185 "key1": "value1", 186 "key2": "value2", 187 "key3": "value3", 188 } 189 190 testInterpolate(t, i, scope, "aws_instance.web.amap", 191 interfaceToVariableSwallowError(expected)) 192 } 193 194 func TestInterpolater_resourceVariableComplexMap(t *testing.T) { 195 lock := new(sync.RWMutex) 196 state := &State{ 197 Modules: []*ModuleState{ 198 &ModuleState{ 199 Path: rootModulePath, 200 Resources: map[string]*ResourceState{ 201 "aws_instance.web": &ResourceState{ 202 Type: "aws_instance", 203 Primary: &InstanceState{ 204 ID: "bar", 205 Attributes: map[string]string{ 206 "amap.%": "2", 207 "amap.key1.#": "2", 208 "amap.key1.0": "hello", 209 "amap.key1.1": "world", 210 "amap.key2.#": "1", 211 "amap.key2.0": "foo", 212 }, 213 }, 214 }, 215 }, 216 }, 217 }, 218 } 219 220 i := &Interpolater{ 221 Module: testModule(t, "interpolate-resource-variable"), 222 State: state, 223 StateLock: lock, 224 } 225 226 scope := &InterpolationScope{ 227 Path: rootModulePath, 228 } 229 230 expected := map[string]interface{}{ 231 "key1": []interface{}{"hello", "world"}, 232 "key2": []interface{}{"foo"}, 233 } 234 235 testInterpolate(t, i, scope, "aws_instance.web.amap", 236 interfaceToVariableSwallowError(expected)) 237 } 238 239 func TestInterpolater_resourceVariable(t *testing.T) { 240 lock := new(sync.RWMutex) 241 state := &State{ 242 Modules: []*ModuleState{ 243 &ModuleState{ 244 Path: rootModulePath, 245 Resources: map[string]*ResourceState{ 246 "aws_instance.web": &ResourceState{ 247 Type: "aws_instance", 248 Primary: &InstanceState{ 249 ID: "bar", 250 Attributes: map[string]string{ 251 "foo": "bar", 252 }, 253 }, 254 }, 255 }, 256 }, 257 }, 258 } 259 260 i := &Interpolater{ 261 Module: testModule(t, "interpolate-resource-variable"), 262 State: state, 263 StateLock: lock, 264 } 265 266 scope := &InterpolationScope{ 267 Path: rootModulePath, 268 } 269 270 testInterpolate(t, i, scope, "aws_instance.web.foo", ast.Variable{ 271 Value: "bar", 272 Type: ast.TypeString, 273 }) 274 } 275 276 func TestInterpolater_resourceVariableMissingDuringInput(t *testing.T) { 277 // During the input walk, computed resource attributes may be entirely 278 // absent since we've not yet produced diffs that tell us what computed 279 // attributes to expect. In that case, interpolator tolerates it and 280 // indicates the value is computed. 281 282 lock := new(sync.RWMutex) 283 state := &State{ 284 Modules: []*ModuleState{ 285 &ModuleState{ 286 Path: rootModulePath, 287 Resources: map[string]*ResourceState{ 288 // No resources at all yet, because we're still dealing 289 // with input and so the resources haven't been created. 290 }, 291 }, 292 }, 293 } 294 295 { 296 i := &Interpolater{ 297 Operation: walkInput, 298 Module: testModule(t, "interpolate-resource-variable"), 299 State: state, 300 StateLock: lock, 301 } 302 303 scope := &InterpolationScope{ 304 Path: rootModulePath, 305 } 306 307 testInterpolate(t, i, scope, "aws_instance.web.foo", ast.Variable{ 308 Value: config.UnknownVariableValue, 309 Type: ast.TypeUnknown, 310 }) 311 } 312 313 // This doesn't apply during other walks, like plan 314 { 315 i := &Interpolater{ 316 Operation: walkPlan, 317 Module: testModule(t, "interpolate-resource-variable"), 318 State: state, 319 StateLock: lock, 320 } 321 322 scope := &InterpolationScope{ 323 Path: rootModulePath, 324 } 325 326 testInterpolateErr(t, i, scope, "aws_instance.web.foo") 327 } 328 } 329 330 func TestInterpolater_resourceVariableMulti(t *testing.T) { 331 lock := new(sync.RWMutex) 332 state := &State{ 333 Modules: []*ModuleState{ 334 &ModuleState{ 335 Path: rootModulePath, 336 Resources: map[string]*ResourceState{ 337 "aws_instance.web": &ResourceState{ 338 Type: "aws_instance", 339 Primary: &InstanceState{ 340 ID: "bar", 341 Attributes: map[string]string{ 342 "foo": config.UnknownVariableValue, 343 }, 344 }, 345 }, 346 }, 347 }, 348 }, 349 } 350 351 i := &Interpolater{ 352 Module: testModule(t, "interpolate-resource-variable"), 353 State: state, 354 StateLock: lock, 355 } 356 357 scope := &InterpolationScope{ 358 Path: rootModulePath, 359 } 360 361 testInterpolate(t, i, scope, "aws_instance.web.*.foo", ast.Variable{ 362 Type: ast.TypeList, 363 Value: []ast.Variable{ 364 { 365 Type: ast.TypeUnknown, 366 Value: config.UnknownVariableValue, 367 }, 368 }, 369 }) 370 } 371 372 func TestInterpolater_resourceVariableMultiPartialUnknown(t *testing.T) { 373 lock := new(sync.RWMutex) 374 state := &State{ 375 Modules: []*ModuleState{ 376 &ModuleState{ 377 Path: rootModulePath, 378 Resources: map[string]*ResourceState{ 379 "aws_instance.web.0": &ResourceState{ 380 Type: "aws_instance", 381 Primary: &InstanceState{ 382 ID: "bar", 383 Attributes: map[string]string{ 384 "foo": "1", 385 }, 386 }, 387 }, 388 "aws_instance.web.1": &ResourceState{ 389 Type: "aws_instance", 390 Primary: &InstanceState{ 391 ID: "bar", 392 Attributes: map[string]string{ 393 "foo": config.UnknownVariableValue, 394 }, 395 }, 396 }, 397 "aws_instance.web.2": &ResourceState{ 398 Type: "aws_instance", 399 Primary: &InstanceState{ 400 ID: "bar", 401 Attributes: map[string]string{ 402 "foo": "2", 403 }, 404 }, 405 }, 406 }, 407 }, 408 }, 409 } 410 411 i := &Interpolater{ 412 Module: testModule(t, "interpolate-resource-variable-multi"), 413 State: state, 414 StateLock: lock, 415 } 416 417 scope := &InterpolationScope{ 418 Path: rootModulePath, 419 } 420 421 testInterpolate(t, i, scope, "aws_instance.web.*.foo", ast.Variable{ 422 Type: ast.TypeList, 423 Value: []ast.Variable{ 424 { 425 Type: ast.TypeString, 426 Value: "1", 427 }, 428 { 429 Type: ast.TypeUnknown, 430 Value: config.UnknownVariableValue, 431 }, 432 { 433 Type: ast.TypeString, 434 Value: "2", 435 }, 436 }, 437 }) 438 } 439 440 // When a splat reference is made to an attribute that is a computed list, 441 // the result should be unknown. 442 func TestInterpolater_resourceVariableMultiList(t *testing.T) { 443 lock := new(sync.RWMutex) 444 state := &State{ 445 Modules: []*ModuleState{ 446 &ModuleState{ 447 Path: rootModulePath, 448 Resources: map[string]*ResourceState{ 449 "aws_instance.web.0": &ResourceState{ 450 Type: "aws_instance", 451 Primary: &InstanceState{ 452 ID: "bar", 453 Attributes: map[string]string{ 454 "ip.#": config.UnknownVariableValue, 455 }, 456 }, 457 }, 458 459 "aws_instance.web.1": &ResourceState{ 460 Type: "aws_instance", 461 Primary: &InstanceState{ 462 ID: "bar", 463 Attributes: map[string]string{ 464 "ip.#": "0", 465 }, 466 }, 467 }, 468 }, 469 }, 470 }, 471 } 472 473 i := &Interpolater{ 474 Module: testModule(t, "interpolate-resource-variable"), 475 State: state, 476 StateLock: lock, 477 } 478 479 scope := &InterpolationScope{ 480 Path: rootModulePath, 481 } 482 483 testInterpolate(t, i, scope, "aws_instance.web.*.ip", ast.Variable{ 484 Type: ast.TypeList, 485 Value: []ast.Variable{ 486 { 487 Type: ast.TypeUnknown, 488 Value: config.UnknownVariableValue, 489 }, 490 }, 491 }) 492 } 493 494 func TestInterpolater_resourceVariableMulti_interpolated(t *testing.T) { 495 lock := new(sync.RWMutex) 496 state := &State{ 497 Modules: []*ModuleState{ 498 &ModuleState{ 499 Path: rootModulePath, 500 Resources: map[string]*ResourceState{ 501 "aws_instance.web.0": &ResourceState{ 502 Type: "aws_instance", 503 Primary: &InstanceState{ 504 ID: "a", 505 Attributes: map[string]string{"foo": "a"}, 506 }, 507 }, 508 509 "aws_instance.web.1": &ResourceState{ 510 Type: "aws_instance", 511 Primary: &InstanceState{ 512 ID: "b", 513 Attributes: map[string]string{"foo": "b"}, 514 }, 515 }, 516 }, 517 }, 518 }, 519 } 520 521 i := &Interpolater{ 522 Operation: walkApply, 523 Module: testModule(t, "interpolate-multi-interp"), 524 State: state, 525 StateLock: lock, 526 } 527 528 scope := &InterpolationScope{ 529 Path: rootModulePath, 530 } 531 532 expected := []interface{}{"a", "b"} 533 testInterpolate(t, i, scope, "aws_instance.web.*.foo", 534 interfaceToVariableSwallowError(expected)) 535 } 536 537 func interfaceToVariableSwallowError(input interface{}) ast.Variable { 538 variable, _ := hil.InterfaceToVariable(input) 539 return variable 540 } 541 542 func TestInterpolator_resourceMultiAttributes(t *testing.T) { 543 lock := new(sync.RWMutex) 544 state := &State{ 545 Modules: []*ModuleState{ 546 { 547 Path: rootModulePath, 548 Resources: map[string]*ResourceState{ 549 "aws_route53_zone.yada": { 550 Type: "aws_route53_zone", 551 Dependencies: []string{}, 552 Primary: &InstanceState{ 553 ID: "AAABBBCCCDDDEEE", 554 Attributes: map[string]string{ 555 "name_servers.#": "4", 556 "name_servers.0": "ns-1334.awsdns-38.org", 557 "name_servers.1": "ns-1680.awsdns-18.co.uk", 558 "name_servers.2": "ns-498.awsdns-62.com", 559 "name_servers.3": "ns-601.awsdns-11.net", 560 "listeners.#": "1", 561 "listeners.0": "red", 562 "tags.%": "1", 563 "tags.Name": "reindeer", 564 "nothing.#": "0", 565 }, 566 }, 567 }, 568 }, 569 }, 570 }, 571 } 572 573 i := &Interpolater{ 574 Module: testModule(t, "interpolate-multi-vars"), 575 StateLock: lock, 576 State: state, 577 } 578 579 scope := &InterpolationScope{ 580 Path: rootModulePath, 581 } 582 583 name_servers := []interface{}{ 584 "ns-1334.awsdns-38.org", 585 "ns-1680.awsdns-18.co.uk", 586 "ns-498.awsdns-62.com", 587 "ns-601.awsdns-11.net", 588 } 589 590 // More than 1 element 591 testInterpolate(t, i, scope, "aws_route53_zone.yada.name_servers", 592 interfaceToVariableSwallowError(name_servers)) 593 594 // Exactly 1 element 595 testInterpolate(t, i, scope, "aws_route53_zone.yada.listeners", 596 interfaceToVariableSwallowError([]interface{}{"red"})) 597 598 // Zero elements 599 testInterpolate(t, i, scope, "aws_route53_zone.yada.nothing", 600 interfaceToVariableSwallowError([]interface{}{})) 601 602 // Maps still need to work 603 testInterpolate(t, i, scope, "aws_route53_zone.yada.tags.Name", ast.Variable{ 604 Value: "reindeer", 605 Type: ast.TypeString, 606 }) 607 } 608 609 func TestInterpolator_resourceMultiAttributesWithResourceCount(t *testing.T) { 610 i := getInterpolaterFixture(t) 611 scope := &InterpolationScope{ 612 Path: rootModulePath, 613 } 614 615 name_servers := []interface{}{ 616 "ns-1334.awsdns-38.org", 617 "ns-1680.awsdns-18.co.uk", 618 "ns-498.awsdns-62.com", 619 "ns-601.awsdns-11.net", 620 "ns-000.awsdns-38.org", 621 "ns-444.awsdns-18.co.uk", 622 "ns-999.awsdns-62.com", 623 "ns-666.awsdns-11.net", 624 } 625 626 // More than 1 element 627 testInterpolate(t, i, scope, "aws_route53_zone.terra.0.name_servers", 628 interfaceToVariableSwallowError(name_servers[:4])) 629 630 // More than 1 element in both 631 testInterpolate(t, i, scope, "aws_route53_zone.terra.*.name_servers", 632 interfaceToVariableSwallowError([]interface{}{name_servers[:4], name_servers[4:]})) 633 634 // Exactly 1 element 635 testInterpolate(t, i, scope, "aws_route53_zone.terra.0.listeners", 636 interfaceToVariableSwallowError([]interface{}{"red"})) 637 638 // Exactly 1 element in both 639 testInterpolate(t, i, scope, "aws_route53_zone.terra.*.listeners", 640 interfaceToVariableSwallowError([]interface{}{[]interface{}{"red"}, []interface{}{"blue"}})) 641 642 // Zero elements 643 testInterpolate(t, i, scope, "aws_route53_zone.terra.0.nothing", 644 interfaceToVariableSwallowError([]interface{}{})) 645 646 // Zero + 1 element 647 testInterpolate(t, i, scope, "aws_route53_zone.terra.*.special", 648 interfaceToVariableSwallowError([]interface{}{[]interface{}{"extra"}})) 649 650 // Maps still need to work 651 testInterpolate(t, i, scope, "aws_route53_zone.terra.0.tags.Name", ast.Variable{ 652 Value: "reindeer", 653 Type: ast.TypeString, 654 }) 655 656 // Maps still need to work in both 657 testInterpolate(t, i, scope, "aws_route53_zone.terra.*.tags.Name", 658 interfaceToVariableSwallowError([]interface{}{"reindeer", "white-hart"})) 659 } 660 661 func TestInterpolator_resourceMultiAttributesComputed(t *testing.T) { 662 lock := new(sync.RWMutex) 663 // The state would never be written with an UnknownVariableValue in it, but 664 // it can/does exist that way in memory during the plan phase. 665 state := &State{ 666 Modules: []*ModuleState{ 667 &ModuleState{ 668 Path: rootModulePath, 669 Resources: map[string]*ResourceState{ 670 "aws_route53_zone.yada": &ResourceState{ 671 Type: "aws_route53_zone", 672 Primary: &InstanceState{ 673 ID: "z-abc123", 674 Attributes: map[string]string{ 675 "name_servers.#": config.UnknownVariableValue, 676 }, 677 }, 678 }, 679 }, 680 }, 681 }, 682 } 683 i := &Interpolater{ 684 Module: testModule(t, "interpolate-multi-vars"), 685 StateLock: lock, 686 State: state, 687 } 688 689 scope := &InterpolationScope{ 690 Path: rootModulePath, 691 } 692 693 testInterpolate(t, i, scope, "aws_route53_zone.yada.name_servers", ast.Variable{ 694 Value: config.UnknownVariableValue, 695 Type: ast.TypeUnknown, 696 }) 697 } 698 699 func TestInterpolator_resourceAttributeComputed(t *testing.T) { 700 lock := new(sync.RWMutex) 701 // The state would never be written with an UnknownVariableValue in it, but 702 // it can/does exist that way in memory during the plan phase. 703 state := &State{ 704 Modules: []*ModuleState{ 705 &ModuleState{ 706 Path: rootModulePath, 707 Resources: map[string]*ResourceState{ 708 "aws_route53_zone.yada": &ResourceState{ 709 Type: "aws_route53_zone", 710 Primary: &InstanceState{ 711 ID: "z-abc123", 712 Attributes: map[string]string{ 713 "id": config.UnknownVariableValue, 714 }, 715 }, 716 }, 717 }, 718 }, 719 }, 720 } 721 i := &Interpolater{ 722 Module: testModule(t, "interpolate-multi-vars"), 723 StateLock: lock, 724 State: state, 725 } 726 727 scope := &InterpolationScope{ 728 Path: rootModulePath, 729 } 730 731 testInterpolate(t, i, scope, "aws_route53_zone.yada.id", ast.Variable{ 732 Value: config.UnknownVariableValue, 733 Type: ast.TypeUnknown, 734 }) 735 } 736 737 func TestInterpolater_selfVarWithoutResource(t *testing.T) { 738 i := &Interpolater{} 739 740 scope := &InterpolationScope{ 741 Path: rootModulePath, 742 } 743 744 v, err := config.NewInterpolatedVariable("self.name") 745 if err != nil { 746 t.Fatalf("err: %s", err) 747 } 748 749 _, err = i.Values(scope, map[string]config.InterpolatedVariable{"foo": v}) 750 if err == nil { 751 t.Fatalf("expected err, got none") 752 } 753 } 754 755 func TestInterpolator_interpolatedListOrder(t *testing.T) { 756 state := &State{ 757 Modules: []*ModuleState{ 758 &ModuleState{ 759 Path: rootModulePath, 760 Resources: map[string]*ResourceState{ 761 "aws_route53_zone.yada": &ResourceState{ 762 Type: "aws_route53_zone", 763 Dependencies: []string{}, 764 Primary: &InstanceState{ 765 ID: "null", 766 Attributes: map[string]string{ 767 "foo.#": "12", 768 "foo.0": "a", 769 "foo.1": "b", 770 "foo.2": "c", 771 "foo.3": "d", 772 "foo.4": "e", 773 "foo.5": "f", 774 "foo.6": "g", 775 "foo.7": "h", 776 "foo.8": "i", 777 "foo.9": "j", 778 "foo.10": "k", 779 "foo.11": "l", 780 }, 781 }, 782 }, 783 }, 784 }, 785 }, 786 } 787 788 i := &Interpolater{ 789 Module: testModule(t, "interpolate-multi-vars"), 790 StateLock: new(sync.RWMutex), 791 State: state, 792 } 793 794 scope := &InterpolationScope{ 795 Path: rootModulePath, 796 } 797 798 list := []interface{}{"a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l"} 799 800 testInterpolate(t, i, scope, "aws_route53_zone.yada.foo", 801 interfaceToVariableSwallowError(list)) 802 } 803 804 func getInterpolaterFixture(t *testing.T) *Interpolater { 805 lock := new(sync.RWMutex) 806 state := &State{ 807 Modules: []*ModuleState{ 808 &ModuleState{ 809 Path: rootModulePath, 810 Resources: map[string]*ResourceState{ 811 "aws_route53_zone.terra.0": &ResourceState{ 812 Type: "aws_route53_zone", 813 Dependencies: []string{}, 814 Primary: &InstanceState{ 815 ID: "AAABBBCCCDDDEEE", 816 Attributes: map[string]string{ 817 "name_servers.#": "4", 818 "name_servers.0": "ns-1334.awsdns-38.org", 819 "name_servers.1": "ns-1680.awsdns-18.co.uk", 820 "name_servers.2": "ns-498.awsdns-62.com", 821 "name_servers.3": "ns-601.awsdns-11.net", 822 "listeners.#": "1", 823 "listeners.0": "red", 824 "tags.%": "1", 825 "tags.Name": "reindeer", 826 "nothing.#": "0", 827 }, 828 }, 829 }, 830 "aws_route53_zone.terra.1": &ResourceState{ 831 Type: "aws_route53_zone", 832 Dependencies: []string{}, 833 Primary: &InstanceState{ 834 ID: "EEEFFFGGGHHHIII", 835 Attributes: map[string]string{ 836 "name_servers.#": "4", 837 "name_servers.0": "ns-000.awsdns-38.org", 838 "name_servers.1": "ns-444.awsdns-18.co.uk", 839 "name_servers.2": "ns-999.awsdns-62.com", 840 "name_servers.3": "ns-666.awsdns-11.net", 841 "listeners.#": "1", 842 "listeners.0": "blue", 843 "special.#": "1", 844 "special.0": "extra", 845 "tags.%": "1", 846 "tags.Name": "white-hart", 847 "nothing.#": "0", 848 }, 849 }, 850 }, 851 }, 852 }, 853 }, 854 } 855 856 return &Interpolater{ 857 Module: testModule(t, "interpolate-multi-vars"), 858 StateLock: lock, 859 State: state, 860 } 861 } 862 863 func TestInterpolator_nestedMapsAndLists(t *testing.T) { 864 state := &State{ 865 Modules: []*ModuleState{ 866 &ModuleState{ 867 Path: rootModulePath, 868 Resources: map[string]*ResourceState{ 869 "aws_route53_zone.yada": &ResourceState{ 870 Type: "aws_route53_zone", 871 Dependencies: []string{}, 872 Primary: &InstanceState{ 873 ID: "null", 874 Attributes: map[string]string{ 875 "list_of_map.#": "2", 876 "list_of_map.0.%": "1", 877 "list_of_map.0.a": "1", 878 "list_of_map.1.%": "1", 879 "list_of_map.1.b": "2", 880 "map_of_list.%": "2", 881 "map_of_list.list2.#": "1", 882 "map_of_list.list2.0": "b", 883 "map_of_list.list1.#": "1", 884 "map_of_list.list1.0": "a", 885 }, 886 }, 887 }, 888 }, 889 }, 890 }, 891 } 892 893 i := &Interpolater{ 894 Module: testModule(t, "interpolate-multi-vars"), 895 StateLock: new(sync.RWMutex), 896 State: state, 897 } 898 899 scope := &InterpolationScope{ 900 Path: rootModulePath, 901 } 902 903 listOfMap := []interface{}{ 904 map[string]interface{}{"a": "1"}, 905 map[string]interface{}{"b": "2"}, 906 } 907 908 mapOfList := map[string]interface{}{ 909 "list1": []interface{}{"a"}, 910 "list2": []interface{}{"b"}, 911 } 912 913 testInterpolate(t, i, scope, "aws_route53_zone.yada.list_of_map", 914 interfaceToVariableSwallowError(listOfMap)) 915 testInterpolate(t, i, scope, `aws_route53_zone.yada.map_of_list`, 916 interfaceToVariableSwallowError(mapOfList)) 917 } 918 919 func TestInterpolator_sets(t *testing.T) { 920 state := &State{ 921 Modules: []*ModuleState{ 922 &ModuleState{ 923 Path: rootModulePath, 924 Resources: map[string]*ResourceState{ 925 "aws_route53_zone.yada": &ResourceState{ 926 Type: "aws_network_interface", 927 Dependencies: []string{}, 928 Primary: &InstanceState{ 929 ID: "null", 930 Attributes: map[string]string{ 931 "private_ips.#": "1", 932 "private_ips.3977356764": "10.42.16.179", 933 }, 934 }, 935 }, 936 }, 937 }, 938 }, 939 } 940 941 i := &Interpolater{ 942 Module: testModule(t, "interpolate-multi-vars"), 943 StateLock: new(sync.RWMutex), 944 State: state, 945 } 946 947 scope := &InterpolationScope{ 948 Path: rootModulePath, 949 } 950 951 set := []interface{}{"10.42.16.179"} 952 953 testInterpolate(t, i, scope, "aws_route53_zone.yada.private_ips", 954 interfaceToVariableSwallowError(set)) 955 } 956 957 // When a splat reference is made to a resource that is unknown, we should 958 // return an empty list rather than panicking. 959 func TestInterpolater_resourceUnknownVariableList(t *testing.T) { 960 i := &Interpolater{ 961 Module: testModule(t, "plan-computed-data-resource"), 962 State: NewState(), // state, 963 StateLock: new(sync.RWMutex), 964 } 965 966 scope := &InterpolationScope{ 967 Path: rootModulePath, 968 } 969 970 testInterpolate(t, i, scope, "aws_vpc.bar.*.foo", 971 interfaceToVariableSwallowError([]interface{}{})) 972 } 973 974 func TestInterpolater_terraformEnv(t *testing.T) { 975 i := &Interpolater{ 976 Meta: &ContextMeta{Env: "foo"}, 977 } 978 979 scope := &InterpolationScope{ 980 Path: rootModulePath, 981 } 982 983 testInterpolate(t, i, scope, "terraform.env", ast.Variable{ 984 Value: "foo", 985 Type: ast.TypeString, 986 }) 987 } 988 989 func TestInterpolater_terraformInvalid(t *testing.T) { 990 i := &Interpolater{ 991 Meta: &ContextMeta{Env: "foo"}, 992 } 993 994 scope := &InterpolationScope{ 995 Path: rootModulePath, 996 } 997 998 testInterpolateErr(t, i, scope, "terraform.nope") 999 } 1000 1001 func testInterpolate( 1002 t *testing.T, i *Interpolater, 1003 scope *InterpolationScope, 1004 n string, expectedVar ast.Variable) { 1005 v, err := config.NewInterpolatedVariable(n) 1006 if err != nil { 1007 t.Fatalf("err: %s", err) 1008 } 1009 1010 actual, err := i.Values(scope, map[string]config.InterpolatedVariable{ 1011 "foo": v, 1012 }) 1013 if err != nil { 1014 t.Fatalf("err: %s", err) 1015 } 1016 1017 expected := map[string]ast.Variable{ 1018 "foo": expectedVar, 1019 } 1020 if !reflect.DeepEqual(actual, expected) { 1021 spew.Config.DisableMethods = true 1022 t.Fatalf("%q:\n\n actual: %#v\nexpected: %#v\n\n%s\n\n%s\n\n", n, actual, expected, 1023 spew.Sdump(actual), spew.Sdump(expected)) 1024 } 1025 } 1026 1027 func testInterpolateErr( 1028 t *testing.T, i *Interpolater, 1029 scope *InterpolationScope, 1030 n string) { 1031 v, err := config.NewInterpolatedVariable(n) 1032 if err != nil { 1033 t.Fatalf("err: %s", err) 1034 } 1035 1036 _, err = i.Values(scope, map[string]config.InterpolatedVariable{ 1037 "foo": v, 1038 }) 1039 if err == nil { 1040 t.Fatalf("%q: succeeded, but wanted error", n) 1041 } 1042 }