github.com/mehmetalisavas/terraform@v0.7.10/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_countIndex(t *testing.T) { 17 i := &Interpolater{} 18 19 scope := &InterpolationScope{ 20 Path: rootModulePath, 21 Resource: &Resource{CountIndex: 42}, 22 } 23 24 testInterpolate(t, i, scope, "count.index", ast.Variable{ 25 Value: 42, 26 Type: ast.TypeInt, 27 }) 28 } 29 30 func TestInterpolater_countIndexInWrongContext(t *testing.T) { 31 i := &Interpolater{} 32 33 scope := &InterpolationScope{ 34 Path: rootModulePath, 35 } 36 37 n := "count.index" 38 39 v, err := config.NewInterpolatedVariable(n) 40 if err != nil { 41 t.Fatalf("err: %s", err) 42 } 43 44 expectedErr := fmt.Errorf("foo: count.index is only valid within resources") 45 46 _, err = i.Values(scope, map[string]config.InterpolatedVariable{ 47 "foo": v, 48 }) 49 50 if !reflect.DeepEqual(expectedErr, err) { 51 t.Fatalf("expected: %#v, got %#v", expectedErr, err) 52 } 53 } 54 55 func TestInterpolater_moduleVariable(t *testing.T) { 56 lock := new(sync.RWMutex) 57 state := &State{ 58 Modules: []*ModuleState{ 59 &ModuleState{ 60 Path: rootModulePath, 61 Resources: map[string]*ResourceState{ 62 "aws_instance.web": &ResourceState{ 63 Type: "aws_instance", 64 Primary: &InstanceState{ 65 ID: "bar", 66 }, 67 }, 68 }, 69 }, 70 &ModuleState{ 71 Path: []string{RootModuleName, "child"}, 72 Outputs: map[string]*OutputState{ 73 "foo": &OutputState{ 74 Type: "string", 75 Value: "bar", 76 }, 77 }, 78 }, 79 }, 80 } 81 82 i := &Interpolater{ 83 State: state, 84 StateLock: lock, 85 } 86 87 scope := &InterpolationScope{ 88 Path: rootModulePath, 89 } 90 91 testInterpolate(t, i, scope, "module.child.foo", ast.Variable{ 92 Value: "bar", 93 Type: ast.TypeString, 94 }) 95 } 96 97 func TestInterpolater_pathCwd(t *testing.T) { 98 i := &Interpolater{} 99 scope := &InterpolationScope{} 100 101 expected, err := os.Getwd() 102 if err != nil { 103 t.Fatalf("err: %s", err) 104 } 105 106 testInterpolate(t, i, scope, "path.cwd", ast.Variable{ 107 Value: expected, 108 Type: ast.TypeString, 109 }) 110 } 111 112 func TestInterpolater_pathModule(t *testing.T) { 113 mod := testModule(t, "interpolate-path-module") 114 i := &Interpolater{ 115 Module: mod, 116 } 117 scope := &InterpolationScope{ 118 Path: []string{RootModuleName, "child"}, 119 } 120 121 path := mod.Child([]string{"child"}).Config().Dir 122 testInterpolate(t, i, scope, "path.module", ast.Variable{ 123 Value: path, 124 Type: ast.TypeString, 125 }) 126 } 127 128 func TestInterpolater_pathRoot(t *testing.T) { 129 mod := testModule(t, "interpolate-path-module") 130 i := &Interpolater{ 131 Module: mod, 132 } 133 scope := &InterpolationScope{ 134 Path: []string{RootModuleName, "child"}, 135 } 136 137 path := mod.Config().Dir 138 testInterpolate(t, i, scope, "path.root", ast.Variable{ 139 Value: path, 140 Type: ast.TypeString, 141 }) 142 } 143 144 func TestInterpolater_resourceVariableMap(t *testing.T) { 145 lock := new(sync.RWMutex) 146 state := &State{ 147 Modules: []*ModuleState{ 148 &ModuleState{ 149 Path: rootModulePath, 150 Resources: map[string]*ResourceState{ 151 "aws_instance.web": &ResourceState{ 152 Type: "aws_instance", 153 Primary: &InstanceState{ 154 ID: "bar", 155 Attributes: map[string]string{ 156 "amap.%": "3", 157 "amap.key1": "value1", 158 "amap.key2": "value2", 159 "amap.key3": "value3", 160 }, 161 }, 162 }, 163 }, 164 }, 165 }, 166 } 167 168 i := &Interpolater{ 169 Module: testModule(t, "interpolate-resource-variable"), 170 State: state, 171 StateLock: lock, 172 } 173 174 scope := &InterpolationScope{ 175 Path: rootModulePath, 176 } 177 178 expected := map[string]interface{}{ 179 "key1": "value1", 180 "key2": "value2", 181 "key3": "value3", 182 } 183 184 testInterpolate(t, i, scope, "aws_instance.web.amap", 185 interfaceToVariableSwallowError(expected)) 186 } 187 188 func TestInterpolater_resourceVariableComplexMap(t *testing.T) { 189 lock := new(sync.RWMutex) 190 state := &State{ 191 Modules: []*ModuleState{ 192 &ModuleState{ 193 Path: rootModulePath, 194 Resources: map[string]*ResourceState{ 195 "aws_instance.web": &ResourceState{ 196 Type: "aws_instance", 197 Primary: &InstanceState{ 198 ID: "bar", 199 Attributes: map[string]string{ 200 "amap.%": "2", 201 "amap.key1.#": "2", 202 "amap.key1.0": "hello", 203 "amap.key1.1": "world", 204 "amap.key2.#": "1", 205 "amap.key2.0": "foo", 206 }, 207 }, 208 }, 209 }, 210 }, 211 }, 212 } 213 214 i := &Interpolater{ 215 Module: testModule(t, "interpolate-resource-variable"), 216 State: state, 217 StateLock: lock, 218 } 219 220 scope := &InterpolationScope{ 221 Path: rootModulePath, 222 } 223 224 expected := map[string]interface{}{ 225 "key1": []interface{}{"hello", "world"}, 226 "key2": []interface{}{"foo"}, 227 } 228 229 testInterpolate(t, i, scope, "aws_instance.web.amap", 230 interfaceToVariableSwallowError(expected)) 231 } 232 233 func TestInterpolater_resourceVariable(t *testing.T) { 234 lock := new(sync.RWMutex) 235 state := &State{ 236 Modules: []*ModuleState{ 237 &ModuleState{ 238 Path: rootModulePath, 239 Resources: map[string]*ResourceState{ 240 "aws_instance.web": &ResourceState{ 241 Type: "aws_instance", 242 Primary: &InstanceState{ 243 ID: "bar", 244 Attributes: map[string]string{ 245 "foo": "bar", 246 }, 247 }, 248 }, 249 }, 250 }, 251 }, 252 } 253 254 i := &Interpolater{ 255 Module: testModule(t, "interpolate-resource-variable"), 256 State: state, 257 StateLock: lock, 258 } 259 260 scope := &InterpolationScope{ 261 Path: rootModulePath, 262 } 263 264 testInterpolate(t, i, scope, "aws_instance.web.foo", ast.Variable{ 265 Value: "bar", 266 Type: ast.TypeString, 267 }) 268 } 269 270 func TestInterpolater_resourceVariableMissingDuringInput(t *testing.T) { 271 // During the input walk, computed resource attributes may be entirely 272 // absent since we've not yet produced diffs that tell us what computed 273 // attributes to expect. In that case, interpolator tolerates it and 274 // indicates the value is computed. 275 276 lock := new(sync.RWMutex) 277 state := &State{ 278 Modules: []*ModuleState{ 279 &ModuleState{ 280 Path: rootModulePath, 281 Resources: map[string]*ResourceState{ 282 // No resources at all yet, because we're still dealing 283 // with input and so the resources haven't been created. 284 }, 285 }, 286 }, 287 } 288 289 { 290 i := &Interpolater{ 291 Operation: walkInput, 292 Module: testModule(t, "interpolate-resource-variable"), 293 State: state, 294 StateLock: lock, 295 } 296 297 scope := &InterpolationScope{ 298 Path: rootModulePath, 299 } 300 301 testInterpolate(t, i, scope, "aws_instance.web.foo", ast.Variable{ 302 Value: config.UnknownVariableValue, 303 Type: ast.TypeString, 304 }) 305 } 306 307 // This doesn't apply during other walks, like plan 308 { 309 i := &Interpolater{ 310 Operation: walkPlan, 311 Module: testModule(t, "interpolate-resource-variable"), 312 State: state, 313 StateLock: lock, 314 } 315 316 scope := &InterpolationScope{ 317 Path: rootModulePath, 318 } 319 320 testInterpolateErr(t, i, scope, "aws_instance.web.foo") 321 } 322 } 323 324 func TestInterpolater_resourceVariableMulti(t *testing.T) { 325 lock := new(sync.RWMutex) 326 state := &State{ 327 Modules: []*ModuleState{ 328 &ModuleState{ 329 Path: rootModulePath, 330 Resources: map[string]*ResourceState{ 331 "aws_instance.web": &ResourceState{ 332 Type: "aws_instance", 333 Primary: &InstanceState{ 334 ID: "bar", 335 Attributes: map[string]string{ 336 "foo": config.UnknownVariableValue, 337 }, 338 }, 339 }, 340 }, 341 }, 342 }, 343 } 344 345 i := &Interpolater{ 346 Module: testModule(t, "interpolate-resource-variable"), 347 State: state, 348 StateLock: lock, 349 } 350 351 scope := &InterpolationScope{ 352 Path: rootModulePath, 353 } 354 355 testInterpolate(t, i, scope, "aws_instance.web.*.foo", ast.Variable{ 356 Value: config.UnknownVariableValue, 357 Type: ast.TypeString, 358 }) 359 } 360 361 func TestInterpolater_resourceVariableMulti_interpolated(t *testing.T) { 362 lock := new(sync.RWMutex) 363 state := &State{ 364 Modules: []*ModuleState{ 365 &ModuleState{ 366 Path: rootModulePath, 367 Resources: map[string]*ResourceState{ 368 "aws_instance.web.0": &ResourceState{ 369 Type: "aws_instance", 370 Primary: &InstanceState{ 371 ID: "a", 372 Attributes: map[string]string{"foo": "a"}, 373 }, 374 }, 375 376 "aws_instance.web.1": &ResourceState{ 377 Type: "aws_instance", 378 Primary: &InstanceState{ 379 ID: "b", 380 Attributes: map[string]string{"foo": "b"}, 381 }, 382 }, 383 }, 384 }, 385 }, 386 } 387 388 i := &Interpolater{ 389 Operation: walkApply, 390 Module: testModule(t, "interpolate-multi-interp"), 391 State: state, 392 StateLock: lock, 393 } 394 395 scope := &InterpolationScope{ 396 Path: rootModulePath, 397 } 398 399 expected := []interface{}{"a", "b"} 400 testInterpolate(t, i, scope, "aws_instance.web.*.foo", 401 interfaceToVariableSwallowError(expected)) 402 } 403 404 func interfaceToVariableSwallowError(input interface{}) ast.Variable { 405 variable, _ := hil.InterfaceToVariable(input) 406 return variable 407 } 408 409 func TestInterpolator_resourceMultiAttributes(t *testing.T) { 410 lock := new(sync.RWMutex) 411 state := &State{ 412 Modules: []*ModuleState{ 413 { 414 Path: rootModulePath, 415 Resources: map[string]*ResourceState{ 416 "aws_route53_zone.yada": { 417 Type: "aws_route53_zone", 418 Dependencies: []string{}, 419 Primary: &InstanceState{ 420 ID: "AAABBBCCCDDDEEE", 421 Attributes: map[string]string{ 422 "name_servers.#": "4", 423 "name_servers.0": "ns-1334.awsdns-38.org", 424 "name_servers.1": "ns-1680.awsdns-18.co.uk", 425 "name_servers.2": "ns-498.awsdns-62.com", 426 "name_servers.3": "ns-601.awsdns-11.net", 427 "listeners.#": "1", 428 "listeners.0": "red", 429 "tags.%": "1", 430 "tags.Name": "reindeer", 431 "nothing.#": "0", 432 }, 433 }, 434 }, 435 }, 436 }, 437 }, 438 } 439 440 i := &Interpolater{ 441 Module: testModule(t, "interpolate-multi-vars"), 442 StateLock: lock, 443 State: state, 444 } 445 446 scope := &InterpolationScope{ 447 Path: rootModulePath, 448 } 449 450 name_servers := []interface{}{ 451 "ns-1334.awsdns-38.org", 452 "ns-1680.awsdns-18.co.uk", 453 "ns-498.awsdns-62.com", 454 "ns-601.awsdns-11.net", 455 } 456 457 // More than 1 element 458 testInterpolate(t, i, scope, "aws_route53_zone.yada.name_servers", 459 interfaceToVariableSwallowError(name_servers)) 460 461 // Exactly 1 element 462 testInterpolate(t, i, scope, "aws_route53_zone.yada.listeners", 463 interfaceToVariableSwallowError([]interface{}{"red"})) 464 465 // Zero elements 466 testInterpolate(t, i, scope, "aws_route53_zone.yada.nothing", 467 interfaceToVariableSwallowError([]interface{}{})) 468 469 // Maps still need to work 470 testInterpolate(t, i, scope, "aws_route53_zone.yada.tags.Name", ast.Variable{ 471 Value: "reindeer", 472 Type: ast.TypeString, 473 }) 474 } 475 476 func TestInterpolator_resourceMultiAttributesWithResourceCount(t *testing.T) { 477 i := getInterpolaterFixture(t) 478 scope := &InterpolationScope{ 479 Path: rootModulePath, 480 } 481 482 name_servers := []interface{}{ 483 "ns-1334.awsdns-38.org", 484 "ns-1680.awsdns-18.co.uk", 485 "ns-498.awsdns-62.com", 486 "ns-601.awsdns-11.net", 487 "ns-000.awsdns-38.org", 488 "ns-444.awsdns-18.co.uk", 489 "ns-999.awsdns-62.com", 490 "ns-666.awsdns-11.net", 491 } 492 493 // More than 1 element 494 testInterpolate(t, i, scope, "aws_route53_zone.terra.0.name_servers", 495 interfaceToVariableSwallowError(name_servers[:4])) 496 497 // More than 1 element in both 498 testInterpolate(t, i, scope, "aws_route53_zone.terra.*.name_servers", 499 interfaceToVariableSwallowError([]interface{}{name_servers[:4], name_servers[4:]})) 500 501 // Exactly 1 element 502 testInterpolate(t, i, scope, "aws_route53_zone.terra.0.listeners", 503 interfaceToVariableSwallowError([]interface{}{"red"})) 504 505 // Exactly 1 element in both 506 testInterpolate(t, i, scope, "aws_route53_zone.terra.*.listeners", 507 interfaceToVariableSwallowError([]interface{}{[]interface{}{"red"}, []interface{}{"blue"}})) 508 509 // Zero elements 510 testInterpolate(t, i, scope, "aws_route53_zone.terra.0.nothing", 511 interfaceToVariableSwallowError([]interface{}{})) 512 513 // Zero + 1 element 514 testInterpolate(t, i, scope, "aws_route53_zone.terra.*.special", 515 interfaceToVariableSwallowError([]interface{}{[]interface{}{"extra"}})) 516 517 // Maps still need to work 518 testInterpolate(t, i, scope, "aws_route53_zone.terra.0.tags.Name", ast.Variable{ 519 Value: "reindeer", 520 Type: ast.TypeString, 521 }) 522 523 // Maps still need to work in both 524 testInterpolate(t, i, scope, "aws_route53_zone.terra.*.tags.Name", 525 interfaceToVariableSwallowError([]interface{}{"reindeer", "white-hart"})) 526 } 527 528 func TestInterpolator_resourceMultiAttributesComputed(t *testing.T) { 529 lock := new(sync.RWMutex) 530 // The state would never be written with an UnknownVariableValue in it, but 531 // it can/does exist that way in memory during the plan phase. 532 state := &State{ 533 Modules: []*ModuleState{ 534 &ModuleState{ 535 Path: rootModulePath, 536 Resources: map[string]*ResourceState{ 537 "aws_route53_zone.yada": &ResourceState{ 538 Type: "aws_route53_zone", 539 Primary: &InstanceState{ 540 ID: "z-abc123", 541 Attributes: map[string]string{ 542 "name_servers.#": config.UnknownVariableValue, 543 }, 544 }, 545 }, 546 }, 547 }, 548 }, 549 } 550 i := &Interpolater{ 551 Module: testModule(t, "interpolate-multi-vars"), 552 StateLock: lock, 553 State: state, 554 } 555 556 scope := &InterpolationScope{ 557 Path: rootModulePath, 558 } 559 560 testInterpolate(t, i, scope, "aws_route53_zone.yada.name_servers", ast.Variable{ 561 Value: config.UnknownVariableValue, 562 Type: ast.TypeString, 563 }) 564 } 565 566 func TestInterpolater_selfVarWithoutResource(t *testing.T) { 567 i := &Interpolater{} 568 569 scope := &InterpolationScope{ 570 Path: rootModulePath, 571 } 572 573 v, err := config.NewInterpolatedVariable("self.name") 574 if err != nil { 575 t.Fatalf("err: %s", err) 576 } 577 578 _, err = i.Values(scope, map[string]config.InterpolatedVariable{"foo": v}) 579 if err == nil { 580 t.Fatalf("expected err, got none") 581 } 582 } 583 584 func getInterpolaterFixture(t *testing.T) *Interpolater { 585 lock := new(sync.RWMutex) 586 state := &State{ 587 Modules: []*ModuleState{ 588 &ModuleState{ 589 Path: rootModulePath, 590 Resources: map[string]*ResourceState{ 591 "aws_route53_zone.terra.0": &ResourceState{ 592 Type: "aws_route53_zone", 593 Dependencies: []string{}, 594 Primary: &InstanceState{ 595 ID: "AAABBBCCCDDDEEE", 596 Attributes: map[string]string{ 597 "name_servers.#": "4", 598 "name_servers.0": "ns-1334.awsdns-38.org", 599 "name_servers.1": "ns-1680.awsdns-18.co.uk", 600 "name_servers.2": "ns-498.awsdns-62.com", 601 "name_servers.3": "ns-601.awsdns-11.net", 602 "listeners.#": "1", 603 "listeners.0": "red", 604 "tags.%": "1", 605 "tags.Name": "reindeer", 606 "nothing.#": "0", 607 }, 608 }, 609 }, 610 "aws_route53_zone.terra.1": &ResourceState{ 611 Type: "aws_route53_zone", 612 Dependencies: []string{}, 613 Primary: &InstanceState{ 614 ID: "EEEFFFGGGHHHIII", 615 Attributes: map[string]string{ 616 "name_servers.#": "4", 617 "name_servers.0": "ns-000.awsdns-38.org", 618 "name_servers.1": "ns-444.awsdns-18.co.uk", 619 "name_servers.2": "ns-999.awsdns-62.com", 620 "name_servers.3": "ns-666.awsdns-11.net", 621 "listeners.#": "1", 622 "listeners.0": "blue", 623 "special.#": "1", 624 "special.0": "extra", 625 "tags.%": "1", 626 "tags.Name": "white-hart", 627 "nothing.#": "0", 628 }, 629 }, 630 }, 631 }, 632 }, 633 }, 634 } 635 636 return &Interpolater{ 637 Module: testModule(t, "interpolate-multi-vars"), 638 StateLock: lock, 639 State: state, 640 } 641 } 642 643 func testInterpolate( 644 t *testing.T, i *Interpolater, 645 scope *InterpolationScope, 646 n string, expectedVar ast.Variable) { 647 v, err := config.NewInterpolatedVariable(n) 648 if err != nil { 649 t.Fatalf("err: %s", err) 650 } 651 652 actual, err := i.Values(scope, map[string]config.InterpolatedVariable{ 653 "foo": v, 654 }) 655 if err != nil { 656 t.Fatalf("err: %s", err) 657 } 658 659 expected := map[string]ast.Variable{ 660 "foo": expectedVar, 661 } 662 if !reflect.DeepEqual(actual, expected) { 663 spew.Config.DisableMethods = true 664 t.Fatalf("%q:\n\n actual: %#v\nexpected: %#v\n\n%s\n\n%s\n\n", n, actual, expected, 665 spew.Sdump(actual), spew.Sdump(expected)) 666 } 667 } 668 669 func testInterpolateErr( 670 t *testing.T, i *Interpolater, 671 scope *InterpolationScope, 672 n string) { 673 v, err := config.NewInterpolatedVariable(n) 674 if err != nil { 675 t.Fatalf("err: %s", err) 676 } 677 678 _, err = i.Values(scope, map[string]config.InterpolatedVariable{ 679 "foo": v, 680 }) 681 if err == nil { 682 t.Fatalf("%q: succeeded, but wanted error", n) 683 } 684 }