github.com/mikesimons/terraform@v0.6.13-0.20160304043642-f11448c69214/terraform/interpolate.go (about) 1 package terraform 2 3 import ( 4 "fmt" 5 "log" 6 "os" 7 "regexp" 8 "sort" 9 "strings" 10 "sync" 11 12 "github.com/hashicorp/hil/ast" 13 "github.com/hashicorp/terraform/config" 14 "github.com/hashicorp/terraform/config/module" 15 ) 16 17 const ( 18 // VarEnvPrefix is the prefix of variables that are read from 19 // the environment to set variables here. 20 VarEnvPrefix = "TF_VAR_" 21 ) 22 23 // Interpolater is the structure responsible for determining the values 24 // for interpolations such as `aws_instance.foo.bar`. 25 type Interpolater struct { 26 Operation walkOperation 27 Module *module.Tree 28 State *State 29 StateLock *sync.RWMutex 30 Variables map[string]string 31 } 32 33 // InterpolationScope is the current scope of execution. This is required 34 // since some variables which are interpolated are dependent on what we're 35 // operating on and where we are. 36 type InterpolationScope struct { 37 Path []string 38 Resource *Resource 39 } 40 41 // Values returns the values for all the variables in the given map. 42 func (i *Interpolater) Values( 43 scope *InterpolationScope, 44 vars map[string]config.InterpolatedVariable) (map[string]ast.Variable, error) { 45 result := make(map[string]ast.Variable, len(vars)) 46 47 // Copy the default variables 48 if i.Module != nil && scope != nil { 49 mod := i.Module 50 if len(scope.Path) > 1 { 51 mod = i.Module.Child(scope.Path[1:]) 52 } 53 for _, v := range mod.Config().Variables { 54 for k, val := range v.DefaultsMap() { 55 result[k] = ast.Variable{ 56 Value: val, 57 Type: ast.TypeString, 58 } 59 } 60 } 61 } 62 63 for n, rawV := range vars { 64 var err error 65 switch v := rawV.(type) { 66 case *config.CountVariable: 67 err = i.valueCountVar(scope, n, v, result) 68 case *config.ModuleVariable: 69 err = i.valueModuleVar(scope, n, v, result) 70 case *config.PathVariable: 71 err = i.valuePathVar(scope, n, v, result) 72 case *config.ResourceVariable: 73 err = i.valueResourceVar(scope, n, v, result) 74 case *config.SelfVariable: 75 err = i.valueSelfVar(scope, n, v, result) 76 case *config.SimpleVariable: 77 err = i.valueSimpleVar(scope, n, v, result) 78 case *config.UserVariable: 79 err = i.valueUserVar(scope, n, v, result) 80 default: 81 err = fmt.Errorf("%s: unknown variable type: %T", n, rawV) 82 } 83 84 if err != nil { 85 return nil, err 86 } 87 } 88 89 return result, nil 90 } 91 92 func (i *Interpolater) valueCountVar( 93 scope *InterpolationScope, 94 n string, 95 v *config.CountVariable, 96 result map[string]ast.Variable) error { 97 switch v.Type { 98 case config.CountValueIndex: 99 if scope.Resource == nil { 100 return fmt.Errorf("%s: count.index is only valid within resources", n) 101 } 102 result[n] = ast.Variable{ 103 Value: scope.Resource.CountIndex, 104 Type: ast.TypeInt, 105 } 106 return nil 107 default: 108 return fmt.Errorf("%s: unknown count type: %#v", n, v.Type) 109 } 110 } 111 112 func (i *Interpolater) valueModuleVar( 113 scope *InterpolationScope, 114 n string, 115 v *config.ModuleVariable, 116 result map[string]ast.Variable) error { 117 // If we're computing all dynamic fields, then module vars count 118 // and we mark it as computed. 119 if i.Operation == walkValidate { 120 result[n] = ast.Variable{ 121 Value: config.UnknownVariableValue, 122 Type: ast.TypeString, 123 } 124 return nil 125 } 126 127 // Build the path to the child module we want 128 path := make([]string, len(scope.Path), len(scope.Path)+1) 129 copy(path, scope.Path) 130 path = append(path, v.Name) 131 132 // Grab the lock so that if other interpolations are running or 133 // state is being modified, we'll be safe. 134 i.StateLock.RLock() 135 defer i.StateLock.RUnlock() 136 137 // Get the module where we're looking for the value 138 var value string 139 mod := i.State.ModuleByPath(path) 140 if mod == nil { 141 // If the module doesn't exist, then we can return an empty string. 142 // This happens usually only in Refresh() when we haven't populated 143 // a state. During validation, we semantically verify that all 144 // modules reference other modules, and graph ordering should 145 // ensure that the module is in the state, so if we reach this 146 // point otherwise it really is a panic. 147 value = config.UnknownVariableValue 148 } else { 149 // Get the value from the outputs 150 var ok bool 151 value, ok = mod.Outputs[v.Field] 152 if !ok { 153 // Same reasons as the comment above. 154 value = config.UnknownVariableValue 155 } 156 } 157 158 result[n] = ast.Variable{ 159 Value: value, 160 Type: ast.TypeString, 161 } 162 return nil 163 } 164 165 func (i *Interpolater) valuePathVar( 166 scope *InterpolationScope, 167 n string, 168 v *config.PathVariable, 169 result map[string]ast.Variable) error { 170 switch v.Type { 171 case config.PathValueCwd: 172 wd, err := os.Getwd() 173 if err != nil { 174 return fmt.Errorf( 175 "Couldn't get cwd for var %s: %s", 176 v.FullKey(), err) 177 } 178 179 result[n] = ast.Variable{ 180 Value: wd, 181 Type: ast.TypeString, 182 } 183 case config.PathValueModule: 184 if t := i.Module.Child(scope.Path[1:]); t != nil { 185 result[n] = ast.Variable{ 186 Value: t.Config().Dir, 187 Type: ast.TypeString, 188 } 189 } 190 case config.PathValueRoot: 191 result[n] = ast.Variable{ 192 Value: i.Module.Config().Dir, 193 Type: ast.TypeString, 194 } 195 default: 196 return fmt.Errorf("%s: unknown path type: %#v", n, v.Type) 197 } 198 199 return nil 200 201 } 202 203 func (i *Interpolater) valueResourceVar( 204 scope *InterpolationScope, 205 n string, 206 v *config.ResourceVariable, 207 result map[string]ast.Variable) error { 208 // If we're computing all dynamic fields, then module vars count 209 // and we mark it as computed. 210 if i.Operation == walkValidate { 211 result[n] = ast.Variable{ 212 Value: config.UnknownVariableValue, 213 Type: ast.TypeString, 214 } 215 return nil 216 } 217 218 var attr string 219 var err error 220 if v.Multi && v.Index == -1 { 221 attr, err = i.computeResourceMultiVariable(scope, v) 222 } else { 223 attr, err = i.computeResourceVariable(scope, v) 224 } 225 if err != nil { 226 return err 227 } 228 229 result[n] = ast.Variable{ 230 Value: attr, 231 Type: ast.TypeString, 232 } 233 return nil 234 } 235 236 func (i *Interpolater) valueSelfVar( 237 scope *InterpolationScope, 238 n string, 239 v *config.SelfVariable, 240 result map[string]ast.Variable) error { 241 if scope == nil || scope.Resource == nil { 242 return fmt.Errorf( 243 "%s: invalid scope, self variables are only valid on resources", n) 244 } 245 rv, err := config.NewResourceVariable(fmt.Sprintf( 246 "%s.%s.%d.%s", 247 scope.Resource.Type, 248 scope.Resource.Name, 249 scope.Resource.CountIndex, 250 v.Field)) 251 if err != nil { 252 return err 253 } 254 255 return i.valueResourceVar(scope, n, rv, result) 256 } 257 258 func (i *Interpolater) valueSimpleVar( 259 scope *InterpolationScope, 260 n string, 261 v *config.SimpleVariable, 262 result map[string]ast.Variable) error { 263 // SimpleVars are never handled by Terraform's interpolator 264 result[n] = ast.Variable{ 265 Value: config.UnknownVariableValue, 266 Type: ast.TypeString, 267 } 268 return nil 269 } 270 271 func (i *Interpolater) valueUserVar( 272 scope *InterpolationScope, 273 n string, 274 v *config.UserVariable, 275 result map[string]ast.Variable) error { 276 val, ok := i.Variables[v.Name] 277 if ok { 278 result[n] = ast.Variable{ 279 Value: val, 280 Type: ast.TypeString, 281 } 282 return nil 283 } 284 285 if _, ok := result[n]; !ok && i.Operation == walkValidate { 286 result[n] = ast.Variable{ 287 Value: config.UnknownVariableValue, 288 Type: ast.TypeString, 289 } 290 return nil 291 } 292 293 // Look up if we have any variables with this prefix because 294 // those are map overrides. Include those. 295 for k, val := range i.Variables { 296 if strings.HasPrefix(k, v.Name+".") { 297 result["var."+k] = ast.Variable{ 298 Value: val, 299 Type: ast.TypeString, 300 } 301 } 302 } 303 304 return nil 305 } 306 307 func (i *Interpolater) computeResourceVariable( 308 scope *InterpolationScope, 309 v *config.ResourceVariable) (string, error) { 310 id := v.ResourceId() 311 if v.Multi { 312 id = fmt.Sprintf("%s.%d", id, v.Index) 313 } 314 315 i.StateLock.RLock() 316 defer i.StateLock.RUnlock() 317 318 // Get the information about this resource variable, and verify 319 // that it exists and such. 320 module, _, err := i.resourceVariableInfo(scope, v) 321 if err != nil { 322 return "", err 323 } 324 325 // If we have no module in the state yet or count, return empty 326 if module == nil || len(module.Resources) == 0 { 327 return "", nil 328 } 329 330 // Get the resource out from the state. We know the state exists 331 // at this point and if there is a state, we expect there to be a 332 // resource with the given name. 333 r, ok := module.Resources[id] 334 if !ok && v.Multi && v.Index == 0 { 335 r, ok = module.Resources[v.ResourceId()] 336 } 337 if !ok { 338 r = nil 339 } 340 if r == nil { 341 goto MISSING 342 } 343 344 if r.Primary == nil { 345 goto MISSING 346 } 347 348 if attr, ok := r.Primary.Attributes[v.Field]; ok { 349 return attr, nil 350 } 351 352 // computed list attribute 353 if _, ok := r.Primary.Attributes[v.Field+".#"]; ok { 354 return i.interpolateListAttribute(v.Field, r.Primary.Attributes) 355 } 356 357 // At apply time, we can't do the "maybe has it" check below 358 // that we need for plans since parent elements might be computed. 359 // Therefore, it is an error and we're missing the key. 360 // 361 // TODO: test by creating a state and configuration that is referencing 362 // a non-existent variable "foo.bar" where the state only has "foo" 363 // and verify plan works, but apply doesn't. 364 if i.Operation == walkApply || i.Operation == walkDestroy { 365 goto MISSING 366 } 367 368 // We didn't find the exact field, so lets separate the dots 369 // and see if anything along the way is a computed set. i.e. if 370 // we have "foo.0.bar" as the field, check to see if "foo" is 371 // a computed list. If so, then the whole thing is computed. 372 if parts := strings.Split(v.Field, "."); len(parts) > 1 { 373 for i := 1; i < len(parts); i++ { 374 // Lists and sets make this 375 key := fmt.Sprintf("%s.#", strings.Join(parts[:i], ".")) 376 if attr, ok := r.Primary.Attributes[key]; ok { 377 return attr, nil 378 } 379 380 // Maps make this 381 key = fmt.Sprintf("%s", strings.Join(parts[:i], ".")) 382 if attr, ok := r.Primary.Attributes[key]; ok { 383 return attr, nil 384 } 385 } 386 } 387 388 MISSING: 389 // Validation for missing interpolations should happen at a higher 390 // semantic level. If we reached this point and don't have variables, 391 // just return the computed value. 392 if scope == nil && scope.Resource == nil { 393 return config.UnknownVariableValue, nil 394 } 395 396 // If the operation is refresh, it isn't an error for a value to 397 // be unknown. Instead, we return that the value is computed so 398 // that the graph can continue to refresh other nodes. It doesn't 399 // matter because the config isn't interpolated anyways. 400 // 401 // For a Destroy, we're also fine with computed values, since our goal is 402 // only to get destroy nodes for existing resources. 403 // 404 // For an input walk, computed values are okay to return because we're only 405 // looking for missing variables to prompt the user for. 406 if i.Operation == walkRefresh || i.Operation == walkPlanDestroy || i.Operation == walkDestroy || i.Operation == walkInput { 407 return config.UnknownVariableValue, nil 408 } 409 410 return "", fmt.Errorf( 411 "Resource '%s' does not have attribute '%s' "+ 412 "for variable '%s'", 413 id, 414 v.Field, 415 v.FullKey()) 416 } 417 418 func (i *Interpolater) computeResourceMultiVariable( 419 scope *InterpolationScope, 420 v *config.ResourceVariable) (string, error) { 421 i.StateLock.RLock() 422 defer i.StateLock.RUnlock() 423 424 // Get the information about this resource variable, and verify 425 // that it exists and such. 426 module, cr, err := i.resourceVariableInfo(scope, v) 427 if err != nil { 428 return "", err 429 } 430 431 // Get the count so we know how many to iterate over 432 count, err := cr.Count() 433 if err != nil { 434 return "", fmt.Errorf( 435 "Error reading %s count: %s", 436 v.ResourceId(), 437 err) 438 } 439 440 // If we have no module in the state yet or count, return empty 441 if module == nil || len(module.Resources) == 0 || count == 0 { 442 return "", nil 443 } 444 445 var values []string 446 for j := 0; j < count; j++ { 447 id := fmt.Sprintf("%s.%d", v.ResourceId(), j) 448 449 // If we're dealing with only a single resource, then the 450 // ID doesn't have a trailing index. 451 if count == 1 { 452 id = v.ResourceId() 453 } 454 455 r, ok := module.Resources[id] 456 if !ok { 457 continue 458 } 459 460 if r.Primary == nil { 461 continue 462 } 463 464 attr, ok := r.Primary.Attributes[v.Field] 465 if !ok { 466 // computed list attribute 467 _, ok := r.Primary.Attributes[v.Field+".#"] 468 if !ok { 469 continue 470 } 471 attr, err = i.interpolateListAttribute(v.Field, r.Primary.Attributes) 472 if err != nil { 473 return "", err 474 } 475 } 476 477 if config.IsStringList(attr) { 478 for _, s := range config.StringList(attr).Slice() { 479 values = append(values, s) 480 } 481 continue 482 } 483 484 // If any value is unknown, the whole thing is unknown 485 if attr == config.UnknownVariableValue { 486 return config.UnknownVariableValue, nil 487 } 488 489 values = append(values, attr) 490 } 491 492 if len(values) == 0 { 493 // If the operation is refresh, it isn't an error for a value to 494 // be unknown. Instead, we return that the value is computed so 495 // that the graph can continue to refresh other nodes. It doesn't 496 // matter because the config isn't interpolated anyways. 497 // 498 // For a Destroy, we're also fine with computed values, since our goal is 499 // only to get destroy nodes for existing resources. 500 // 501 // For an input walk, computed values are okay to return because we're only 502 // looking for missing variables to prompt the user for. 503 if i.Operation == walkRefresh || i.Operation == walkPlanDestroy || i.Operation == walkDestroy || i.Operation == walkInput { 504 return config.UnknownVariableValue, nil 505 } 506 507 return "", fmt.Errorf( 508 "Resource '%s' does not have attribute '%s' "+ 509 "for variable '%s'", 510 v.ResourceId(), 511 v.Field, 512 v.FullKey()) 513 } 514 515 return config.NewStringList(values).String(), nil 516 } 517 518 func (i *Interpolater) interpolateListAttribute( 519 resourceID string, 520 attributes map[string]string) (string, error) { 521 522 attr := attributes[resourceID+".#"] 523 log.Printf("[DEBUG] Interpolating computed list attribute %s (%s)", 524 resourceID, attr) 525 526 // In Terraform's internal dotted representation of list-like attributes, the 527 // ".#" count field is marked as unknown to indicate "this whole list is 528 // unknown". We must honor that meaning here so computed references can be 529 // treated properly during the plan phase. 530 if attr == config.UnknownVariableValue { 531 return attr, nil 532 } 533 534 // Otherwise we gather the values from the list-like attribute and return 535 // them. 536 var members []string 537 numberedListMember := regexp.MustCompile("^" + resourceID + "\\.[0-9]+$") 538 for id, value := range attributes { 539 if numberedListMember.MatchString(id) { 540 members = append(members, value) 541 } 542 } 543 544 sort.Strings(members) 545 return config.NewStringList(members).String(), nil 546 } 547 548 func (i *Interpolater) resourceVariableInfo( 549 scope *InterpolationScope, 550 v *config.ResourceVariable) (*ModuleState, *config.Resource, error) { 551 // Get the module tree that contains our current path. This is 552 // either the current module (path is empty) or a child. 553 modTree := i.Module 554 if len(scope.Path) > 1 { 555 modTree = i.Module.Child(scope.Path[1:]) 556 } 557 558 // Get the resource from the configuration so we can verify 559 // that the resource is in the configuration and so we can access 560 // the configuration if we need to. 561 var cr *config.Resource 562 for _, r := range modTree.Config().Resources { 563 if r.Id() == v.ResourceId() { 564 cr = r 565 break 566 } 567 } 568 if cr == nil { 569 return nil, nil, fmt.Errorf( 570 "Resource '%s' not found for variable '%s'", 571 v.ResourceId(), 572 v.FullKey()) 573 } 574 575 // Get the relevant module 576 module := i.State.ModuleByPath(scope.Path) 577 return module, cr, nil 578 }