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