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