github.com/hartzell/terraform@v0.8.6-0.20180503104400-0cc9e050ecd4/config/interpolate.go (about)

     1  package config
     2  
     3  import (
     4  	"fmt"
     5  	"strconv"
     6  	"strings"
     7  
     8  	"github.com/hashicorp/terraform/tfdiags"
     9  
    10  	"github.com/hashicorp/hil/ast"
    11  )
    12  
    13  // An InterpolatedVariable is a variable reference within an interpolation.
    14  //
    15  // Implementations of this interface represents various sources where
    16  // variables can come from: user variables, resources, etc.
    17  type InterpolatedVariable interface {
    18  	FullKey() string
    19  	SourceRange() tfdiags.SourceRange
    20  }
    21  
    22  // varRange can be embedded into an InterpolatedVariable implementation to
    23  // implement the SourceRange method.
    24  type varRange struct {
    25  	rng tfdiags.SourceRange
    26  }
    27  
    28  func (r varRange) SourceRange() tfdiags.SourceRange {
    29  	return r.rng
    30  }
    31  
    32  func makeVarRange(rng tfdiags.SourceRange) varRange {
    33  	return varRange{rng}
    34  }
    35  
    36  // CountVariable is a variable for referencing information about
    37  // the count.
    38  type CountVariable struct {
    39  	Type CountValueType
    40  	key  string
    41  	varRange
    42  }
    43  
    44  // CountValueType is the type of the count variable that is referenced.
    45  type CountValueType byte
    46  
    47  const (
    48  	CountValueInvalid CountValueType = iota
    49  	CountValueIndex
    50  )
    51  
    52  // A ModuleVariable is a variable that is referencing the output
    53  // of a module, such as "${module.foo.bar}"
    54  type ModuleVariable struct {
    55  	Name  string
    56  	Field string
    57  	key   string
    58  	varRange
    59  }
    60  
    61  // A PathVariable is a variable that references path information about the
    62  // module.
    63  type PathVariable struct {
    64  	Type PathValueType
    65  	key  string
    66  	varRange
    67  }
    68  
    69  type PathValueType byte
    70  
    71  const (
    72  	PathValueInvalid PathValueType = iota
    73  	PathValueCwd
    74  	PathValueModule
    75  	PathValueRoot
    76  )
    77  
    78  // A ResourceVariable is a variable that is referencing the field
    79  // of a resource, such as "${aws_instance.foo.ami}"
    80  type ResourceVariable struct {
    81  	Mode  ResourceMode
    82  	Type  string // Resource type, i.e. "aws_instance"
    83  	Name  string // Resource name
    84  	Field string // Resource field
    85  
    86  	Multi bool // True if multi-variable: aws_instance.foo.*.id
    87  	Index int  // Index for multi-variable: aws_instance.foo.1.id == 1
    88  
    89  	key string
    90  	varRange
    91  }
    92  
    93  // SelfVariable is a variable that is referencing the same resource
    94  // it is running on: "${self.address}"
    95  type SelfVariable struct {
    96  	Field string
    97  
    98  	key string
    99  	varRange
   100  }
   101  
   102  // SimpleVariable is an unprefixed variable, which can show up when users have
   103  // strings they are passing down to resources that use interpolation
   104  // internally. The template_file resource is an example of this.
   105  type SimpleVariable struct {
   106  	Key string
   107  	varRange
   108  }
   109  
   110  // TerraformVariable is a "terraform."-prefixed variable used to access
   111  // metadata about the Terraform run.
   112  type TerraformVariable struct {
   113  	Field string
   114  	key   string
   115  	varRange
   116  }
   117  
   118  // A UserVariable is a variable that is referencing a user variable
   119  // that is inputted from outside the configuration. This looks like
   120  // "${var.foo}"
   121  type UserVariable struct {
   122  	Name string
   123  	Elem string
   124  
   125  	key string
   126  	varRange
   127  }
   128  
   129  // A LocalVariable is a variable that references a local value defined within
   130  // the current module, via a "locals" block. This looks like "${local.foo}".
   131  type LocalVariable struct {
   132  	Name string
   133  	varRange
   134  }
   135  
   136  func NewInterpolatedVariable(v string) (InterpolatedVariable, error) {
   137  	if strings.HasPrefix(v, "count.") {
   138  		return NewCountVariable(v)
   139  	} else if strings.HasPrefix(v, "path.") {
   140  		return NewPathVariable(v)
   141  	} else if strings.HasPrefix(v, "self.") {
   142  		return NewSelfVariable(v)
   143  	} else if strings.HasPrefix(v, "terraform.") {
   144  		return NewTerraformVariable(v)
   145  	} else if strings.HasPrefix(v, "var.") {
   146  		return NewUserVariable(v)
   147  	} else if strings.HasPrefix(v, "local.") {
   148  		return NewLocalVariable(v)
   149  	} else if strings.HasPrefix(v, "module.") {
   150  		return NewModuleVariable(v)
   151  	} else if !strings.ContainsRune(v, '.') {
   152  		return NewSimpleVariable(v)
   153  	} else {
   154  		return NewResourceVariable(v)
   155  	}
   156  }
   157  
   158  func NewCountVariable(key string) (*CountVariable, error) {
   159  	var fieldType CountValueType
   160  	parts := strings.SplitN(key, ".", 2)
   161  	switch parts[1] {
   162  	case "index":
   163  		fieldType = CountValueIndex
   164  	}
   165  
   166  	return &CountVariable{
   167  		Type: fieldType,
   168  		key:  key,
   169  	}, nil
   170  }
   171  
   172  func (c *CountVariable) FullKey() string {
   173  	return c.key
   174  }
   175  
   176  func NewModuleVariable(key string) (*ModuleVariable, error) {
   177  	parts := strings.SplitN(key, ".", 3)
   178  	if len(parts) < 3 {
   179  		return nil, fmt.Errorf(
   180  			"%s: module variables must be three parts: module.name.attr",
   181  			key)
   182  	}
   183  
   184  	return &ModuleVariable{
   185  		Name:  parts[1],
   186  		Field: parts[2],
   187  		key:   key,
   188  	}, nil
   189  }
   190  
   191  func (v *ModuleVariable) FullKey() string {
   192  	return v.key
   193  }
   194  
   195  func (v *ModuleVariable) GoString() string {
   196  	return fmt.Sprintf("*%#v", *v)
   197  }
   198  
   199  func NewPathVariable(key string) (*PathVariable, error) {
   200  	var fieldType PathValueType
   201  	parts := strings.SplitN(key, ".", 2)
   202  	switch parts[1] {
   203  	case "cwd":
   204  		fieldType = PathValueCwd
   205  	case "module":
   206  		fieldType = PathValueModule
   207  	case "root":
   208  		fieldType = PathValueRoot
   209  	}
   210  
   211  	return &PathVariable{
   212  		Type: fieldType,
   213  		key:  key,
   214  	}, nil
   215  }
   216  
   217  func (v *PathVariable) FullKey() string {
   218  	return v.key
   219  }
   220  
   221  func NewResourceVariable(key string) (*ResourceVariable, error) {
   222  	var mode ResourceMode
   223  	var parts []string
   224  	if strings.HasPrefix(key, "data.") {
   225  		mode = DataResourceMode
   226  		parts = strings.SplitN(key, ".", 4)
   227  		if len(parts) < 4 {
   228  			return nil, fmt.Errorf(
   229  				"%s: data variables must be four parts: data.TYPE.NAME.ATTR",
   230  				key)
   231  		}
   232  
   233  		// Don't actually need the "data." prefix for parsing, since it's
   234  		// always constant.
   235  		parts = parts[1:]
   236  	} else {
   237  		mode = ManagedResourceMode
   238  		parts = strings.SplitN(key, ".", 3)
   239  		if len(parts) < 3 {
   240  			return nil, fmt.Errorf(
   241  				"%s: resource variables must be three parts: TYPE.NAME.ATTR",
   242  				key)
   243  		}
   244  	}
   245  
   246  	field := parts[2]
   247  	multi := false
   248  	var index int
   249  
   250  	if idx := strings.Index(field, "."); idx != -1 {
   251  		indexStr := field[:idx]
   252  		multi = indexStr == "*"
   253  		index = -1
   254  
   255  		if !multi {
   256  			indexInt, err := strconv.ParseInt(indexStr, 0, 0)
   257  			if err == nil {
   258  				multi = true
   259  				index = int(indexInt)
   260  			}
   261  		}
   262  
   263  		if multi {
   264  			field = field[idx+1:]
   265  		}
   266  	}
   267  
   268  	return &ResourceVariable{
   269  		Mode:  mode,
   270  		Type:  parts[0],
   271  		Name:  parts[1],
   272  		Field: field,
   273  		Multi: multi,
   274  		Index: index,
   275  		key:   key,
   276  	}, nil
   277  }
   278  
   279  func (v *ResourceVariable) ResourceId() string {
   280  	switch v.Mode {
   281  	case ManagedResourceMode:
   282  		return fmt.Sprintf("%s.%s", v.Type, v.Name)
   283  	case DataResourceMode:
   284  		return fmt.Sprintf("data.%s.%s", v.Type, v.Name)
   285  	default:
   286  		panic(fmt.Errorf("unknown resource mode %s", v.Mode))
   287  	}
   288  }
   289  
   290  func (v *ResourceVariable) FullKey() string {
   291  	return v.key
   292  }
   293  
   294  func NewSelfVariable(key string) (*SelfVariable, error) {
   295  	field := key[len("self."):]
   296  
   297  	return &SelfVariable{
   298  		Field: field,
   299  
   300  		key: key,
   301  	}, nil
   302  }
   303  
   304  func (v *SelfVariable) FullKey() string {
   305  	return v.key
   306  }
   307  
   308  func (v *SelfVariable) GoString() string {
   309  	return fmt.Sprintf("*%#v", *v)
   310  }
   311  
   312  func NewSimpleVariable(key string) (*SimpleVariable, error) {
   313  	return &SimpleVariable{Key: key}, nil
   314  }
   315  
   316  func (v *SimpleVariable) FullKey() string {
   317  	return v.Key
   318  }
   319  
   320  func (v *SimpleVariable) GoString() string {
   321  	return fmt.Sprintf("*%#v", *v)
   322  }
   323  
   324  func NewTerraformVariable(key string) (*TerraformVariable, error) {
   325  	field := key[len("terraform."):]
   326  	return &TerraformVariable{
   327  		Field: field,
   328  		key:   key,
   329  	}, nil
   330  }
   331  
   332  func (v *TerraformVariable) FullKey() string {
   333  	return v.key
   334  }
   335  
   336  func (v *TerraformVariable) GoString() string {
   337  	return fmt.Sprintf("*%#v", *v)
   338  }
   339  
   340  func NewUserVariable(key string) (*UserVariable, error) {
   341  	name := key[len("var."):]
   342  	elem := ""
   343  	if idx := strings.Index(name, "."); idx > -1 {
   344  		elem = name[idx+1:]
   345  		name = name[:idx]
   346  	}
   347  
   348  	if len(elem) > 0 {
   349  		return nil, fmt.Errorf("Invalid dot index found: 'var.%s.%s'. Values in maps and lists can be referenced using square bracket indexing, like: 'var.mymap[\"key\"]' or 'var.mylist[1]'.", name, elem)
   350  	}
   351  
   352  	return &UserVariable{
   353  		key: key,
   354  
   355  		Name: name,
   356  		Elem: elem,
   357  	}, nil
   358  }
   359  
   360  func (v *UserVariable) FullKey() string {
   361  	return v.key
   362  }
   363  
   364  func (v *UserVariable) GoString() string {
   365  	return fmt.Sprintf("*%#v", *v)
   366  }
   367  
   368  func NewLocalVariable(key string) (*LocalVariable, error) {
   369  	name := key[len("local."):]
   370  	if idx := strings.Index(name, "."); idx > -1 {
   371  		return nil, fmt.Errorf("Can't use dot (.) attribute access in local.%s; use square bracket indexing", name)
   372  	}
   373  
   374  	return &LocalVariable{
   375  		Name: name,
   376  	}, nil
   377  }
   378  
   379  func (v *LocalVariable) FullKey() string {
   380  	return fmt.Sprintf("local.%s", v.Name)
   381  }
   382  
   383  func (v *LocalVariable) GoString() string {
   384  	return fmt.Sprintf("*%#v", *v)
   385  }
   386  
   387  // DetectVariables takes an AST root and returns all the interpolated
   388  // variables that are detected in the AST tree.
   389  func DetectVariables(root ast.Node) ([]InterpolatedVariable, error) {
   390  	var result []InterpolatedVariable
   391  	var resultErr error
   392  
   393  	// Visitor callback
   394  	fn := func(n ast.Node) ast.Node {
   395  		if resultErr != nil {
   396  			return n
   397  		}
   398  
   399  		switch vn := n.(type) {
   400  		case *ast.VariableAccess:
   401  			v, err := NewInterpolatedVariable(vn.Name)
   402  			if err != nil {
   403  				resultErr = err
   404  				return n
   405  			}
   406  			result = append(result, v)
   407  		case *ast.Index:
   408  			if va, ok := vn.Target.(*ast.VariableAccess); ok {
   409  				v, err := NewInterpolatedVariable(va.Name)
   410  				if err != nil {
   411  					resultErr = err
   412  					return n
   413  				}
   414  				result = append(result, v)
   415  			}
   416  			if va, ok := vn.Key.(*ast.VariableAccess); ok {
   417  				v, err := NewInterpolatedVariable(va.Name)
   418  				if err != nil {
   419  					resultErr = err
   420  					return n
   421  				}
   422  				result = append(result, v)
   423  			}
   424  		default:
   425  			return n
   426  		}
   427  
   428  		return n
   429  	}
   430  
   431  	// Visitor pattern
   432  	root.Accept(fn)
   433  
   434  	if resultErr != nil {
   435  		return nil, resultErr
   436  	}
   437  
   438  	return result, nil
   439  }