github.com/nathanielks/terraform@v0.6.1-0.20170509030759-13e1a62319dc/terraform/interpolate.go (about)

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