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