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