]> git.immae.eu Git - github/fretlink/terraform-provider-statuscake.git/blob - vendor/github.com/hashicorp/terraform/terraform/interpolate.go
Upgrade to 0.12
[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 return nil, fmt.Errorf("type Interpolator is no longer supported; use the evaluator API instead")
49 }
50
51 func (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
71 func unknownVariable() ast.Variable {
72 return ast.Variable{
73 Type: ast.TypeUnknown,
74 Value: config.UnknownVariableValue,
75 }
76 }
77
78 func unknownValue() string {
79 return hil.UnknownValue
80 }
81
82 func (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
138 func (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
176 func (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
220 func (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
243 func (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
258 func (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
280 func (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
333 func (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
382 func (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
514 MISSING:
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
541 func (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
646 func (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
689 func (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
715 func (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 }