github.com/renier/terraform@v0.7.8-0.20161024133817-eb8a9ef5471a/config/interpolate.go (about)

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