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