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