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