]> git.immae.eu Git - github/fretlink/terraform-provider-statuscake.git/blob - vendor/github.com/hashicorp/terraform/terraform/interpolate.go
vendor: github.com/hashicorp/terraform/...@v0.10.0
[github/fretlink/terraform-provider-statuscake.git] / vendor / github.com / hashicorp / terraform / terraform / interpolate.go
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 }