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