github.com/mikesimons/terraform@v0.6.13-0.20160304043642-f11448c69214/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  	Type  string // Resource type, i.e. "aws_instance"
    62  	Name  string // Resource name
    63  	Field string // Resource field
    64  
    65  	Multi bool // True if multi-variable: aws_instance.foo.*.id
    66  	Index int  // Index for multi-variable: aws_instance.foo.1.id == 1
    67  
    68  	key string
    69  }
    70  
    71  // SelfVariable is a variable that is referencing the same resource
    72  // it is running on: "${self.address}"
    73  type SelfVariable struct {
    74  	Field string
    75  
    76  	key string
    77  }
    78  
    79  // SimpleVariable is an unprefixed variable, which can show up when users have
    80  // strings they are passing down to resources that use interpolation
    81  // internally. The template_file resource is an example of this.
    82  type SimpleVariable struct {
    83  	Key string
    84  }
    85  
    86  // A UserVariable is a variable that is referencing a user variable
    87  // that is inputted from outside the configuration. This looks like
    88  // "${var.foo}"
    89  type UserVariable struct {
    90  	Name string
    91  	Elem string
    92  
    93  	key string
    94  }
    95  
    96  func NewInterpolatedVariable(v string) (InterpolatedVariable, error) {
    97  	if strings.HasPrefix(v, "count.") {
    98  		return NewCountVariable(v)
    99  	} else if strings.HasPrefix(v, "path.") {
   100  		return NewPathVariable(v)
   101  	} else if strings.HasPrefix(v, "self.") {
   102  		return NewSelfVariable(v)
   103  	} else if strings.HasPrefix(v, "var.") {
   104  		return NewUserVariable(v)
   105  	} else if strings.HasPrefix(v, "module.") {
   106  		return NewModuleVariable(v)
   107  	} else if !strings.ContainsRune(v, '.') {
   108  		return NewSimpleVariable(v)
   109  	} else {
   110  		return NewResourceVariable(v)
   111  	}
   112  }
   113  
   114  func NewCountVariable(key string) (*CountVariable, error) {
   115  	var fieldType CountValueType
   116  	parts := strings.SplitN(key, ".", 2)
   117  	switch parts[1] {
   118  	case "index":
   119  		fieldType = CountValueIndex
   120  	}
   121  
   122  	return &CountVariable{
   123  		Type: fieldType,
   124  		key:  key,
   125  	}, nil
   126  }
   127  
   128  func (c *CountVariable) FullKey() string {
   129  	return c.key
   130  }
   131  
   132  func NewModuleVariable(key string) (*ModuleVariable, error) {
   133  	parts := strings.SplitN(key, ".", 3)
   134  	if len(parts) < 3 {
   135  		return nil, fmt.Errorf(
   136  			"%s: module variables must be three parts: module.name.attr",
   137  			key)
   138  	}
   139  
   140  	return &ModuleVariable{
   141  		Name:  parts[1],
   142  		Field: parts[2],
   143  		key:   key,
   144  	}, nil
   145  }
   146  
   147  func (v *ModuleVariable) FullKey() string {
   148  	return v.key
   149  }
   150  
   151  func NewPathVariable(key string) (*PathVariable, error) {
   152  	var fieldType PathValueType
   153  	parts := strings.SplitN(key, ".", 2)
   154  	switch parts[1] {
   155  	case "cwd":
   156  		fieldType = PathValueCwd
   157  	case "module":
   158  		fieldType = PathValueModule
   159  	case "root":
   160  		fieldType = PathValueRoot
   161  	}
   162  
   163  	return &PathVariable{
   164  		Type: fieldType,
   165  		key:  key,
   166  	}, nil
   167  }
   168  
   169  func (v *PathVariable) FullKey() string {
   170  	return v.key
   171  }
   172  
   173  func NewResourceVariable(key string) (*ResourceVariable, error) {
   174  	parts := strings.SplitN(key, ".", 3)
   175  	if len(parts) < 3 {
   176  		return nil, fmt.Errorf(
   177  			"%s: resource variables must be three parts: type.name.attr",
   178  			key)
   179  	}
   180  
   181  	field := parts[2]
   182  	multi := false
   183  	var index int
   184  
   185  	if idx := strings.Index(field, "."); idx != -1 {
   186  		indexStr := field[:idx]
   187  		multi = indexStr == "*"
   188  		index = -1
   189  
   190  		if !multi {
   191  			indexInt, err := strconv.ParseInt(indexStr, 0, 0)
   192  			if err == nil {
   193  				multi = true
   194  				index = int(indexInt)
   195  			}
   196  		}
   197  
   198  		if multi {
   199  			field = field[idx+1:]
   200  		}
   201  	}
   202  
   203  	return &ResourceVariable{
   204  		Type:  parts[0],
   205  		Name:  parts[1],
   206  		Field: field,
   207  		Multi: multi,
   208  		Index: index,
   209  		key:   key,
   210  	}, nil
   211  }
   212  
   213  func (v *ResourceVariable) ResourceId() string {
   214  	return fmt.Sprintf("%s.%s", v.Type, v.Name)
   215  }
   216  
   217  func (v *ResourceVariable) FullKey() string {
   218  	return v.key
   219  }
   220  
   221  func NewSelfVariable(key string) (*SelfVariable, error) {
   222  	field := key[len("self."):]
   223  
   224  	return &SelfVariable{
   225  		Field: field,
   226  
   227  		key: key,
   228  	}, nil
   229  }
   230  
   231  func (v *SelfVariable) FullKey() string {
   232  	return v.key
   233  }
   234  
   235  func (v *SelfVariable) GoString() string {
   236  	return fmt.Sprintf("*%#v", *v)
   237  }
   238  
   239  func NewSimpleVariable(key string) (*SimpleVariable, error) {
   240  	return &SimpleVariable{key}, nil
   241  }
   242  
   243  func (v *SimpleVariable) FullKey() string {
   244  	return v.Key
   245  }
   246  
   247  func (v *SimpleVariable) GoString() string {
   248  	return fmt.Sprintf("*%#v", *v)
   249  }
   250  
   251  func NewUserVariable(key string) (*UserVariable, error) {
   252  	name := key[len("var."):]
   253  	elem := ""
   254  	if idx := strings.Index(name, "."); idx > -1 {
   255  		elem = name[idx+1:]
   256  		name = name[:idx]
   257  	}
   258  
   259  	return &UserVariable{
   260  		key: key,
   261  
   262  		Name: name,
   263  		Elem: elem,
   264  	}, nil
   265  }
   266  
   267  func (v *UserVariable) FullKey() string {
   268  	return v.key
   269  }
   270  
   271  func (v *UserVariable) GoString() string {
   272  	return fmt.Sprintf("*%#v", *v)
   273  }
   274  
   275  // DetectVariables takes an AST root and returns all the interpolated
   276  // variables that are detected in the AST tree.
   277  func DetectVariables(root ast.Node) ([]InterpolatedVariable, error) {
   278  	var result []InterpolatedVariable
   279  	var resultErr error
   280  
   281  	// Visitor callback
   282  	fn := func(n ast.Node) ast.Node {
   283  		if resultErr != nil {
   284  			return n
   285  		}
   286  
   287  		vn, ok := n.(*ast.VariableAccess)
   288  		if !ok {
   289  			return n
   290  		}
   291  
   292  		v, err := NewInterpolatedVariable(vn.Name)
   293  		if err != nil {
   294  			resultErr = err
   295  			return n
   296  		}
   297  
   298  		result = append(result, v)
   299  		return n
   300  	}
   301  
   302  	// Visitor pattern
   303  	root.Accept(fn)
   304  
   305  	if resultErr != nil {
   306  		return nil, resultErr
   307  	}
   308  
   309  	return result, nil
   310  }