github.com/ns1/terraform@v0.7.10-0.20161109153551-8949419bef40/terraform/interpolate.go (about)

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