8 "github.com/hashicorp/hil/ast"
11 // An InterpolatedVariable is a variable reference within an interpolation.
13 // Implementations of this interface represents various sources where
14 // variables can come from: user variables, resources, etc.
15 type InterpolatedVariable interface {
19 // CountVariable is a variable for referencing information about
21 type CountVariable struct {
26 // CountValueType is the type of the count variable that is referenced.
27 type CountValueType byte
30 CountValueInvalid CountValueType = iota
34 // A ModuleVariable is a variable that is referencing the output
35 // of a module, such as "${module.foo.bar}"
36 type ModuleVariable struct {
42 // A PathVariable is a variable that references path information about the
44 type PathVariable struct {
49 type PathValueType byte
52 PathValueInvalid PathValueType = iota
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 {
62 Type string // Resource type, i.e. "aws_instance"
63 Name string // Resource name
64 Field string // Resource field
66 Multi bool // True if multi-variable: aws_instance.foo.*.id
67 Index int // Index for multi-variable: aws_instance.foo.1.id == 1
72 // SelfVariable is a variable that is referencing the same resource
73 // it is running on: "${self.address}"
74 type SelfVariable struct {
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 {
87 // TerraformVariable is a "terraform."-prefixed variable used to access
88 // metadata about the Terraform run.
89 type TerraformVariable struct {
94 // A UserVariable is a variable that is referencing a user variable
95 // that is inputted from outside the configuration. This looks like
97 type UserVariable struct {
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)
120 return NewResourceVariable(v)
124 func NewCountVariable(key string) (*CountVariable, error) {
125 var fieldType CountValueType
126 parts := strings.SplitN(key, ".", 2)
129 fieldType = CountValueIndex
132 return &CountVariable{
138 func (c *CountVariable) FullKey() string {
142 func NewModuleVariable(key string) (*ModuleVariable, error) {
143 parts := strings.SplitN(key, ".", 3)
145 return nil, fmt.Errorf(
146 "%s: module variables must be three parts: module.name.attr",
150 return &ModuleVariable{
157 func (v *ModuleVariable) FullKey() string {
161 func (v *ModuleVariable) GoString() string {
162 return fmt.Sprintf("*%#v", *v)
165 func NewPathVariable(key string) (*PathVariable, error) {
166 var fieldType PathValueType
167 parts := strings.SplitN(key, ".", 2)
170 fieldType = PathValueCwd
172 fieldType = PathValueModule
174 fieldType = PathValueRoot
177 return &PathVariable{
183 func (v *PathVariable) FullKey() string {
187 func NewResourceVariable(key string) (*ResourceVariable, error) {
188 var mode ResourceMode
190 if strings.HasPrefix(key, "data.") {
191 mode = DataResourceMode
192 parts = strings.SplitN(key, ".", 4)
194 return nil, fmt.Errorf(
195 "%s: data variables must be four parts: data.TYPE.NAME.ATTR",
199 // Don't actually need the "data." prefix for parsing, since it's
203 mode = ManagedResourceMode
204 parts = strings.SplitN(key, ".", 3)
206 return nil, fmt.Errorf(
207 "%s: resource variables must be three parts: TYPE.NAME.ATTR",
216 if idx := strings.Index(field, "."); idx != -1 {
217 indexStr := field[:idx]
218 multi = indexStr == "*"
222 indexInt, err := strconv.ParseInt(indexStr, 0, 0)
225 index = int(indexInt)
230 field = field[idx+1:]
234 return &ResourceVariable{
245 func (v *ResourceVariable) ResourceId() string {
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)
252 panic(fmt.Errorf("unknown resource mode %s", v.Mode))
256 func (v *ResourceVariable) FullKey() string {
260 func NewSelfVariable(key string) (*SelfVariable, error) {
261 field := key[len("self."):]
263 return &SelfVariable{
270 func (v *SelfVariable) FullKey() string {
274 func (v *SelfVariable) GoString() string {
275 return fmt.Sprintf("*%#v", *v)
278 func NewSimpleVariable(key string) (*SimpleVariable, error) {
279 return &SimpleVariable{key}, nil
282 func (v *SimpleVariable) FullKey() string {
286 func (v *SimpleVariable) GoString() string {
287 return fmt.Sprintf("*%#v", *v)
290 func NewTerraformVariable(key string) (*TerraformVariable, error) {
291 field := key[len("terraform."):]
292 return &TerraformVariable{
298 func (v *TerraformVariable) FullKey() string {
302 func (v *TerraformVariable) GoString() string {
303 return fmt.Sprintf("*%#v", *v)
306 func NewUserVariable(key string) (*UserVariable, error) {
307 name := key[len("var."):]
309 if idx := strings.Index(name, "."); idx > -1 {
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)
318 return &UserVariable{
326 func (v *UserVariable) FullKey() string {
330 func (v *UserVariable) GoString() string {
331 return fmt.Sprintf("*%#v", *v)
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
341 fn := func(n ast.Node) ast.Node {
342 if resultErr != nil {
346 switch vn := n.(type) {
347 case *ast.VariableAccess:
348 v, err := NewInterpolatedVariable(vn.Name)
353 result = append(result, v)
355 if va, ok := vn.Target.(*ast.VariableAccess); ok {
356 v, err := NewInterpolatedVariable(va.Name)
361 result = append(result, v)
363 if va, ok := vn.Key.(*ast.VariableAccess); ok {
364 v, err := NewInterpolatedVariable(va.Name)
369 result = append(result, v)
381 if resultErr != nil {
382 return nil, resultErr