github.com/paybyphone/terraform@v0.9.5-0.20170613192930-9706042ddd51/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  // TerraformVariable is a "terraform."-prefixed variable used to access
    88  // metadata about the Terraform run.
    89  type TerraformVariable struct {
    90  	Field string
    91  	key   string
    92  }
    93  
    94  // A UserVariable is a variable that is referencing a user variable
    95  // that is inputted from outside the configuration. This looks like
    96  // "${var.foo}"
    97  type UserVariable struct {
    98  	Name string
    99  	Elem string
   100  
   101  	key string
   102  }
   103  
   104  func NewInterpolatedVariable(v string) (InterpolatedVariable, error) {
   105  	if strings.HasPrefix(v, "count.") {
   106  		return NewCountVariable(v)
   107  	} else if strings.HasPrefix(v, "path.") {
   108  		return NewPathVariable(v)
   109  	} else if strings.HasPrefix(v, "self.") {
   110  		return NewSelfVariable(v)
   111  	} else if strings.HasPrefix(v, "terraform.") {
   112  		return NewTerraformVariable(v)
   113  	} else if strings.HasPrefix(v, "var.") {
   114  		return NewUserVariable(v)
   115  	} else if strings.HasPrefix(v, "module.") {
   116  		return NewModuleVariable(v)
   117  	} else if !strings.ContainsRune(v, '.') {
   118  		return NewSimpleVariable(v)
   119  	} else {
   120  		return NewResourceVariable(v)
   121  	}
   122  }
   123  
   124  func NewCountVariable(key string) (*CountVariable, error) {
   125  	var fieldType CountValueType
   126  	parts := strings.SplitN(key, ".", 2)
   127  	switch parts[1] {
   128  	case "index":
   129  		fieldType = CountValueIndex
   130  	}
   131  
   132  	return &CountVariable{
   133  		Type: fieldType,
   134  		key:  key,
   135  	}, nil
   136  }
   137  
   138  func (c *CountVariable) FullKey() string {
   139  	return c.key
   140  }
   141  
   142  func NewModuleVariable(key string) (*ModuleVariable, error) {
   143  	parts := strings.SplitN(key, ".", 3)
   144  	if len(parts) < 3 {
   145  		return nil, fmt.Errorf(
   146  			"%s: module variables must be three parts: module.name.attr",
   147  			key)
   148  	}
   149  
   150  	return &ModuleVariable{
   151  		Name:  parts[1],
   152  		Field: parts[2],
   153  		key:   key,
   154  	}, nil
   155  }
   156  
   157  func (v *ModuleVariable) FullKey() string {
   158  	return v.key
   159  }
   160  
   161  func (v *ModuleVariable) GoString() string {
   162  	return fmt.Sprintf("*%#v", *v)
   163  }
   164  
   165  func NewPathVariable(key string) (*PathVariable, error) {
   166  	var fieldType PathValueType
   167  	parts := strings.SplitN(key, ".", 2)
   168  	switch parts[1] {
   169  	case "cwd":
   170  		fieldType = PathValueCwd
   171  	case "module":
   172  		fieldType = PathValueModule
   173  	case "root":
   174  		fieldType = PathValueRoot
   175  	}
   176  
   177  	return &PathVariable{
   178  		Type: fieldType,
   179  		key:  key,
   180  	}, nil
   181  }
   182  
   183  func (v *PathVariable) FullKey() string {
   184  	return v.key
   185  }
   186  
   187  func NewResourceVariable(key string) (*ResourceVariable, error) {
   188  	var mode ResourceMode
   189  	var parts []string
   190  	if strings.HasPrefix(key, "data.") {
   191  		mode = DataResourceMode
   192  		parts = strings.SplitN(key, ".", 4)
   193  		if len(parts) < 4 {
   194  			return nil, fmt.Errorf(
   195  				"%s: data variables must be four parts: data.TYPE.NAME.ATTR",
   196  				key)
   197  		}
   198  
   199  		// Don't actually need the "data." prefix for parsing, since it's
   200  		// always constant.
   201  		parts = parts[1:]
   202  	} else {
   203  		mode = ManagedResourceMode
   204  		parts = strings.SplitN(key, ".", 3)
   205  		if len(parts) < 3 {
   206  			return nil, fmt.Errorf(
   207  				"%s: resource variables must be three parts: TYPE.NAME.ATTR",
   208  				key)
   209  		}
   210  	}
   211  
   212  	field := parts[2]
   213  	multi := false
   214  	var index int
   215  
   216  	if idx := strings.Index(field, "."); idx != -1 {
   217  		indexStr := field[:idx]
   218  		multi = indexStr == "*"
   219  		index = -1
   220  
   221  		if !multi {
   222  			indexInt, err := strconv.ParseInt(indexStr, 0, 0)
   223  			if err == nil {
   224  				multi = true
   225  				index = int(indexInt)
   226  			}
   227  		}
   228  
   229  		if multi {
   230  			field = field[idx+1:]
   231  		}
   232  	}
   233  
   234  	return &ResourceVariable{
   235  		Mode:  mode,
   236  		Type:  parts[0],
   237  		Name:  parts[1],
   238  		Field: field,
   239  		Multi: multi,
   240  		Index: index,
   241  		key:   key,
   242  	}, nil
   243  }
   244  
   245  func (v *ResourceVariable) ResourceId() string {
   246  	switch v.Mode {
   247  	case ManagedResourceMode:
   248  		return fmt.Sprintf("%s.%s", v.Type, v.Name)
   249  	case DataResourceMode:
   250  		return fmt.Sprintf("data.%s.%s", v.Type, v.Name)
   251  	default:
   252  		panic(fmt.Errorf("unknown resource mode %s", v.Mode))
   253  	}
   254  }
   255  
   256  func (v *ResourceVariable) FullKey() string {
   257  	return v.key
   258  }
   259  
   260  func NewSelfVariable(key string) (*SelfVariable, error) {
   261  	field := key[len("self."):]
   262  
   263  	return &SelfVariable{
   264  		Field: field,
   265  
   266  		key: key,
   267  	}, nil
   268  }
   269  
   270  func (v *SelfVariable) FullKey() string {
   271  	return v.key
   272  }
   273  
   274  func (v *SelfVariable) GoString() string {
   275  	return fmt.Sprintf("*%#v", *v)
   276  }
   277  
   278  func NewSimpleVariable(key string) (*SimpleVariable, error) {
   279  	return &SimpleVariable{key}, nil
   280  }
   281  
   282  func (v *SimpleVariable) FullKey() string {
   283  	return v.Key
   284  }
   285  
   286  func (v *SimpleVariable) GoString() string {
   287  	return fmt.Sprintf("*%#v", *v)
   288  }
   289  
   290  func NewTerraformVariable(key string) (*TerraformVariable, error) {
   291  	field := key[len("terraform."):]
   292  	return &TerraformVariable{
   293  		Field: field,
   294  		key:   key,
   295  	}, nil
   296  }
   297  
   298  func (v *TerraformVariable) FullKey() string {
   299  	return v.key
   300  }
   301  
   302  func (v *TerraformVariable) GoString() string {
   303  	return fmt.Sprintf("*%#v", *v)
   304  }
   305  
   306  func NewUserVariable(key string) (*UserVariable, error) {
   307  	name := key[len("var."):]
   308  	elem := ""
   309  	if idx := strings.Index(name, "."); idx > -1 {
   310  		elem = name[idx+1:]
   311  		name = name[:idx]
   312  	}
   313  
   314  	if len(elem) > 0 {
   315  		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)
   316  	}
   317  
   318  	return &UserVariable{
   319  		key: key,
   320  
   321  		Name: name,
   322  		Elem: elem,
   323  	}, nil
   324  }
   325  
   326  func (v *UserVariable) FullKey() string {
   327  	return v.key
   328  }
   329  
   330  func (v *UserVariable) GoString() string {
   331  	return fmt.Sprintf("*%#v", *v)
   332  }
   333  
   334  // DetectVariables takes an AST root and returns all the interpolated
   335  // variables that are detected in the AST tree.
   336  func DetectVariables(root ast.Node) ([]InterpolatedVariable, error) {
   337  	var result []InterpolatedVariable
   338  	var resultErr error
   339  
   340  	// Visitor callback
   341  	fn := func(n ast.Node) ast.Node {
   342  		if resultErr != nil {
   343  			return n
   344  		}
   345  
   346  		switch vn := n.(type) {
   347  		case *ast.VariableAccess:
   348  			v, err := NewInterpolatedVariable(vn.Name)
   349  			if err != nil {
   350  				resultErr = err
   351  				return n
   352  			}
   353  			result = append(result, v)
   354  		case *ast.Index:
   355  			if va, ok := vn.Target.(*ast.VariableAccess); ok {
   356  				v, err := NewInterpolatedVariable(va.Name)
   357  				if err != nil {
   358  					resultErr = err
   359  					return n
   360  				}
   361  				result = append(result, v)
   362  			}
   363  			if va, ok := vn.Key.(*ast.VariableAccess); ok {
   364  				v, err := NewInterpolatedVariable(va.Name)
   365  				if err != nil {
   366  					resultErr = err
   367  					return n
   368  				}
   369  				result = append(result, v)
   370  			}
   371  		default:
   372  			return n
   373  		}
   374  
   375  		return n
   376  	}
   377  
   378  	// Visitor pattern
   379  	root.Accept(fn)
   380  
   381  	if resultErr != nil {
   382  		return nil, resultErr
   383  	}
   384  
   385  	return result, nil
   386  }