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