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