github.com/wikibal01/hashicorp-terraform@v0.11.12-beta1/terraform/interpolate.go (about) 1 package terraform 2 3 import ( 4 "fmt" 5 "log" 6 "os" 7 "strconv" 8 "strings" 9 "sync" 10 11 "github.com/hashicorp/hil" 12 "github.com/hashicorp/hil/ast" 13 "github.com/hashicorp/terraform/config" 14 "github.com/hashicorp/terraform/config/module" 15 "github.com/hashicorp/terraform/flatmap" 16 ) 17 18 const ( 19 // VarEnvPrefix is the prefix of variables that are read from 20 // the environment to set variables here. 21 VarEnvPrefix = "TF_VAR_" 22 ) 23 24 // Interpolater is the structure responsible for determining the values 25 // for interpolations such as `aws_instance.foo.bar`. 26 type Interpolater struct { 27 Operation walkOperation 28 Meta *ContextMeta 29 Module *module.Tree 30 State *State 31 StateLock *sync.RWMutex 32 VariableValues map[string]interface{} 33 VariableValuesLock *sync.Mutex 34 } 35 36 // InterpolationScope is the current scope of execution. This is required 37 // since some variables which are interpolated are dependent on what we're 38 // operating on and where we are. 39 type InterpolationScope struct { 40 Path []string 41 Resource *Resource 42 } 43 44 // Values returns the values for all the variables in the given map. 45 func (i *Interpolater) Values( 46 scope *InterpolationScope, 47 vars map[string]config.InterpolatedVariable) (map[string]ast.Variable, error) { 48 if scope == nil { 49 scope = &InterpolationScope{} 50 } 51 52 result := make(map[string]ast.Variable, len(vars)) 53 54 // Copy the default variables 55 if i.Module != nil && scope != nil { 56 mod := i.Module 57 if len(scope.Path) > 1 { 58 mod = i.Module.Child(scope.Path[1:]) 59 } 60 for _, v := range mod.Config().Variables { 61 // Set default variables 62 if v.Default == nil { 63 continue 64 } 65 66 n := fmt.Sprintf("var.%s", v.Name) 67 variable, err := hil.InterfaceToVariable(v.Default) 68 if err != nil { 69 return nil, fmt.Errorf("invalid default map value for %s: %v", v.Name, v.Default) 70 } 71 72 result[n] = variable 73 } 74 } 75 76 for n, rawV := range vars { 77 var err error 78 switch v := rawV.(type) { 79 case *config.CountVariable: 80 err = i.valueCountVar(scope, n, v, result) 81 case *config.ModuleVariable: 82 err = i.valueModuleVar(scope, n, v, result) 83 case *config.PathVariable: 84 err = i.valuePathVar(scope, n, v, result) 85 case *config.ResourceVariable: 86 err = i.valueResourceVar(scope, n, v, result) 87 case *config.SelfVariable: 88 err = i.valueSelfVar(scope, n, v, result) 89 case *config.SimpleVariable: 90 err = i.valueSimpleVar(scope, n, v, result) 91 case *config.TerraformVariable: 92 err = i.valueTerraformVar(scope, n, v, result) 93 case *config.LocalVariable: 94 err = i.valueLocalVar(scope, n, v, result) 95 case *config.UserVariable: 96 err = i.valueUserVar(scope, n, v, result) 97 default: 98 err = fmt.Errorf("%s: unknown variable type: %T", n, rawV) 99 } 100 101 if err != nil { 102 return nil, err 103 } 104 } 105 106 return result, nil 107 } 108 109 func (i *Interpolater) valueCountVar( 110 scope *InterpolationScope, 111 n string, 112 v *config.CountVariable, 113 result map[string]ast.Variable) error { 114 switch v.Type { 115 case config.CountValueIndex: 116 if scope.Resource == nil { 117 return fmt.Errorf("%s: count.index is only valid within resources", n) 118 } 119 result[n] = ast.Variable{ 120 Value: scope.Resource.CountIndex, 121 Type: ast.TypeInt, 122 } 123 return nil 124 default: 125 return fmt.Errorf("%s: unknown count type: %#v", n, v.Type) 126 } 127 } 128 129 func unknownVariable() ast.Variable { 130 return ast.Variable{ 131 Type: ast.TypeUnknown, 132 Value: config.UnknownVariableValue, 133 } 134 } 135 136 func unknownValue() string { 137 return hil.UnknownValue 138 } 139 140 func (i *Interpolater) valueModuleVar( 141 scope *InterpolationScope, 142 n string, 143 v *config.ModuleVariable, 144 result map[string]ast.Variable) error { 145 // Build the path to the child module we want 146 path := make([]string, len(scope.Path), len(scope.Path)+1) 147 copy(path, scope.Path) 148 path = append(path, v.Name) 149 150 // Grab the lock so that if other interpolations are running or 151 // state is being modified, we'll be safe. 152 i.StateLock.RLock() 153 defer i.StateLock.RUnlock() 154 155 // Get the module where we're looking for the value 156 mod := i.State.ModuleByPath(path) 157 if mod == nil { 158 // If the module doesn't exist, then we can return an empty string. 159 // This happens usually only in Refresh() when we haven't populated 160 // a state. During validation, we semantically verify that all 161 // modules reference other modules, and graph ordering should 162 // ensure that the module is in the state, so if we reach this 163 // point otherwise it really is a panic. 164 result[n] = unknownVariable() 165 166 // During apply this is always an error 167 if i.Operation == walkApply { 168 return fmt.Errorf( 169 "Couldn't find module %q for var: %s", 170 v.Name, v.FullKey()) 171 } 172 } else { 173 // Get the value from the outputs 174 if outputState, ok := mod.Outputs[v.Field]; ok { 175 output, err := hil.InterfaceToVariable(outputState.Value) 176 if err != nil { 177 return err 178 } 179 result[n] = output 180 } else { 181 // Same reasons as the comment above. 182 result[n] = unknownVariable() 183 184 // During apply this is always an error 185 if i.Operation == walkApply { 186 return fmt.Errorf( 187 "Couldn't find output %q for module var: %s", 188 v.Field, v.FullKey()) 189 } 190 } 191 } 192 193 return nil 194 } 195 196 func (i *Interpolater) valuePathVar( 197 scope *InterpolationScope, 198 n string, 199 v *config.PathVariable, 200 result map[string]ast.Variable) error { 201 switch v.Type { 202 case config.PathValueCwd: 203 wd, err := os.Getwd() 204 if err != nil { 205 return fmt.Errorf( 206 "Couldn't get cwd for var %s: %s", 207 v.FullKey(), err) 208 } 209 210 result[n] = ast.Variable{ 211 Value: wd, 212 Type: ast.TypeString, 213 } 214 case config.PathValueModule: 215 if t := i.Module.Child(scope.Path[1:]); t != nil { 216 result[n] = ast.Variable{ 217 Value: t.Config().Dir, 218 Type: ast.TypeString, 219 } 220 } 221 case config.PathValueRoot: 222 result[n] = ast.Variable{ 223 Value: i.Module.Config().Dir, 224 Type: ast.TypeString, 225 } 226 default: 227 return fmt.Errorf("%s: unknown path type: %#v", n, v.Type) 228 } 229 230 return nil 231 232 } 233 234 func (i *Interpolater) valueResourceVar( 235 scope *InterpolationScope, 236 n string, 237 v *config.ResourceVariable, 238 result map[string]ast.Variable) error { 239 // If we're computing all dynamic fields, then module vars count 240 // and we mark it as computed. 241 if i.Operation == walkValidate { 242 result[n] = unknownVariable() 243 return nil 244 } 245 246 var variable *ast.Variable 247 var err error 248 249 if v.Multi && v.Index == -1 { 250 variable, err = i.computeResourceMultiVariable(scope, v) 251 } else { 252 variable, err = i.computeResourceVariable(scope, v) 253 } 254 255 if err != nil { 256 return err 257 } 258 259 if variable == nil { 260 // During the input walk we tolerate missing variables because 261 // we haven't yet had a chance to refresh state, so dynamic data may 262 // not yet be complete. 263 // If it truly is missing, we'll catch it on a later walk. 264 // This applies only to graph nodes that interpolate during the 265 // config walk, e.g. providers. 266 if i.Operation == walkInput || i.Operation == walkRefresh { 267 result[n] = unknownVariable() 268 return nil 269 } 270 271 return fmt.Errorf("variable %q is nil, but no error was reported", v.Name) 272 } 273 274 result[n] = *variable 275 return nil 276 } 277 278 func (i *Interpolater) valueSelfVar( 279 scope *InterpolationScope, 280 n string, 281 v *config.SelfVariable, 282 result map[string]ast.Variable) error { 283 if scope == nil || scope.Resource == nil { 284 return fmt.Errorf( 285 "%s: invalid scope, self variables are only valid on resources", n) 286 } 287 288 rv, err := config.NewResourceVariable(fmt.Sprintf( 289 "%s.%s.%d.%s", 290 scope.Resource.Type, 291 scope.Resource.Name, 292 scope.Resource.CountIndex, 293 v.Field)) 294 if err != nil { 295 return err 296 } 297 298 return i.valueResourceVar(scope, n, rv, result) 299 } 300 301 func (i *Interpolater) valueSimpleVar( 302 scope *InterpolationScope, 303 n string, 304 v *config.SimpleVariable, 305 result map[string]ast.Variable) error { 306 // This error message includes some information for people who 307 // relied on this for their template_file data sources. We should 308 // remove this at some point but there isn't any rush. 309 return fmt.Errorf( 310 "invalid variable syntax: %q. Did you mean 'var.%s'? If this is part of inline `template` parameter\n"+ 311 "then you must escape the interpolation with two dollar signs. For\n"+ 312 "example: ${a} becomes $${a}.", 313 n, n) 314 } 315 316 func (i *Interpolater) valueTerraformVar( 317 scope *InterpolationScope, 318 n string, 319 v *config.TerraformVariable, 320 result map[string]ast.Variable) error { 321 // "env" is supported for backward compatibility, but it's deprecated and 322 // so we won't advertise it as being allowed in the error message. It will 323 // be removed in a future version of Terraform. 324 if v.Field != "workspace" && v.Field != "env" { 325 return fmt.Errorf( 326 "%s: only supported key for 'terraform.X' interpolations is 'workspace'", n) 327 } 328 329 if i.Meta == nil { 330 return fmt.Errorf( 331 "%s: internal error: nil Meta. Please report a bug.", n) 332 } 333 334 result[n] = ast.Variable{Type: ast.TypeString, Value: i.Meta.Env} 335 return nil 336 } 337 338 func (i *Interpolater) valueLocalVar( 339 scope *InterpolationScope, 340 n string, 341 v *config.LocalVariable, 342 result map[string]ast.Variable, 343 ) error { 344 i.StateLock.RLock() 345 defer i.StateLock.RUnlock() 346 347 modTree := i.Module 348 if len(scope.Path) > 1 { 349 modTree = i.Module.Child(scope.Path[1:]) 350 } 351 352 // Get the resource from the configuration so we can verify 353 // that the resource is in the configuration and so we can access 354 // the configuration if we need to. 355 var cl *config.Local 356 for _, l := range modTree.Config().Locals { 357 if l.Name == v.Name { 358 cl = l 359 break 360 } 361 } 362 363 if cl == nil { 364 return fmt.Errorf("%s: no local value of this name has been declared", n) 365 } 366 367 // Get the relevant module 368 module := i.State.ModuleByPath(scope.Path) 369 if module == nil { 370 result[n] = unknownVariable() 371 return nil 372 } 373 374 rawV, exists := module.Locals[v.Name] 375 if !exists { 376 result[n] = unknownVariable() 377 return nil 378 } 379 380 varV, err := hil.InterfaceToVariable(rawV) 381 if err != nil { 382 // Should never happen, since interpolation should always produce 383 // something we can feed back in to interpolation. 384 return fmt.Errorf("%s: %s", n, err) 385 } 386 387 result[n] = varV 388 return nil 389 } 390 391 func (i *Interpolater) valueUserVar( 392 scope *InterpolationScope, 393 n string, 394 v *config.UserVariable, 395 result map[string]ast.Variable) error { 396 i.VariableValuesLock.Lock() 397 defer i.VariableValuesLock.Unlock() 398 val, ok := i.VariableValues[v.Name] 399 if ok { 400 varValue, err := hil.InterfaceToVariable(val) 401 if err != nil { 402 return fmt.Errorf("cannot convert %s value %q to an ast.Variable for interpolation: %s", 403 v.Name, val, err) 404 } 405 result[n] = varValue 406 return nil 407 } 408 409 if _, ok := result[n]; !ok && i.Operation == walkValidate { 410 result[n] = unknownVariable() 411 return nil 412 } 413 414 // Look up if we have any variables with this prefix because 415 // those are map overrides. Include those. 416 for k, val := range i.VariableValues { 417 if strings.HasPrefix(k, v.Name+".") { 418 keyComponents := strings.Split(k, ".") 419 overrideKey := keyComponents[len(keyComponents)-1] 420 421 mapInterface, ok := result["var."+v.Name] 422 if !ok { 423 return fmt.Errorf("override for non-existent variable: %s", v.Name) 424 } 425 426 mapVariable := mapInterface.Value.(map[string]ast.Variable) 427 428 varValue, err := hil.InterfaceToVariable(val) 429 if err != nil { 430 return fmt.Errorf("cannot convert %s value %q to an ast.Variable for interpolation: %s", 431 v.Name, val, err) 432 } 433 mapVariable[overrideKey] = varValue 434 } 435 } 436 437 return nil 438 } 439 440 func (i *Interpolater) computeResourceVariable( 441 scope *InterpolationScope, 442 v *config.ResourceVariable) (*ast.Variable, error) { 443 id := v.ResourceId() 444 if v.Multi { 445 id = fmt.Sprintf("%s.%d", id, v.Index) 446 } 447 448 i.StateLock.RLock() 449 defer i.StateLock.RUnlock() 450 451 unknownVariable := unknownVariable() 452 453 // These variables must be declared early because of the use of GOTO 454 var isList bool 455 var isMap bool 456 457 // Get the information about this resource variable, and verify 458 // that it exists and such. 459 module, cr, err := i.resourceVariableInfo(scope, v) 460 if err != nil { 461 return nil, err 462 } 463 464 // If we're requesting "count" its a special variable that we grab 465 // directly from the config itself. 466 if v.Field == "count" { 467 var count int 468 if cr != nil { 469 count, err = cr.Count() 470 } else { 471 count, err = i.resourceCountMax(module, cr, v) 472 } 473 if err != nil { 474 return nil, fmt.Errorf( 475 "Error reading %s count: %s", 476 v.ResourceId(), 477 err) 478 } 479 480 return &ast.Variable{Type: ast.TypeInt, Value: count}, nil 481 } 482 483 // Get the resource out from the state. We know the state exists 484 // at this point and if there is a state, we expect there to be a 485 // resource with the given name. 486 var r *ResourceState 487 if module != nil && len(module.Resources) > 0 { 488 var ok bool 489 r, ok = module.Resources[id] 490 if !ok && v.Multi && v.Index == 0 { 491 r, ok = module.Resources[v.ResourceId()] 492 } 493 if !ok { 494 r = nil 495 } 496 } 497 if r == nil || r.Primary == nil { 498 if i.Operation == walkApply || i.Operation == walkPlan { 499 return nil, fmt.Errorf( 500 "Resource '%s' not found for variable '%s'", 501 v.ResourceId(), 502 v.FullKey()) 503 } 504 505 // If we have no module in the state yet or count, return empty. 506 // NOTE(@mitchellh): I actually don't know why this is here. During 507 // a refactor I kept this here to maintain the same behavior, but 508 // I'm not sure why its here. 509 if module == nil || len(module.Resources) == 0 { 510 return nil, nil 511 } 512 513 goto MISSING 514 } 515 516 if attr, ok := r.Primary.Attributes[v.Field]; ok { 517 v, err := hil.InterfaceToVariable(attr) 518 return &v, err 519 } 520 521 // special case for the "id" field which is usually also an attribute 522 if v.Field == "id" && r.Primary.ID != "" { 523 // This is usually pulled from the attributes, but is sometimes missing 524 // during destroy. We can return the ID field in this case. 525 // FIXME: there should only be one ID to rule them all. 526 log.Printf("[WARN] resource %s missing 'id' attribute", v.ResourceId()) 527 v, err := hil.InterfaceToVariable(r.Primary.ID) 528 return &v, err 529 } 530 531 // computed list or map attribute 532 _, isList = r.Primary.Attributes[v.Field+".#"] 533 _, isMap = r.Primary.Attributes[v.Field+".%"] 534 if isList || isMap { 535 variable, err := i.interpolateComplexTypeAttribute(v.Field, r.Primary.Attributes) 536 return &variable, err 537 } 538 539 // At apply time, we can't do the "maybe has it" check below 540 // that we need for plans since parent elements might be computed. 541 // Therefore, it is an error and we're missing the key. 542 // 543 // TODO: test by creating a state and configuration that is referencing 544 // a non-existent variable "foo.bar" where the state only has "foo" 545 // and verify plan works, but apply doesn't. 546 if i.Operation == walkApply || i.Operation == walkDestroy { 547 goto MISSING 548 } 549 550 // We didn't find the exact field, so lets separate the dots 551 // and see if anything along the way is a computed set. i.e. if 552 // we have "foo.0.bar" as the field, check to see if "foo" is 553 // a computed list. If so, then the whole thing is computed. 554 if parts := strings.Split(v.Field, "."); len(parts) > 1 { 555 for i := 1; i < len(parts); i++ { 556 // Lists and sets make this 557 key := fmt.Sprintf("%s.#", strings.Join(parts[:i], ".")) 558 if attr, ok := r.Primary.Attributes[key]; ok { 559 v, err := hil.InterfaceToVariable(attr) 560 return &v, err 561 } 562 563 // Maps make this 564 key = fmt.Sprintf("%s", strings.Join(parts[:i], ".")) 565 if attr, ok := r.Primary.Attributes[key]; ok { 566 v, err := hil.InterfaceToVariable(attr) 567 return &v, err 568 } 569 } 570 } 571 572 MISSING: 573 // Validation for missing interpolations should happen at a higher 574 // semantic level. If we reached this point and don't have variables, 575 // just return the computed value. 576 if scope == nil && scope.Resource == nil { 577 return &unknownVariable, nil 578 } 579 580 // If the operation is refresh, it isn't an error for a value to 581 // be unknown. Instead, we return that the value is computed so 582 // that the graph can continue to refresh other nodes. It doesn't 583 // matter because the config isn't interpolated anyways. 584 // 585 // For a Destroy, we're also fine with computed values, since our goal is 586 // only to get destroy nodes for existing resources. 587 // 588 // For an input walk, computed values are okay to return because we're only 589 // looking for missing variables to prompt the user for. 590 if i.Operation == walkRefresh || i.Operation == walkPlanDestroy || i.Operation == walkInput { 591 return &unknownVariable, nil 592 } 593 594 return nil, fmt.Errorf( 595 "Resource '%s' does not have attribute '%s' "+ 596 "for variable '%s'", 597 id, 598 v.Field, 599 v.FullKey()) 600 } 601 602 func (i *Interpolater) computeResourceMultiVariable( 603 scope *InterpolationScope, 604 v *config.ResourceVariable) (*ast.Variable, error) { 605 i.StateLock.RLock() 606 defer i.StateLock.RUnlock() 607 608 unknownVariable := unknownVariable() 609 610 // If we're only looking for input, we don't need to expand a 611 // multi-variable. This prevents us from encountering things that should be 612 // known but aren't because the state has yet to be refreshed. 613 if i.Operation == walkInput { 614 return &unknownVariable, nil 615 } 616 617 // Get the information about this resource variable, and verify 618 // that it exists and such. 619 module, cr, err := i.resourceVariableInfo(scope, v) 620 if err != nil { 621 return nil, err 622 } 623 624 // Get the keys for all the resources that are created for this resource 625 countMax, err := i.resourceCountMax(module, cr, v) 626 if err != nil { 627 return nil, err 628 } 629 630 // If count is zero, we return an empty list 631 if countMax == 0 { 632 return &ast.Variable{Type: ast.TypeList, Value: []ast.Variable{}}, nil 633 } 634 635 // If we have no module in the state yet or count, return unknown 636 if module == nil || len(module.Resources) == 0 { 637 return &unknownVariable, nil 638 } 639 640 var values []interface{} 641 for idx := 0; idx < countMax; idx++ { 642 id := fmt.Sprintf("%s.%d", v.ResourceId(), idx) 643 644 // ID doesn't have a trailing index. We try both here, but if a value 645 // without a trailing index is found we prefer that. This choice 646 // is for legacy reasons: older versions of TF preferred it. 647 if id == v.ResourceId()+".0" { 648 potential := v.ResourceId() 649 if _, ok := module.Resources[potential]; ok { 650 id = potential 651 } 652 } 653 654 r, ok := module.Resources[id] 655 if !ok { 656 continue 657 } 658 659 if r.Primary == nil { 660 continue 661 } 662 663 if singleAttr, ok := r.Primary.Attributes[v.Field]; ok { 664 values = append(values, singleAttr) 665 continue 666 } 667 668 if v.Field == "id" && r.Primary.ID != "" { 669 log.Printf("[WARN] resource %s missing 'id' attribute", v.ResourceId()) 670 values = append(values, r.Primary.ID) 671 } 672 673 // computed list or map attribute 674 _, isList := r.Primary.Attributes[v.Field+".#"] 675 _, isMap := r.Primary.Attributes[v.Field+".%"] 676 if !(isList || isMap) { 677 continue 678 } 679 multiAttr, err := i.interpolateComplexTypeAttribute(v.Field, r.Primary.Attributes) 680 if err != nil { 681 return nil, err 682 } 683 684 values = append(values, multiAttr) 685 } 686 687 if len(values) == 0 { 688 // If the operation is refresh, it isn't an error for a value to 689 // be unknown. Instead, we return that the value is computed so 690 // that the graph can continue to refresh other nodes. It doesn't 691 // matter because the config isn't interpolated anyways. 692 // 693 // For a Destroy, we're also fine with computed values, since our goal is 694 // only to get destroy nodes for existing resources. 695 // 696 // For an input walk, computed values are okay to return because we're only 697 // looking for missing variables to prompt the user for. 698 if i.Operation == walkRefresh || i.Operation == walkPlanDestroy || i.Operation == walkDestroy || i.Operation == walkInput { 699 return &unknownVariable, nil 700 } 701 702 return nil, fmt.Errorf( 703 "Resource '%s' does not have attribute '%s' "+ 704 "for variable '%s'", 705 v.ResourceId(), 706 v.Field, 707 v.FullKey()) 708 } 709 710 variable, err := hil.InterfaceToVariable(values) 711 return &variable, err 712 } 713 714 func (i *Interpolater) interpolateComplexTypeAttribute( 715 resourceID string, 716 attributes map[string]string) (ast.Variable, error) { 717 // We can now distinguish between lists and maps in state by the count field: 718 // - lists (and by extension, sets) use the traditional .# notation 719 // - maps use the newer .% notation 720 // Consequently here we can decide how to deal with the keys appropriately 721 // based on whether the type is a map of list. 722 if lengthAttr, isList := attributes[resourceID+".#"]; isList { 723 log.Printf("[DEBUG] Interpolating computed list element attribute %s (%s)", 724 resourceID, lengthAttr) 725 726 // In Terraform's internal dotted representation of list-like attributes, the 727 // ".#" count field is marked as unknown to indicate "this whole list is 728 // unknown". We must honor that meaning here so computed references can be 729 // treated properly during the plan phase. 730 if lengthAttr == config.UnknownVariableValue { 731 return unknownVariable(), nil 732 } 733 734 expanded := flatmap.Expand(attributes, resourceID) 735 return hil.InterfaceToVariable(expanded) 736 } 737 738 if lengthAttr, isMap := attributes[resourceID+".%"]; isMap { 739 log.Printf("[DEBUG] Interpolating computed map element attribute %s (%s)", 740 resourceID, lengthAttr) 741 742 // In Terraform's internal dotted representation of map attributes, the 743 // ".%" count field is marked as unknown to indicate "this whole list is 744 // unknown". We must honor that meaning here so computed references can be 745 // treated properly during the plan phase. 746 if lengthAttr == config.UnknownVariableValue { 747 return unknownVariable(), nil 748 } 749 750 expanded := flatmap.Expand(attributes, resourceID) 751 return hil.InterfaceToVariable(expanded) 752 } 753 754 return ast.Variable{}, fmt.Errorf("No complex type %s found", resourceID) 755 } 756 757 func (i *Interpolater) resourceVariableInfo( 758 scope *InterpolationScope, 759 v *config.ResourceVariable) (*ModuleState, *config.Resource, error) { 760 // Get the module tree that contains our current path. This is 761 // either the current module (path is empty) or a child. 762 modTree := i.Module 763 if len(scope.Path) > 1 { 764 modTree = i.Module.Child(scope.Path[1:]) 765 } 766 767 // Get the resource from the configuration so we can verify 768 // that the resource is in the configuration and so we can access 769 // the configuration if we need to. 770 var cr *config.Resource 771 for _, r := range modTree.Config().Resources { 772 if r.Id() == v.ResourceId() { 773 cr = r 774 break 775 } 776 } 777 778 // Get the relevant module 779 module := i.State.ModuleByPath(scope.Path) 780 return module, cr, nil 781 } 782 783 func (i *Interpolater) resourceCountMax( 784 ms *ModuleState, 785 cr *config.Resource, 786 v *config.ResourceVariable) (int, error) { 787 id := v.ResourceId() 788 789 // If we're NOT applying, then we assume we can read the count 790 // from the state. Plan and so on may not have any state yet so 791 // we do a full interpolation. 792 // Don't forget walkDestroy, which is a special case of walkApply 793 if !(i.Operation == walkApply || i.Operation == walkDestroy) { 794 if cr == nil { 795 return 0, nil 796 } 797 798 count, err := cr.Count() 799 if err != nil { 800 return 0, err 801 } 802 803 return count, nil 804 } 805 806 // If we have no module state in the apply walk, that suggests we've hit 807 // a rather awkward edge-case: the resource this variable refers to 808 // has count = 0 and is the only resource processed so far on this walk, 809 // and so we've ended up not creating any resource states yet. We don't 810 // create a module state until the first resource is written into it, 811 // so the module state doesn't exist when we get here. 812 // 813 // In this case we act as we would if we had been passed a module 814 // with an empty resource state map. 815 if ms == nil { 816 return 0, nil 817 } 818 819 // We need to determine the list of resource keys to get values from. 820 // This needs to be sorted so the order is deterministic. We used to 821 // use "cr.Count()" but that doesn't work if the count is interpolated 822 // and we can't guarantee that so we instead depend on the state. 823 max := -1 824 for k, s := range ms.Resources { 825 // This resource may have been just removed, in which case the Primary 826 // may be nil, or just empty. 827 if s == nil || s.Primary == nil || len(s.Primary.Attributes) == 0 { 828 continue 829 } 830 831 // Get the index number for this resource 832 index := "" 833 if k == id { 834 // If the key is the id, then its just 0 (no explicit index) 835 index = "0" 836 } else if strings.HasPrefix(k, id+".") { 837 // Grab the index number out of the state 838 index = k[len(id+"."):] 839 if idx := strings.IndexRune(index, '.'); idx >= 0 { 840 index = index[:idx] 841 } 842 } 843 844 // If there was no index then this resource didn't match 845 // the one we're looking for, exit. 846 if index == "" { 847 continue 848 } 849 850 // Turn the index into an int 851 raw, err := strconv.ParseInt(index, 0, 0) 852 if err != nil { 853 return 0, fmt.Errorf( 854 "%s: error parsing index %q as int: %s", 855 id, index, err) 856 } 857 858 // Keep track of this index if its the max 859 if new := int(raw); new > max { 860 max = new 861 } 862 } 863 864 // If we never found any matching resources in the state, we 865 // have zero. 866 if max == -1 { 867 return 0, nil 868 } 869 870 // The result value is "max+1" because we're returning the 871 // max COUNT, not the max INDEX, and we zero-index. 872 return max + 1, nil 873 }