github.com/paybyphone/terraform@v0.9.5-0.20170613192930-9706042ddd51/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 func TestInterpolater_resourceVariableMultiNoState(t *testing.T) { 441 // When evaluating a "splat" variable in a module that doesn't have 442 // any state yet, we should still be able to resolve to an empty 443 // list. 444 // See https://github.com/hashicorp/terraform/issues/14438 for an 445 // example of what we're testing for here. 446 lock := new(sync.RWMutex) 447 state := &State{ 448 Modules: []*ModuleState{}, 449 } 450 451 i := &Interpolater{ 452 Module: testModule(t, "interpolate-resource-variable-multi"), 453 State: state, 454 StateLock: lock, 455 Operation: walkApply, 456 } 457 458 scope := &InterpolationScope{ 459 Path: rootModulePath, 460 } 461 462 testInterpolate(t, i, scope, "aws_instance.web.*.foo", ast.Variable{ 463 Type: ast.TypeList, 464 Value: []ast.Variable{}, 465 }) 466 } 467 468 // When a splat reference is made to an attribute that is a computed list, 469 // the result should be unknown. 470 func TestInterpolater_resourceVariableMultiList(t *testing.T) { 471 lock := new(sync.RWMutex) 472 state := &State{ 473 Modules: []*ModuleState{ 474 &ModuleState{ 475 Path: rootModulePath, 476 Resources: map[string]*ResourceState{ 477 "aws_instance.web.0": &ResourceState{ 478 Type: "aws_instance", 479 Primary: &InstanceState{ 480 ID: "bar", 481 Attributes: map[string]string{ 482 "ip.#": config.UnknownVariableValue, 483 }, 484 }, 485 }, 486 487 "aws_instance.web.1": &ResourceState{ 488 Type: "aws_instance", 489 Primary: &InstanceState{ 490 ID: "bar", 491 Attributes: map[string]string{ 492 "ip.#": "0", 493 }, 494 }, 495 }, 496 }, 497 }, 498 }, 499 } 500 501 i := &Interpolater{ 502 Module: testModule(t, "interpolate-resource-variable"), 503 State: state, 504 StateLock: lock, 505 } 506 507 scope := &InterpolationScope{ 508 Path: rootModulePath, 509 } 510 511 testInterpolate(t, i, scope, "aws_instance.web.*.ip", ast.Variable{ 512 Type: ast.TypeList, 513 Value: []ast.Variable{ 514 { 515 Type: ast.TypeUnknown, 516 Value: config.UnknownVariableValue, 517 }, 518 }, 519 }) 520 } 521 522 func TestInterpolater_resourceVariableMulti_interpolated(t *testing.T) { 523 lock := new(sync.RWMutex) 524 state := &State{ 525 Modules: []*ModuleState{ 526 &ModuleState{ 527 Path: rootModulePath, 528 Resources: map[string]*ResourceState{ 529 "aws_instance.web.0": &ResourceState{ 530 Type: "aws_instance", 531 Primary: &InstanceState{ 532 ID: "a", 533 Attributes: map[string]string{"foo": "a"}, 534 }, 535 }, 536 537 "aws_instance.web.1": &ResourceState{ 538 Type: "aws_instance", 539 Primary: &InstanceState{ 540 ID: "b", 541 Attributes: map[string]string{"foo": "b"}, 542 }, 543 }, 544 }, 545 }, 546 }, 547 } 548 549 i := &Interpolater{ 550 Operation: walkApply, 551 Module: testModule(t, "interpolate-multi-interp"), 552 State: state, 553 StateLock: lock, 554 } 555 556 scope := &InterpolationScope{ 557 Path: rootModulePath, 558 } 559 560 expected := []interface{}{"a", "b"} 561 testInterpolate(t, i, scope, "aws_instance.web.*.foo", 562 interfaceToVariableSwallowError(expected)) 563 } 564 565 func interfaceToVariableSwallowError(input interface{}) ast.Variable { 566 variable, _ := hil.InterfaceToVariable(input) 567 return variable 568 } 569 570 func TestInterpolator_resourceMultiAttributes(t *testing.T) { 571 lock := new(sync.RWMutex) 572 state := &State{ 573 Modules: []*ModuleState{ 574 { 575 Path: rootModulePath, 576 Resources: map[string]*ResourceState{ 577 "aws_route53_zone.yada": { 578 Type: "aws_route53_zone", 579 Dependencies: []string{}, 580 Primary: &InstanceState{ 581 ID: "AAABBBCCCDDDEEE", 582 Attributes: map[string]string{ 583 "name_servers.#": "4", 584 "name_servers.0": "ns-1334.awsdns-38.org", 585 "name_servers.1": "ns-1680.awsdns-18.co.uk", 586 "name_servers.2": "ns-498.awsdns-62.com", 587 "name_servers.3": "ns-601.awsdns-11.net", 588 "listeners.#": "1", 589 "listeners.0": "red", 590 "tags.%": "1", 591 "tags.Name": "reindeer", 592 "nothing.#": "0", 593 }, 594 }, 595 }, 596 }, 597 }, 598 }, 599 } 600 601 i := &Interpolater{ 602 Module: testModule(t, "interpolate-multi-vars"), 603 StateLock: lock, 604 State: state, 605 } 606 607 scope := &InterpolationScope{ 608 Path: rootModulePath, 609 } 610 611 name_servers := []interface{}{ 612 "ns-1334.awsdns-38.org", 613 "ns-1680.awsdns-18.co.uk", 614 "ns-498.awsdns-62.com", 615 "ns-601.awsdns-11.net", 616 } 617 618 // More than 1 element 619 testInterpolate(t, i, scope, "aws_route53_zone.yada.name_servers", 620 interfaceToVariableSwallowError(name_servers)) 621 622 // Exactly 1 element 623 testInterpolate(t, i, scope, "aws_route53_zone.yada.listeners", 624 interfaceToVariableSwallowError([]interface{}{"red"})) 625 626 // Zero elements 627 testInterpolate(t, i, scope, "aws_route53_zone.yada.nothing", 628 interfaceToVariableSwallowError([]interface{}{})) 629 630 // Maps still need to work 631 testInterpolate(t, i, scope, "aws_route53_zone.yada.tags.Name", ast.Variable{ 632 Value: "reindeer", 633 Type: ast.TypeString, 634 }) 635 } 636 637 func TestInterpolator_resourceMultiAttributesWithResourceCount(t *testing.T) { 638 i := getInterpolaterFixture(t) 639 scope := &InterpolationScope{ 640 Path: rootModulePath, 641 } 642 643 name_servers := []interface{}{ 644 "ns-1334.awsdns-38.org", 645 "ns-1680.awsdns-18.co.uk", 646 "ns-498.awsdns-62.com", 647 "ns-601.awsdns-11.net", 648 "ns-000.awsdns-38.org", 649 "ns-444.awsdns-18.co.uk", 650 "ns-999.awsdns-62.com", 651 "ns-666.awsdns-11.net", 652 } 653 654 // More than 1 element 655 testInterpolate(t, i, scope, "aws_route53_zone.terra.0.name_servers", 656 interfaceToVariableSwallowError(name_servers[:4])) 657 658 // More than 1 element in both 659 testInterpolate(t, i, scope, "aws_route53_zone.terra.*.name_servers", 660 interfaceToVariableSwallowError([]interface{}{name_servers[:4], name_servers[4:]})) 661 662 // Exactly 1 element 663 testInterpolate(t, i, scope, "aws_route53_zone.terra.0.listeners", 664 interfaceToVariableSwallowError([]interface{}{"red"})) 665 666 // Exactly 1 element in both 667 testInterpolate(t, i, scope, "aws_route53_zone.terra.*.listeners", 668 interfaceToVariableSwallowError([]interface{}{[]interface{}{"red"}, []interface{}{"blue"}})) 669 670 // Zero elements 671 testInterpolate(t, i, scope, "aws_route53_zone.terra.0.nothing", 672 interfaceToVariableSwallowError([]interface{}{})) 673 674 // Zero + 1 element 675 testInterpolate(t, i, scope, "aws_route53_zone.terra.*.special", 676 interfaceToVariableSwallowError([]interface{}{[]interface{}{"extra"}})) 677 678 // Maps still need to work 679 testInterpolate(t, i, scope, "aws_route53_zone.terra.0.tags.Name", ast.Variable{ 680 Value: "reindeer", 681 Type: ast.TypeString, 682 }) 683 684 // Maps still need to work in both 685 testInterpolate(t, i, scope, "aws_route53_zone.terra.*.tags.Name", 686 interfaceToVariableSwallowError([]interface{}{"reindeer", "white-hart"})) 687 } 688 689 func TestInterpolator_resourceMultiAttributesComputed(t *testing.T) { 690 lock := new(sync.RWMutex) 691 // The state would never be written with an UnknownVariableValue in it, but 692 // it can/does exist that way in memory during the plan phase. 693 state := &State{ 694 Modules: []*ModuleState{ 695 &ModuleState{ 696 Path: rootModulePath, 697 Resources: map[string]*ResourceState{ 698 "aws_route53_zone.yada": &ResourceState{ 699 Type: "aws_route53_zone", 700 Primary: &InstanceState{ 701 ID: "z-abc123", 702 Attributes: map[string]string{ 703 "name_servers.#": config.UnknownVariableValue, 704 }, 705 }, 706 }, 707 }, 708 }, 709 }, 710 } 711 i := &Interpolater{ 712 Module: testModule(t, "interpolate-multi-vars"), 713 StateLock: lock, 714 State: state, 715 } 716 717 scope := &InterpolationScope{ 718 Path: rootModulePath, 719 } 720 721 testInterpolate(t, i, scope, "aws_route53_zone.yada.name_servers", ast.Variable{ 722 Value: config.UnknownVariableValue, 723 Type: ast.TypeUnknown, 724 }) 725 } 726 727 func TestInterpolator_resourceAttributeComputed(t *testing.T) { 728 lock := new(sync.RWMutex) 729 // The state would never be written with an UnknownVariableValue in it, but 730 // it can/does exist that way in memory during the plan phase. 731 state := &State{ 732 Modules: []*ModuleState{ 733 &ModuleState{ 734 Path: rootModulePath, 735 Resources: map[string]*ResourceState{ 736 "aws_route53_zone.yada": &ResourceState{ 737 Type: "aws_route53_zone", 738 Primary: &InstanceState{ 739 ID: "z-abc123", 740 Attributes: map[string]string{ 741 "id": config.UnknownVariableValue, 742 }, 743 }, 744 }, 745 }, 746 }, 747 }, 748 } 749 i := &Interpolater{ 750 Module: testModule(t, "interpolate-multi-vars"), 751 StateLock: lock, 752 State: state, 753 } 754 755 scope := &InterpolationScope{ 756 Path: rootModulePath, 757 } 758 759 testInterpolate(t, i, scope, "aws_route53_zone.yada.id", ast.Variable{ 760 Value: config.UnknownVariableValue, 761 Type: ast.TypeUnknown, 762 }) 763 } 764 765 func TestInterpolater_selfVarWithoutResource(t *testing.T) { 766 i := &Interpolater{} 767 768 scope := &InterpolationScope{ 769 Path: rootModulePath, 770 } 771 772 v, err := config.NewInterpolatedVariable("self.name") 773 if err != nil { 774 t.Fatalf("err: %s", err) 775 } 776 777 _, err = i.Values(scope, map[string]config.InterpolatedVariable{"foo": v}) 778 if err == nil { 779 t.Fatalf("expected err, got none") 780 } 781 } 782 783 func TestInterpolator_interpolatedListOrder(t *testing.T) { 784 state := &State{ 785 Modules: []*ModuleState{ 786 &ModuleState{ 787 Path: rootModulePath, 788 Resources: map[string]*ResourceState{ 789 "aws_route53_zone.yada": &ResourceState{ 790 Type: "aws_route53_zone", 791 Dependencies: []string{}, 792 Primary: &InstanceState{ 793 ID: "null", 794 Attributes: map[string]string{ 795 "foo.#": "12", 796 "foo.0": "a", 797 "foo.1": "b", 798 "foo.2": "c", 799 "foo.3": "d", 800 "foo.4": "e", 801 "foo.5": "f", 802 "foo.6": "g", 803 "foo.7": "h", 804 "foo.8": "i", 805 "foo.9": "j", 806 "foo.10": "k", 807 "foo.11": "l", 808 }, 809 }, 810 }, 811 }, 812 }, 813 }, 814 } 815 816 i := &Interpolater{ 817 Module: testModule(t, "interpolate-multi-vars"), 818 StateLock: new(sync.RWMutex), 819 State: state, 820 } 821 822 scope := &InterpolationScope{ 823 Path: rootModulePath, 824 } 825 826 list := []interface{}{"a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l"} 827 828 testInterpolate(t, i, scope, "aws_route53_zone.yada.foo", 829 interfaceToVariableSwallowError(list)) 830 } 831 832 func getInterpolaterFixture(t *testing.T) *Interpolater { 833 lock := new(sync.RWMutex) 834 state := &State{ 835 Modules: []*ModuleState{ 836 &ModuleState{ 837 Path: rootModulePath, 838 Resources: map[string]*ResourceState{ 839 "aws_route53_zone.terra.0": &ResourceState{ 840 Type: "aws_route53_zone", 841 Dependencies: []string{}, 842 Primary: &InstanceState{ 843 ID: "AAABBBCCCDDDEEE", 844 Attributes: map[string]string{ 845 "name_servers.#": "4", 846 "name_servers.0": "ns-1334.awsdns-38.org", 847 "name_servers.1": "ns-1680.awsdns-18.co.uk", 848 "name_servers.2": "ns-498.awsdns-62.com", 849 "name_servers.3": "ns-601.awsdns-11.net", 850 "listeners.#": "1", 851 "listeners.0": "red", 852 "tags.%": "1", 853 "tags.Name": "reindeer", 854 "nothing.#": "0", 855 }, 856 }, 857 }, 858 "aws_route53_zone.terra.1": &ResourceState{ 859 Type: "aws_route53_zone", 860 Dependencies: []string{}, 861 Primary: &InstanceState{ 862 ID: "EEEFFFGGGHHHIII", 863 Attributes: map[string]string{ 864 "name_servers.#": "4", 865 "name_servers.0": "ns-000.awsdns-38.org", 866 "name_servers.1": "ns-444.awsdns-18.co.uk", 867 "name_servers.2": "ns-999.awsdns-62.com", 868 "name_servers.3": "ns-666.awsdns-11.net", 869 "listeners.#": "1", 870 "listeners.0": "blue", 871 "special.#": "1", 872 "special.0": "extra", 873 "tags.%": "1", 874 "tags.Name": "white-hart", 875 "nothing.#": "0", 876 }, 877 }, 878 }, 879 }, 880 }, 881 }, 882 } 883 884 return &Interpolater{ 885 Module: testModule(t, "interpolate-multi-vars"), 886 StateLock: lock, 887 State: state, 888 } 889 } 890 891 func TestInterpolator_nestedMapsAndLists(t *testing.T) { 892 state := &State{ 893 Modules: []*ModuleState{ 894 &ModuleState{ 895 Path: rootModulePath, 896 Resources: map[string]*ResourceState{ 897 "aws_route53_zone.yada": &ResourceState{ 898 Type: "aws_route53_zone", 899 Dependencies: []string{}, 900 Primary: &InstanceState{ 901 ID: "null", 902 Attributes: map[string]string{ 903 "list_of_map.#": "2", 904 "list_of_map.0.%": "1", 905 "list_of_map.0.a": "1", 906 "list_of_map.1.%": "1", 907 "list_of_map.1.b": "2", 908 "map_of_list.%": "2", 909 "map_of_list.list2.#": "1", 910 "map_of_list.list2.0": "b", 911 "map_of_list.list1.#": "1", 912 "map_of_list.list1.0": "a", 913 }, 914 }, 915 }, 916 }, 917 }, 918 }, 919 } 920 921 i := &Interpolater{ 922 Module: testModule(t, "interpolate-multi-vars"), 923 StateLock: new(sync.RWMutex), 924 State: state, 925 } 926 927 scope := &InterpolationScope{ 928 Path: rootModulePath, 929 } 930 931 listOfMap := []interface{}{ 932 map[string]interface{}{"a": "1"}, 933 map[string]interface{}{"b": "2"}, 934 } 935 936 mapOfList := map[string]interface{}{ 937 "list1": []interface{}{"a"}, 938 "list2": []interface{}{"b"}, 939 } 940 941 testInterpolate(t, i, scope, "aws_route53_zone.yada.list_of_map", 942 interfaceToVariableSwallowError(listOfMap)) 943 testInterpolate(t, i, scope, `aws_route53_zone.yada.map_of_list`, 944 interfaceToVariableSwallowError(mapOfList)) 945 } 946 947 func TestInterpolator_sets(t *testing.T) { 948 state := &State{ 949 Modules: []*ModuleState{ 950 &ModuleState{ 951 Path: rootModulePath, 952 Resources: map[string]*ResourceState{ 953 "aws_route53_zone.yada": &ResourceState{ 954 Type: "aws_network_interface", 955 Dependencies: []string{}, 956 Primary: &InstanceState{ 957 ID: "null", 958 Attributes: map[string]string{ 959 "private_ips.#": "1", 960 "private_ips.3977356764": "10.42.16.179", 961 }, 962 }, 963 }, 964 }, 965 }, 966 }, 967 } 968 969 i := &Interpolater{ 970 Module: testModule(t, "interpolate-multi-vars"), 971 StateLock: new(sync.RWMutex), 972 State: state, 973 } 974 975 scope := &InterpolationScope{ 976 Path: rootModulePath, 977 } 978 979 set := []interface{}{"10.42.16.179"} 980 981 testInterpolate(t, i, scope, "aws_route53_zone.yada.private_ips", 982 interfaceToVariableSwallowError(set)) 983 } 984 985 // When a splat reference is made to a resource that is unknown, we should 986 // return an empty list rather than panicking. 987 func TestInterpolater_resourceUnknownVariableList(t *testing.T) { 988 i := &Interpolater{ 989 Module: testModule(t, "plan-computed-data-resource"), 990 State: NewState(), // state, 991 StateLock: new(sync.RWMutex), 992 } 993 994 scope := &InterpolationScope{ 995 Path: rootModulePath, 996 } 997 998 testInterpolate(t, i, scope, "aws_vpc.bar.*.foo", 999 interfaceToVariableSwallowError([]interface{}{})) 1000 } 1001 1002 func TestInterpolater_terraformEnv(t *testing.T) { 1003 i := &Interpolater{ 1004 Meta: &ContextMeta{Env: "foo"}, 1005 } 1006 1007 scope := &InterpolationScope{ 1008 Path: rootModulePath, 1009 } 1010 1011 testInterpolate(t, i, scope, "terraform.env", ast.Variable{ 1012 Value: "foo", 1013 Type: ast.TypeString, 1014 }) 1015 } 1016 1017 func TestInterpolater_terraformInvalid(t *testing.T) { 1018 i := &Interpolater{ 1019 Meta: &ContextMeta{Env: "foo"}, 1020 } 1021 1022 scope := &InterpolationScope{ 1023 Path: rootModulePath, 1024 } 1025 1026 testInterpolateErr(t, i, scope, "terraform.nope") 1027 } 1028 1029 func testInterpolate( 1030 t *testing.T, i *Interpolater, 1031 scope *InterpolationScope, 1032 n string, expectedVar ast.Variable) { 1033 v, err := config.NewInterpolatedVariable(n) 1034 if err != nil { 1035 t.Fatalf("err: %s", err) 1036 } 1037 1038 actual, err := i.Values(scope, map[string]config.InterpolatedVariable{ 1039 "foo": v, 1040 }) 1041 if err != nil { 1042 t.Fatalf("err: %s", err) 1043 } 1044 1045 expected := map[string]ast.Variable{ 1046 "foo": expectedVar, 1047 } 1048 if !reflect.DeepEqual(actual, expected) { 1049 spew.Config.DisableMethods = true 1050 t.Fatalf("%q:\n\n actual: %#v\nexpected: %#v\n\n%s\n\n%s\n\n", n, actual, expected, 1051 spew.Sdump(actual), spew.Sdump(expected)) 1052 } 1053 } 1054 1055 func testInterpolateErr( 1056 t *testing.T, i *Interpolater, 1057 scope *InterpolationScope, 1058 n string) { 1059 v, err := config.NewInterpolatedVariable(n) 1060 if err != nil { 1061 t.Fatalf("err: %s", err) 1062 } 1063 1064 _, err = i.Values(scope, map[string]config.InterpolatedVariable{ 1065 "foo": v, 1066 }) 1067 if err == nil { 1068 t.Fatalf("%q: succeeded, but wanted error", n) 1069 } 1070 }