github.com/ojiry/terraform@v0.8.2-0.20161218223921-e50cec712c4a/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 (v *ModuleVariable) GoString() string {
   153  	return fmt.Sprintf("*%#v", *v)
   154  }
   155  
   156  func NewPathVariable(key string) (*PathVariable, error) {
   157  	var fieldType PathValueType
   158  	parts := strings.SplitN(key, ".", 2)
   159  	switch parts[1] {
   160  	case "cwd":
   161  		fieldType = PathValueCwd
   162  	case "module":
   163  		fieldType = PathValueModule
   164  	case "root":
   165  		fieldType = PathValueRoot
   166  	}
   167  
   168  	return &PathVariable{
   169  		Type: fieldType,
   170  		key:  key,
   171  	}, nil
   172  }
   173  
   174  func (v *PathVariable) FullKey() string {
   175  	return v.key
   176  }
   177  
   178  func NewResourceVariable(key string) (*ResourceVariable, error) {
   179  	var mode ResourceMode
   180  	var parts []string
   181  	if strings.HasPrefix(key, "data.") {
   182  		mode = DataResourceMode
   183  		parts = strings.SplitN(key, ".", 4)
   184  		if len(parts) < 4 {
   185  			return nil, fmt.Errorf(
   186  				"%s: data variables must be four parts: data.TYPE.NAME.ATTR",
   187  				key)
   188  		}
   189  
   190  		// Don't actually need the "data." prefix for parsing, since it's
   191  		// always constant.
   192  		parts = parts[1:]
   193  	} else {
   194  		mode = ManagedResourceMode
   195  		parts = strings.SplitN(key, ".", 3)
   196  		if len(parts) < 3 {
   197  			return nil, fmt.Errorf(
   198  				"%s: resource variables must be three parts: TYPE.NAME.ATTR",
   199  				key)
   200  		}
   201  	}
   202  
   203  	field := parts[2]
   204  	multi := false
   205  	var index int
   206  
   207  	if idx := strings.Index(field, "."); idx != -1 {
   208  		indexStr := field[:idx]
   209  		multi = indexStr == "*"
   210  		index = -1
   211  
   212  		if !multi {
   213  			indexInt, err := strconv.ParseInt(indexStr, 0, 0)
   214  			if err == nil {
   215  				multi = true
   216  				index = int(indexInt)
   217  			}
   218  		}
   219  
   220  		if multi {
   221  			field = field[idx+1:]
   222  		}
   223  	}
   224  
   225  	return &ResourceVariable{
   226  		Mode:  mode,
   227  		Type:  parts[0],
   228  		Name:  parts[1],
   229  		Field: field,
   230  		Multi: multi,
   231  		Index: index,
   232  		key:   key,
   233  	}, nil
   234  }
   235  
   236  func (v *ResourceVariable) ResourceId() string {
   237  	switch v.Mode {
   238  	case ManagedResourceMode:
   239  		return fmt.Sprintf("%s.%s", v.Type, v.Name)
   240  	case DataResourceMode:
   241  		return fmt.Sprintf("data.%s.%s", v.Type, v.Name)
   242  	default:
   243  		panic(fmt.Errorf("unknown resource mode %s", v.Mode))
   244  	}
   245  }
   246  
   247  func (v *ResourceVariable) FullKey() string {
   248  	return v.key
   249  }
   250  
   251  func NewSelfVariable(key string) (*SelfVariable, error) {
   252  	field := key[len("self."):]
   253  
   254  	return &SelfVariable{
   255  		Field: field,
   256  
   257  		key: key,
   258  	}, nil
   259  }
   260  
   261  func (v *SelfVariable) FullKey() string {
   262  	return v.key
   263  }
   264  
   265  func (v *SelfVariable) GoString() string {
   266  	return fmt.Sprintf("*%#v", *v)
   267  }
   268  
   269  func NewSimpleVariable(key string) (*SimpleVariable, error) {
   270  	return &SimpleVariable{key}, nil
   271  }
   272  
   273  func (v *SimpleVariable) FullKey() string {
   274  	return v.Key
   275  }
   276  
   277  func (v *SimpleVariable) GoString() string {
   278  	return fmt.Sprintf("*%#v", *v)
   279  }
   280  
   281  func NewUserVariable(key string) (*UserVariable, error) {
   282  	name := key[len("var."):]
   283  	elem := ""
   284  	if idx := strings.Index(name, "."); idx > -1 {
   285  		elem = name[idx+1:]
   286  		name = name[:idx]
   287  	}
   288  
   289  	if len(elem) > 0 {
   290  		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)
   291  	}
   292  
   293  	return &UserVariable{
   294  		key: key,
   295  
   296  		Name: name,
   297  		Elem: elem,
   298  	}, nil
   299  }
   300  
   301  func (v *UserVariable) FullKey() string {
   302  	return v.key
   303  }
   304  
   305  func (v *UserVariable) GoString() string {
   306  	return fmt.Sprintf("*%#v", *v)
   307  }
   308  
   309  // DetectVariables takes an AST root and returns all the interpolated
   310  // variables that are detected in the AST tree.
   311  func DetectVariables(root ast.Node) ([]InterpolatedVariable, error) {
   312  	var result []InterpolatedVariable
   313  	var resultErr error
   314  
   315  	// Visitor callback
   316  	fn := func(n ast.Node) ast.Node {
   317  		if resultErr != nil {
   318  			return n
   319  		}
   320  
   321  		switch vn := n.(type) {
   322  		case *ast.VariableAccess:
   323  			v, err := NewInterpolatedVariable(vn.Name)
   324  			if err != nil {
   325  				resultErr = err
   326  				return n
   327  			}
   328  			result = append(result, v)
   329  		case *ast.Index:
   330  			if va, ok := vn.Target.(*ast.VariableAccess); ok {
   331  				v, err := NewInterpolatedVariable(va.Name)
   332  				if err != nil {
   333  					resultErr = err
   334  					return n
   335  				}
   336  				result = append(result, v)
   337  			}
   338  			if va, ok := vn.Key.(*ast.VariableAccess); ok {
   339  				v, err := NewInterpolatedVariable(va.Name)
   340  				if err != nil {
   341  					resultErr = err
   342  					return n
   343  				}
   344  				result = append(result, v)
   345  			}
   346  		default:
   347  			return n
   348  		}
   349  
   350  		return n
   351  	}
   352  
   353  	// Visitor pattern
   354  	root.Accept(fn)
   355  
   356  	if resultErr != nil {
   357  		return nil, resultErr
   358  	}
   359  
   360  	return result, nil
   361  }