github.com/rhenning/terraform@v0.8.0-beta2/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 Value: config.UnknownVariableValue, 363 Type: ast.TypeUnknown, 364 }) 365 } 366 367 func TestInterpolater_resourceVariableMulti_interpolated(t *testing.T) { 368 lock := new(sync.RWMutex) 369 state := &State{ 370 Modules: []*ModuleState{ 371 &ModuleState{ 372 Path: rootModulePath, 373 Resources: map[string]*ResourceState{ 374 "aws_instance.web.0": &ResourceState{ 375 Type: "aws_instance", 376 Primary: &InstanceState{ 377 ID: "a", 378 Attributes: map[string]string{"foo": "a"}, 379 }, 380 }, 381 382 "aws_instance.web.1": &ResourceState{ 383 Type: "aws_instance", 384 Primary: &InstanceState{ 385 ID: "b", 386 Attributes: map[string]string{"foo": "b"}, 387 }, 388 }, 389 }, 390 }, 391 }, 392 } 393 394 i := &Interpolater{ 395 Operation: walkApply, 396 Module: testModule(t, "interpolate-multi-interp"), 397 State: state, 398 StateLock: lock, 399 } 400 401 scope := &InterpolationScope{ 402 Path: rootModulePath, 403 } 404 405 expected := []interface{}{"a", "b"} 406 testInterpolate(t, i, scope, "aws_instance.web.*.foo", 407 interfaceToVariableSwallowError(expected)) 408 } 409 410 func interfaceToVariableSwallowError(input interface{}) ast.Variable { 411 variable, _ := hil.InterfaceToVariable(input) 412 return variable 413 } 414 415 func TestInterpolator_resourceMultiAttributes(t *testing.T) { 416 lock := new(sync.RWMutex) 417 state := &State{ 418 Modules: []*ModuleState{ 419 { 420 Path: rootModulePath, 421 Resources: map[string]*ResourceState{ 422 "aws_route53_zone.yada": { 423 Type: "aws_route53_zone", 424 Dependencies: []string{}, 425 Primary: &InstanceState{ 426 ID: "AAABBBCCCDDDEEE", 427 Attributes: map[string]string{ 428 "name_servers.#": "4", 429 "name_servers.0": "ns-1334.awsdns-38.org", 430 "name_servers.1": "ns-1680.awsdns-18.co.uk", 431 "name_servers.2": "ns-498.awsdns-62.com", 432 "name_servers.3": "ns-601.awsdns-11.net", 433 "listeners.#": "1", 434 "listeners.0": "red", 435 "tags.%": "1", 436 "tags.Name": "reindeer", 437 "nothing.#": "0", 438 }, 439 }, 440 }, 441 }, 442 }, 443 }, 444 } 445 446 i := &Interpolater{ 447 Module: testModule(t, "interpolate-multi-vars"), 448 StateLock: lock, 449 State: state, 450 } 451 452 scope := &InterpolationScope{ 453 Path: rootModulePath, 454 } 455 456 name_servers := []interface{}{ 457 "ns-1334.awsdns-38.org", 458 "ns-1680.awsdns-18.co.uk", 459 "ns-498.awsdns-62.com", 460 "ns-601.awsdns-11.net", 461 } 462 463 // More than 1 element 464 testInterpolate(t, i, scope, "aws_route53_zone.yada.name_servers", 465 interfaceToVariableSwallowError(name_servers)) 466 467 // Exactly 1 element 468 testInterpolate(t, i, scope, "aws_route53_zone.yada.listeners", 469 interfaceToVariableSwallowError([]interface{}{"red"})) 470 471 // Zero elements 472 testInterpolate(t, i, scope, "aws_route53_zone.yada.nothing", 473 interfaceToVariableSwallowError([]interface{}{})) 474 475 // Maps still need to work 476 testInterpolate(t, i, scope, "aws_route53_zone.yada.tags.Name", ast.Variable{ 477 Value: "reindeer", 478 Type: ast.TypeString, 479 }) 480 } 481 482 func TestInterpolator_resourceMultiAttributesWithResourceCount(t *testing.T) { 483 i := getInterpolaterFixture(t) 484 scope := &InterpolationScope{ 485 Path: rootModulePath, 486 } 487 488 name_servers := []interface{}{ 489 "ns-1334.awsdns-38.org", 490 "ns-1680.awsdns-18.co.uk", 491 "ns-498.awsdns-62.com", 492 "ns-601.awsdns-11.net", 493 "ns-000.awsdns-38.org", 494 "ns-444.awsdns-18.co.uk", 495 "ns-999.awsdns-62.com", 496 "ns-666.awsdns-11.net", 497 } 498 499 // More than 1 element 500 testInterpolate(t, i, scope, "aws_route53_zone.terra.0.name_servers", 501 interfaceToVariableSwallowError(name_servers[:4])) 502 503 // More than 1 element in both 504 testInterpolate(t, i, scope, "aws_route53_zone.terra.*.name_servers", 505 interfaceToVariableSwallowError([]interface{}{name_servers[:4], name_servers[4:]})) 506 507 // Exactly 1 element 508 testInterpolate(t, i, scope, "aws_route53_zone.terra.0.listeners", 509 interfaceToVariableSwallowError([]interface{}{"red"})) 510 511 // Exactly 1 element in both 512 testInterpolate(t, i, scope, "aws_route53_zone.terra.*.listeners", 513 interfaceToVariableSwallowError([]interface{}{[]interface{}{"red"}, []interface{}{"blue"}})) 514 515 // Zero elements 516 testInterpolate(t, i, scope, "aws_route53_zone.terra.0.nothing", 517 interfaceToVariableSwallowError([]interface{}{})) 518 519 // Zero + 1 element 520 testInterpolate(t, i, scope, "aws_route53_zone.terra.*.special", 521 interfaceToVariableSwallowError([]interface{}{[]interface{}{"extra"}})) 522 523 // Maps still need to work 524 testInterpolate(t, i, scope, "aws_route53_zone.terra.0.tags.Name", ast.Variable{ 525 Value: "reindeer", 526 Type: ast.TypeString, 527 }) 528 529 // Maps still need to work in both 530 testInterpolate(t, i, scope, "aws_route53_zone.terra.*.tags.Name", 531 interfaceToVariableSwallowError([]interface{}{"reindeer", "white-hart"})) 532 } 533 534 func TestInterpolator_resourceMultiAttributesComputed(t *testing.T) { 535 lock := new(sync.RWMutex) 536 // The state would never be written with an UnknownVariableValue in it, but 537 // it can/does exist that way in memory during the plan phase. 538 state := &State{ 539 Modules: []*ModuleState{ 540 &ModuleState{ 541 Path: rootModulePath, 542 Resources: map[string]*ResourceState{ 543 "aws_route53_zone.yada": &ResourceState{ 544 Type: "aws_route53_zone", 545 Primary: &InstanceState{ 546 ID: "z-abc123", 547 Attributes: map[string]string{ 548 "name_servers.#": config.UnknownVariableValue, 549 }, 550 }, 551 }, 552 }, 553 }, 554 }, 555 } 556 i := &Interpolater{ 557 Module: testModule(t, "interpolate-multi-vars"), 558 StateLock: lock, 559 State: state, 560 } 561 562 scope := &InterpolationScope{ 563 Path: rootModulePath, 564 } 565 566 testInterpolate(t, i, scope, "aws_route53_zone.yada.name_servers", ast.Variable{ 567 Value: config.UnknownVariableValue, 568 Type: ast.TypeUnknown, 569 }) 570 } 571 572 func TestInterpolator_resourceAttributeComputed(t *testing.T) { 573 lock := new(sync.RWMutex) 574 // The state would never be written with an UnknownVariableValue in it, but 575 // it can/does exist that way in memory during the plan phase. 576 state := &State{ 577 Modules: []*ModuleState{ 578 &ModuleState{ 579 Path: rootModulePath, 580 Resources: map[string]*ResourceState{ 581 "aws_route53_zone.yada": &ResourceState{ 582 Type: "aws_route53_zone", 583 Primary: &InstanceState{ 584 ID: "z-abc123", 585 Attributes: map[string]string{ 586 "id": config.UnknownVariableValue, 587 }, 588 }, 589 }, 590 }, 591 }, 592 }, 593 } 594 i := &Interpolater{ 595 Module: testModule(t, "interpolate-multi-vars"), 596 StateLock: lock, 597 State: state, 598 } 599 600 scope := &InterpolationScope{ 601 Path: rootModulePath, 602 } 603 604 testInterpolate(t, i, scope, "aws_route53_zone.yada.id", ast.Variable{ 605 Value: config.UnknownVariableValue, 606 Type: ast.TypeUnknown, 607 }) 608 } 609 610 func TestInterpolater_selfVarWithoutResource(t *testing.T) { 611 i := &Interpolater{} 612 613 scope := &InterpolationScope{ 614 Path: rootModulePath, 615 } 616 617 v, err := config.NewInterpolatedVariable("self.name") 618 if err != nil { 619 t.Fatalf("err: %s", err) 620 } 621 622 _, err = i.Values(scope, map[string]config.InterpolatedVariable{"foo": v}) 623 if err == nil { 624 t.Fatalf("expected err, got none") 625 } 626 } 627 628 func getInterpolaterFixture(t *testing.T) *Interpolater { 629 lock := new(sync.RWMutex) 630 state := &State{ 631 Modules: []*ModuleState{ 632 &ModuleState{ 633 Path: rootModulePath, 634 Resources: map[string]*ResourceState{ 635 "aws_route53_zone.terra.0": &ResourceState{ 636 Type: "aws_route53_zone", 637 Dependencies: []string{}, 638 Primary: &InstanceState{ 639 ID: "AAABBBCCCDDDEEE", 640 Attributes: map[string]string{ 641 "name_servers.#": "4", 642 "name_servers.0": "ns-1334.awsdns-38.org", 643 "name_servers.1": "ns-1680.awsdns-18.co.uk", 644 "name_servers.2": "ns-498.awsdns-62.com", 645 "name_servers.3": "ns-601.awsdns-11.net", 646 "listeners.#": "1", 647 "listeners.0": "red", 648 "tags.%": "1", 649 "tags.Name": "reindeer", 650 "nothing.#": "0", 651 }, 652 }, 653 }, 654 "aws_route53_zone.terra.1": &ResourceState{ 655 Type: "aws_route53_zone", 656 Dependencies: []string{}, 657 Primary: &InstanceState{ 658 ID: "EEEFFFGGGHHHIII", 659 Attributes: map[string]string{ 660 "name_servers.#": "4", 661 "name_servers.0": "ns-000.awsdns-38.org", 662 "name_servers.1": "ns-444.awsdns-18.co.uk", 663 "name_servers.2": "ns-999.awsdns-62.com", 664 "name_servers.3": "ns-666.awsdns-11.net", 665 "listeners.#": "1", 666 "listeners.0": "blue", 667 "special.#": "1", 668 "special.0": "extra", 669 "tags.%": "1", 670 "tags.Name": "white-hart", 671 "nothing.#": "0", 672 }, 673 }, 674 }, 675 }, 676 }, 677 }, 678 } 679 680 return &Interpolater{ 681 Module: testModule(t, "interpolate-multi-vars"), 682 StateLock: lock, 683 State: state, 684 } 685 } 686 687 func testInterpolate( 688 t *testing.T, i *Interpolater, 689 scope *InterpolationScope, 690 n string, expectedVar ast.Variable) { 691 v, err := config.NewInterpolatedVariable(n) 692 if err != nil { 693 t.Fatalf("err: %s", err) 694 } 695 696 actual, err := i.Values(scope, map[string]config.InterpolatedVariable{ 697 "foo": v, 698 }) 699 if err != nil { 700 t.Fatalf("err: %s", err) 701 } 702 703 expected := map[string]ast.Variable{ 704 "foo": expectedVar, 705 } 706 if !reflect.DeepEqual(actual, expected) { 707 spew.Config.DisableMethods = true 708 t.Fatalf("%q:\n\n actual: %#v\nexpected: %#v\n\n%s\n\n%s\n\n", n, actual, expected, 709 spew.Sdump(actual), spew.Sdump(expected)) 710 } 711 } 712 713 func testInterpolateErr( 714 t *testing.T, i *Interpolater, 715 scope *InterpolationScope, 716 n string) { 717 v, err := config.NewInterpolatedVariable(n) 718 if err != nil { 719 t.Fatalf("err: %s", err) 720 } 721 722 _, err = i.Values(scope, map[string]config.InterpolatedVariable{ 723 "foo": v, 724 }) 725 if err == nil { 726 t.Fatalf("%q: succeeded, but wanted error", n) 727 } 728 }