github.com/federicobaldo/terraform@v0.6.15-0.20160323222747-b20f680cbf05/terraform/interpolate.go (about)

     1  package terraform
     2  
     3  import (
     4  	"fmt"
     5  	"log"
     6  	"os"
     7  	"regexp"
     8  	"sort"
     9  	"strings"
    10  	"sync"
    11  
    12  	"github.com/hashicorp/hil/ast"
    13  	"github.com/hashicorp/terraform/config"
    14  	"github.com/hashicorp/terraform/config/module"
    15  )
    16  
    17  const (
    18  	// VarEnvPrefix is the prefix of variables that are read from
    19  	// the environment to set variables here.
    20  	VarEnvPrefix = "TF_VAR_"
    21  )
    22  
    23  // Interpolater is the structure responsible for determining the values
    24  // for interpolations such as `aws_instance.foo.bar`.
    25  type Interpolater struct {
    26  	Operation     walkOperation
    27  	Module        *module.Tree
    28  	State         *State
    29  	StateLock     *sync.RWMutex
    30  	Variables     map[string]string
    31  	VariablesLock *sync.Mutex
    32  }
    33  
    34  // InterpolationScope is the current scope of execution. This is required
    35  // since some variables which are interpolated are dependent on what we're
    36  // operating on and where we are.
    37  type InterpolationScope struct {
    38  	Path     []string
    39  	Resource *Resource
    40  }
    41  
    42  // Values returns the values for all the variables in the given map.
    43  func (i *Interpolater) Values(
    44  	scope *InterpolationScope,
    45  	vars map[string]config.InterpolatedVariable) (map[string]ast.Variable, error) {
    46  	result := make(map[string]ast.Variable, len(vars))
    47  
    48  	// Copy the default variables
    49  	if i.Module != nil && scope != nil {
    50  		mod := i.Module
    51  		if len(scope.Path) > 1 {
    52  			mod = i.Module.Child(scope.Path[1:])
    53  		}
    54  		for _, v := range mod.Config().Variables {
    55  			for k, val := range v.DefaultsMap() {
    56  				result[k] = ast.Variable{
    57  					Value: val,
    58  					Type:  ast.TypeString,
    59  				}
    60  			}
    61  		}
    62  	}
    63  
    64  	for n, rawV := range vars {
    65  		var err error
    66  		switch v := rawV.(type) {
    67  		case *config.CountVariable:
    68  			err = i.valueCountVar(scope, n, v, result)
    69  		case *config.ModuleVariable:
    70  			err = i.valueModuleVar(scope, n, v, result)
    71  		case *config.PathVariable:
    72  			err = i.valuePathVar(scope, n, v, result)
    73  		case *config.ResourceVariable:
    74  			err = i.valueResourceVar(scope, n, v, result)
    75  		case *config.SelfVariable:
    76  			err = i.valueSelfVar(scope, n, v, result)
    77  		case *config.SimpleVariable:
    78  			err = i.valueSimpleVar(scope, n, v, result)
    79  		case *config.UserVariable:
    80  			err = i.valueUserVar(scope, n, v, result)
    81  		default:
    82  			err = fmt.Errorf("%s: unknown variable type: %T", n, rawV)
    83  		}
    84  
    85  		if err != nil {
    86  			return nil, err
    87  		}
    88  	}
    89  
    90  	return result, nil
    91  }
    92  
    93  func (i *Interpolater) valueCountVar(
    94  	scope *InterpolationScope,
    95  	n string,
    96  	v *config.CountVariable,
    97  	result map[string]ast.Variable) error {
    98  	switch v.Type {
    99  	case config.CountValueIndex:
   100  		if scope.Resource == nil {
   101  			return fmt.Errorf("%s: count.index is only valid within resources", n)
   102  		}
   103  		result[n] = ast.Variable{
   104  			Value: scope.Resource.CountIndex,
   105  			Type:  ast.TypeInt,
   106  		}
   107  		return nil
   108  	default:
   109  		return fmt.Errorf("%s: unknown count type: %#v", n, v.Type)
   110  	}
   111  }
   112  
   113  func (i *Interpolater) valueModuleVar(
   114  	scope *InterpolationScope,
   115  	n string,
   116  	v *config.ModuleVariable,
   117  	result map[string]ast.Variable) error {
   118  	// If we're computing all dynamic fields, then module vars count
   119  	// and we mark it as computed.
   120  	if i.Operation == walkValidate {
   121  		result[n] = ast.Variable{
   122  			Value: config.UnknownVariableValue,
   123  			Type:  ast.TypeString,
   124  		}
   125  		return nil
   126  	}
   127  
   128  	// Build the path to the child module we want
   129  	path := make([]string, len(scope.Path), len(scope.Path)+1)
   130  	copy(path, scope.Path)
   131  	path = append(path, v.Name)
   132  
   133  	// Grab the lock so that if other interpolations are running or
   134  	// state is being modified, we'll be safe.
   135  	i.StateLock.RLock()
   136  	defer i.StateLock.RUnlock()
   137  
   138  	// Get the module where we're looking for the value
   139  	var value string
   140  	mod := i.State.ModuleByPath(path)
   141  	if mod == nil {
   142  		// If the module doesn't exist, then we can return an empty string.
   143  		// This happens usually only in Refresh() when we haven't populated
   144  		// a state. During validation, we semantically verify that all
   145  		// modules reference other modules, and graph ordering should
   146  		// ensure that the module is in the state, so if we reach this
   147  		// point otherwise it really is a panic.
   148  		value = config.UnknownVariableValue
   149  	} else {
   150  		// Get the value from the outputs
   151  		var ok bool
   152  		value, ok = mod.Outputs[v.Field]
   153  		if !ok {
   154  			// Same reasons as the comment above.
   155  			value = config.UnknownVariableValue
   156  		}
   157  	}
   158  
   159  	result[n] = ast.Variable{
   160  		Value: value,
   161  		Type:  ast.TypeString,
   162  	}
   163  	return nil
   164  }
   165  
   166  func (i *Interpolater) valuePathVar(
   167  	scope *InterpolationScope,
   168  	n string,
   169  	v *config.PathVariable,
   170  	result map[string]ast.Variable) error {
   171  	switch v.Type {
   172  	case config.PathValueCwd:
   173  		wd, err := os.Getwd()
   174  		if err != nil {
   175  			return fmt.Errorf(
   176  				"Couldn't get cwd for var %s: %s",
   177  				v.FullKey(), err)
   178  		}
   179  
   180  		result[n] = ast.Variable{
   181  			Value: wd,
   182  			Type:  ast.TypeString,
   183  		}
   184  	case config.PathValueModule:
   185  		if t := i.Module.Child(scope.Path[1:]); t != nil {
   186  			result[n] = ast.Variable{
   187  				Value: t.Config().Dir,
   188  				Type:  ast.TypeString,
   189  			}
   190  		}
   191  	case config.PathValueRoot:
   192  		result[n] = ast.Variable{
   193  			Value: i.Module.Config().Dir,
   194  			Type:  ast.TypeString,
   195  		}
   196  	default:
   197  		return fmt.Errorf("%s: unknown path type: %#v", n, v.Type)
   198  	}
   199  
   200  	return nil
   201  
   202  }
   203  
   204  func (i *Interpolater) valueResourceVar(
   205  	scope *InterpolationScope,
   206  	n string,
   207  	v *config.ResourceVariable,
   208  	result map[string]ast.Variable) error {
   209  	// If we're computing all dynamic fields, then module vars count
   210  	// and we mark it as computed.
   211  	if i.Operation == walkValidate {
   212  		result[n] = ast.Variable{
   213  			Value: config.UnknownVariableValue,
   214  			Type:  ast.TypeString,
   215  		}
   216  		return nil
   217  	}
   218  
   219  	var attr string
   220  	var err error
   221  	if v.Multi && v.Index == -1 {
   222  		attr, err = i.computeResourceMultiVariable(scope, v)
   223  	} else {
   224  		attr, err = i.computeResourceVariable(scope, v)
   225  	}
   226  	if err != nil {
   227  		return err
   228  	}
   229  
   230  	result[n] = ast.Variable{
   231  		Value: attr,
   232  		Type:  ast.TypeString,
   233  	}
   234  	return nil
   235  }
   236  
   237  func (i *Interpolater) valueSelfVar(
   238  	scope *InterpolationScope,
   239  	n string,
   240  	v *config.SelfVariable,
   241  	result map[string]ast.Variable) error {
   242  	if scope == nil || scope.Resource == nil {
   243  		return fmt.Errorf(
   244  			"%s: invalid scope, self variables are only valid on resources", n)
   245  	}
   246  	rv, err := config.NewResourceVariable(fmt.Sprintf(
   247  		"%s.%s.%d.%s",
   248  		scope.Resource.Type,
   249  		scope.Resource.Name,
   250  		scope.Resource.CountIndex,
   251  		v.Field))
   252  	if err != nil {
   253  		return err
   254  	}
   255  
   256  	return i.valueResourceVar(scope, n, rv, result)
   257  }
   258  
   259  func (i *Interpolater) valueSimpleVar(
   260  	scope *InterpolationScope,
   261  	n string,
   262  	v *config.SimpleVariable,
   263  	result map[string]ast.Variable) error {
   264  	// SimpleVars are never handled by Terraform's interpolator
   265  	result[n] = ast.Variable{
   266  		Value: config.UnknownVariableValue,
   267  		Type:  ast.TypeString,
   268  	}
   269  	return nil
   270  }
   271  
   272  func (i *Interpolater) valueUserVar(
   273  	scope *InterpolationScope,
   274  	n string,
   275  	v *config.UserVariable,
   276  	result map[string]ast.Variable) error {
   277  	i.VariablesLock.Lock()
   278  	defer i.VariablesLock.Unlock()
   279  	val, ok := i.Variables[v.Name]
   280  	if ok {
   281  		result[n] = ast.Variable{
   282  			Value: val,
   283  			Type:  ast.TypeString,
   284  		}
   285  		return nil
   286  	}
   287  
   288  	if _, ok := result[n]; !ok && i.Operation == walkValidate {
   289  		result[n] = ast.Variable{
   290  			Value: config.UnknownVariableValue,
   291  			Type:  ast.TypeString,
   292  		}
   293  		return nil
   294  	}
   295  
   296  	// Look up if we have any variables with this prefix because
   297  	// those are map overrides. Include those.
   298  	for k, val := range i.Variables {
   299  		if strings.HasPrefix(k, v.Name+".") {
   300  			result["var."+k] = ast.Variable{
   301  				Value: val,
   302  				Type:  ast.TypeString,
   303  			}
   304  		}
   305  	}
   306  
   307  	return nil
   308  }
   309  
   310  func (i *Interpolater) computeResourceVariable(
   311  	scope *InterpolationScope,
   312  	v *config.ResourceVariable) (string, error) {
   313  	id := v.ResourceId()
   314  	if v.Multi {
   315  		id = fmt.Sprintf("%s.%d", id, v.Index)
   316  	}
   317  
   318  	i.StateLock.RLock()
   319  	defer i.StateLock.RUnlock()
   320  
   321  	// Get the information about this resource variable, and verify
   322  	// that it exists and such.
   323  	module, _, err := i.resourceVariableInfo(scope, v)
   324  	if err != nil {
   325  		return "", err
   326  	}
   327  
   328  	// If we have no module in the state yet or count, return empty
   329  	if module == nil || len(module.Resources) == 0 {
   330  		return "", nil
   331  	}
   332  
   333  	// Get the resource out from the state. We know the state exists
   334  	// at this point and if there is a state, we expect there to be a
   335  	// resource with the given name.
   336  	r, ok := module.Resources[id]
   337  	if !ok && v.Multi && v.Index == 0 {
   338  		r, ok = module.Resources[v.ResourceId()]
   339  	}
   340  	if !ok {
   341  		r = nil
   342  	}
   343  	if r == nil {
   344  		goto MISSING
   345  	}
   346  
   347  	if r.Primary == nil {
   348  		goto MISSING
   349  	}
   350  
   351  	if attr, ok := r.Primary.Attributes[v.Field]; ok {
   352  		return attr, nil
   353  	}
   354  
   355  	// computed list attribute
   356  	if _, ok := r.Primary.Attributes[v.Field+".#"]; ok {
   357  		return i.interpolateListAttribute(v.Field, r.Primary.Attributes)
   358  	}
   359  
   360  	// At apply time, we can't do the "maybe has it" check below
   361  	// that we need for plans since parent elements might be computed.
   362  	// Therefore, it is an error and we're missing the key.
   363  	//
   364  	// TODO: test by creating a state and configuration that is referencing
   365  	// a non-existent variable "foo.bar" where the state only has "foo"
   366  	// and verify plan works, but apply doesn't.
   367  	if i.Operation == walkApply || i.Operation == walkDestroy {
   368  		goto MISSING
   369  	}
   370  
   371  	// We didn't find the exact field, so lets separate the dots
   372  	// and see if anything along the way is a computed set. i.e. if
   373  	// we have "foo.0.bar" as the field, check to see if "foo" is
   374  	// a computed list. If so, then the whole thing is computed.
   375  	if parts := strings.Split(v.Field, "."); len(parts) > 1 {
   376  		for i := 1; i < len(parts); i++ {
   377  			// Lists and sets make this
   378  			key := fmt.Sprintf("%s.#", strings.Join(parts[:i], "."))
   379  			if attr, ok := r.Primary.Attributes[key]; ok {
   380  				return attr, nil
   381  			}
   382  
   383  			// Maps make this
   384  			key = fmt.Sprintf("%s", strings.Join(parts[:i], "."))
   385  			if attr, ok := r.Primary.Attributes[key]; ok {
   386  				return attr, nil
   387  			}
   388  		}
   389  	}
   390  
   391  MISSING:
   392  	// Validation for missing interpolations should happen at a higher
   393  	// semantic level. If we reached this point and don't have variables,
   394  	// just return the computed value.
   395  	if scope == nil && scope.Resource == nil {
   396  		return config.UnknownVariableValue, nil
   397  	}
   398  
   399  	// If the operation is refresh, it isn't an error for a value to
   400  	// be unknown. Instead, we return that the value is computed so
   401  	// that the graph can continue to refresh other nodes. It doesn't
   402  	// matter because the config isn't interpolated anyways.
   403  	//
   404  	// For a Destroy, we're also fine with computed values, since our goal is
   405  	// only to get destroy nodes for existing resources.
   406  	//
   407  	// For an input walk, computed values are okay to return because we're only
   408  	// looking for missing variables to prompt the user for.
   409  	if i.Operation == walkRefresh || i.Operation == walkPlanDestroy || i.Operation == walkDestroy || i.Operation == walkInput {
   410  		return config.UnknownVariableValue, nil
   411  	}
   412  
   413  	return "", fmt.Errorf(
   414  		"Resource '%s' does not have attribute '%s' "+
   415  			"for variable '%s'",
   416  		id,
   417  		v.Field,
   418  		v.FullKey())
   419  }
   420  
   421  func (i *Interpolater) computeResourceMultiVariable(
   422  	scope *InterpolationScope,
   423  	v *config.ResourceVariable) (string, error) {
   424  	i.StateLock.RLock()
   425  	defer i.StateLock.RUnlock()
   426  
   427  	// Get the information about this resource variable, and verify
   428  	// that it exists and such.
   429  	module, cr, err := i.resourceVariableInfo(scope, v)
   430  	if err != nil {
   431  		return "", err
   432  	}
   433  
   434  	// Get the count so we know how many to iterate over
   435  	count, err := cr.Count()
   436  	if err != nil {
   437  		return "", fmt.Errorf(
   438  			"Error reading %s count: %s",
   439  			v.ResourceId(),
   440  			err)
   441  	}
   442  
   443  	// If we have no module in the state yet or count, return empty
   444  	if module == nil || len(module.Resources) == 0 || count == 0 {
   445  		return "", nil
   446  	}
   447  
   448  	var values []string
   449  	for j := 0; j < count; j++ {
   450  		id := fmt.Sprintf("%s.%d", v.ResourceId(), j)
   451  
   452  		// If we're dealing with only a single resource, then the
   453  		// ID doesn't have a trailing index.
   454  		if count == 1 {
   455  			id = v.ResourceId()
   456  		}
   457  
   458  		r, ok := module.Resources[id]
   459  		if !ok {
   460  			continue
   461  		}
   462  
   463  		if r.Primary == nil {
   464  			continue
   465  		}
   466  
   467  		attr, ok := r.Primary.Attributes[v.Field]
   468  		if !ok {
   469  			// computed list attribute
   470  			_, ok := r.Primary.Attributes[v.Field+".#"]
   471  			if !ok {
   472  				continue
   473  			}
   474  			attr, err = i.interpolateListAttribute(v.Field, r.Primary.Attributes)
   475  			if err != nil {
   476  				return "", err
   477  			}
   478  		}
   479  
   480  		if config.IsStringList(attr) {
   481  			for _, s := range config.StringList(attr).Slice() {
   482  				values = append(values, s)
   483  			}
   484  			continue
   485  		}
   486  
   487  		// If any value is unknown, the whole thing is unknown
   488  		if attr == config.UnknownVariableValue {
   489  			return config.UnknownVariableValue, nil
   490  		}
   491  
   492  		values = append(values, attr)
   493  	}
   494  
   495  	if len(values) == 0 {
   496  		// If the operation is refresh, it isn't an error for a value to
   497  		// be unknown. Instead, we return that the value is computed so
   498  		// that the graph can continue to refresh other nodes. It doesn't
   499  		// matter because the config isn't interpolated anyways.
   500  		//
   501  		// For a Destroy, we're also fine with computed values, since our goal is
   502  		// only to get destroy nodes for existing resources.
   503  		//
   504  		// For an input walk, computed values are okay to return because we're only
   505  		// looking for missing variables to prompt the user for.
   506  		if i.Operation == walkRefresh || i.Operation == walkPlanDestroy || i.Operation == walkDestroy || i.Operation == walkInput {
   507  			return config.UnknownVariableValue, nil
   508  		}
   509  
   510  		return "", fmt.Errorf(
   511  			"Resource '%s' does not have attribute '%s' "+
   512  				"for variable '%s'",
   513  			v.ResourceId(),
   514  			v.Field,
   515  			v.FullKey())
   516  	}
   517  
   518  	return config.NewStringList(values).String(), nil
   519  }
   520  
   521  func (i *Interpolater) interpolateListAttribute(
   522  	resourceID string,
   523  	attributes map[string]string) (string, error) {
   524  
   525  	attr := attributes[resourceID+".#"]
   526  	log.Printf("[DEBUG] Interpolating computed list attribute %s (%s)",
   527  		resourceID, attr)
   528  
   529  	// In Terraform's internal dotted representation of list-like attributes, the
   530  	// ".#" count field is marked as unknown to indicate "this whole list is
   531  	// unknown". We must honor that meaning here so computed references can be
   532  	// treated properly during the plan phase.
   533  	if attr == config.UnknownVariableValue {
   534  		return attr, nil
   535  	}
   536  
   537  	// Otherwise we gather the values from the list-like attribute and return
   538  	// them.
   539  	var members []string
   540  	numberedListMember := regexp.MustCompile("^" + resourceID + "\\.[0-9]+$")
   541  	for id, value := range attributes {
   542  		if numberedListMember.MatchString(id) {
   543  			members = append(members, value)
   544  		}
   545  	}
   546  
   547  	sort.Strings(members)
   548  	return config.NewStringList(members).String(), nil
   549  }
   550  
   551  func (i *Interpolater) resourceVariableInfo(
   552  	scope *InterpolationScope,
   553  	v *config.ResourceVariable) (*ModuleState, *config.Resource, error) {
   554  	// Get the module tree that contains our current path. This is
   555  	// either the current module (path is empty) or a child.
   556  	modTree := i.Module
   557  	if len(scope.Path) > 1 {
   558  		modTree = i.Module.Child(scope.Path[1:])
   559  	}
   560  
   561  	// Get the resource from the configuration so we can verify
   562  	// that the resource is in the configuration and so we can access
   563  	// the configuration if we need to.
   564  	var cr *config.Resource
   565  	for _, r := range modTree.Config().Resources {
   566  		if r.Id() == v.ResourceId() {
   567  			cr = r
   568  			break
   569  		}
   570  	}
   571  	if cr == nil {
   572  		return nil, nil, fmt.Errorf(
   573  			"Resource '%s' not found for variable '%s'",
   574  			v.ResourceId(),
   575  			v.FullKey())
   576  	}
   577  
   578  	// Get the relevant module
   579  	module := i.State.ModuleByPath(scope.Path)
   580  	return module, cr, nil
   581  }