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