8 "github.com/hashicorp/terraform/tfdiags"
10 "github.com/hashicorp/hil/ast"
13 // An InterpolatedVariable is a variable reference within an interpolation.
15 // Implementations of this interface represents various sources where
16 // variables can come from: user variables, resources, etc.
17 type InterpolatedVariable interface {
19 SourceRange() tfdiags.SourceRange
22 // varRange can be embedded into an InterpolatedVariable implementation to
23 // implement the SourceRange method.
24 type varRange struct {
25 rng tfdiags.SourceRange
28 func (r varRange) SourceRange() tfdiags.SourceRange {
32 func makeVarRange(rng tfdiags.SourceRange) varRange {
36 // CountVariable is a variable for referencing information about
38 type CountVariable struct {
44 // CountValueType is the type of the count variable that is referenced.
45 type CountValueType byte
48 CountValueInvalid CountValueType = iota
52 // A ModuleVariable is a variable that is referencing the output
53 // of a module, such as "${module.foo.bar}"
54 type ModuleVariable struct {
61 // A PathVariable is a variable that references path information about the
63 type PathVariable struct {
69 type PathValueType byte
72 PathValueInvalid PathValueType = iota
78 // A ResourceVariable is a variable that is referencing the field
79 // of a resource, such as "${aws_instance.foo.ami}"
80 type ResourceVariable struct {
82 Type string // Resource type, i.e. "aws_instance"
83 Name string // Resource name
84 Field string // Resource field
86 Multi bool // True if multi-variable: aws_instance.foo.*.id
87 Index int // Index for multi-variable: aws_instance.foo.1.id == 1
93 // SelfVariable is a variable that is referencing the same resource
94 // it is running on: "${self.address}"
95 type SelfVariable struct {
102 // SimpleVariable is an unprefixed variable, which can show up when users have
103 // strings they are passing down to resources that use interpolation
104 // internally. The template_file resource is an example of this.
105 type SimpleVariable struct {
110 // TerraformVariable is a "terraform."-prefixed variable used to access
111 // metadata about the Terraform run.
112 type TerraformVariable struct {
118 // A UserVariable is a variable that is referencing a user variable
119 // that is inputted from outside the configuration. This looks like
121 type UserVariable struct {
129 // A LocalVariable is a variable that references a local value defined within
130 // the current module, via a "locals" block. This looks like "${local.foo}".
131 type LocalVariable struct {
136 func NewInterpolatedVariable(v string) (InterpolatedVariable, error) {
137 if strings.HasPrefix(v, "count.") {
138 return NewCountVariable(v)
139 } else if strings.HasPrefix(v, "path.") {
140 return NewPathVariable(v)
141 } else if strings.HasPrefix(v, "self.") {
142 return NewSelfVariable(v)
143 } else if strings.HasPrefix(v, "terraform.") {
144 return NewTerraformVariable(v)
145 } else if strings.HasPrefix(v, "var.") {
146 return NewUserVariable(v)
147 } else if strings.HasPrefix(v, "local.") {
148 return NewLocalVariable(v)
149 } else if strings.HasPrefix(v, "module.") {
150 return NewModuleVariable(v)
151 } else if !strings.ContainsRune(v, '.') {
152 return NewSimpleVariable(v)
154 return NewResourceVariable(v)
158 func NewCountVariable(key string) (*CountVariable, error) {
159 var fieldType CountValueType
160 parts := strings.SplitN(key, ".", 2)
163 fieldType = CountValueIndex
166 return &CountVariable{
172 func (c *CountVariable) FullKey() string {
176 func NewModuleVariable(key string) (*ModuleVariable, error) {
177 parts := strings.SplitN(key, ".", 3)
179 return nil, fmt.Errorf(
180 "%s: module variables must be three parts: module.name.attr",
184 return &ModuleVariable{
191 func (v *ModuleVariable) FullKey() string {
195 func (v *ModuleVariable) GoString() string {
196 return fmt.Sprintf("*%#v", *v)
199 func NewPathVariable(key string) (*PathVariable, error) {
200 var fieldType PathValueType
201 parts := strings.SplitN(key, ".", 2)
204 fieldType = PathValueCwd
206 fieldType = PathValueModule
208 fieldType = PathValueRoot
211 return &PathVariable{
217 func (v *PathVariable) FullKey() string {
221 func NewResourceVariable(key string) (*ResourceVariable, error) {
222 var mode ResourceMode
224 if strings.HasPrefix(key, "data.") {
225 mode = DataResourceMode
226 parts = strings.SplitN(key, ".", 4)
228 return nil, fmt.Errorf(
229 "%s: data variables must be four parts: data.TYPE.NAME.ATTR",
233 // Don't actually need the "data." prefix for parsing, since it's
237 mode = ManagedResourceMode
238 parts = strings.SplitN(key, ".", 3)
240 return nil, fmt.Errorf(
241 "%s: resource variables must be three parts: TYPE.NAME.ATTR",
250 if idx := strings.Index(field, "."); idx != -1 {
251 indexStr := field[:idx]
252 multi = indexStr == "*"
256 indexInt, err := strconv.ParseInt(indexStr, 0, 0)
259 index = int(indexInt)
264 field = field[idx+1:]
268 return &ResourceVariable{
279 func (v *ResourceVariable) ResourceId() string {
281 case ManagedResourceMode:
282 return fmt.Sprintf("%s.%s", v.Type, v.Name)
283 case DataResourceMode:
284 return fmt.Sprintf("data.%s.%s", v.Type, v.Name)
286 panic(fmt.Errorf("unknown resource mode %s", v.Mode))
290 func (v *ResourceVariable) FullKey() string {
294 func NewSelfVariable(key string) (*SelfVariable, error) {
295 field := key[len("self."):]
297 return &SelfVariable{
304 func (v *SelfVariable) FullKey() string {
308 func (v *SelfVariable) GoString() string {
309 return fmt.Sprintf("*%#v", *v)
312 func NewSimpleVariable(key string) (*SimpleVariable, error) {
313 return &SimpleVariable{Key: key}, nil
316 func (v *SimpleVariable) FullKey() string {
320 func (v *SimpleVariable) GoString() string {
321 return fmt.Sprintf("*%#v", *v)
324 func NewTerraformVariable(key string) (*TerraformVariable, error) {
325 field := key[len("terraform."):]
326 return &TerraformVariable{
332 func (v *TerraformVariable) FullKey() string {
336 func (v *TerraformVariable) GoString() string {
337 return fmt.Sprintf("*%#v", *v)
340 func NewUserVariable(key string) (*UserVariable, error) {
341 name := key[len("var."):]
343 if idx := strings.Index(name, "."); idx > -1 {
349 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)
352 return &UserVariable{
360 func (v *UserVariable) FullKey() string {
364 func (v *UserVariable) GoString() string {
365 return fmt.Sprintf("*%#v", *v)
368 func NewLocalVariable(key string) (*LocalVariable, error) {
369 name := key[len("local."):]
370 if idx := strings.Index(name, "."); idx > -1 {
371 return nil, fmt.Errorf("Can't use dot (.) attribute access in local.%s; use square bracket indexing", name)
374 return &LocalVariable{
379 func (v *LocalVariable) FullKey() string {
380 return fmt.Sprintf("local.%s", v.Name)
383 func (v *LocalVariable) GoString() string {
384 return fmt.Sprintf("*%#v", *v)
387 // DetectVariables takes an AST root and returns all the interpolated
388 // variables that are detected in the AST tree.
389 func DetectVariables(root ast.Node) ([]InterpolatedVariable, error) {
390 var result []InterpolatedVariable
394 fn := func(n ast.Node) ast.Node {
395 if resultErr != nil {
399 switch vn := n.(type) {
400 case *ast.VariableAccess:
401 v, err := NewInterpolatedVariable(vn.Name)
406 result = append(result, v)
408 if va, ok := vn.Target.(*ast.VariableAccess); ok {
409 v, err := NewInterpolatedVariable(va.Name)
414 result = append(result, v)
416 if va, ok := vn.Key.(*ast.VariableAccess); ok {
417 v, err := NewInterpolatedVariable(va.Name)
422 result = append(result, v)
434 if resultErr != nil {
435 return nil, resultErr