2017-06-06 15:58:56 +00:00
package config
import (
"fmt"
"strconv"
"strings"
2018-01-10 18:52:15 +00:00
"github.com/hashicorp/terraform/tfdiags"
2017-06-06 15:58:56 +00:00
"github.com/hashicorp/hil/ast"
)
// An InterpolatedVariable is a variable reference within an interpolation.
//
// Implementations of this interface represents various sources where
// variables can come from: user variables, resources, etc.
type InterpolatedVariable interface {
FullKey ( ) string
2018-01-10 18:52:15 +00:00
SourceRange ( ) tfdiags . SourceRange
}
// varRange can be embedded into an InterpolatedVariable implementation to
// implement the SourceRange method.
type varRange struct {
rng tfdiags . SourceRange
}
func ( r varRange ) SourceRange ( ) tfdiags . SourceRange {
return r . rng
}
func makeVarRange ( rng tfdiags . SourceRange ) varRange {
return varRange { rng }
2017-06-06 15:58:56 +00:00
}
// CountVariable is a variable for referencing information about
// the count.
type CountVariable struct {
Type CountValueType
key string
2018-01-10 18:52:15 +00:00
varRange
2017-06-06 15:58:56 +00:00
}
// CountValueType is the type of the count variable that is referenced.
type CountValueType byte
const (
CountValueInvalid CountValueType = iota
CountValueIndex
)
// A ModuleVariable is a variable that is referencing the output
// of a module, such as "${module.foo.bar}"
type ModuleVariable struct {
Name string
Field string
key string
2018-01-10 18:52:15 +00:00
varRange
2017-06-06 15:58:56 +00:00
}
// A PathVariable is a variable that references path information about the
// module.
type PathVariable struct {
Type PathValueType
key string
2018-01-10 18:52:15 +00:00
varRange
2017-06-06 15:58:56 +00:00
}
type PathValueType byte
const (
PathValueInvalid PathValueType = iota
PathValueCwd
PathValueModule
PathValueRoot
)
// A ResourceVariable is a variable that is referencing the field
// of a resource, such as "${aws_instance.foo.ami}"
type ResourceVariable struct {
Mode ResourceMode
Type string // Resource type, i.e. "aws_instance"
Name string // Resource name
Field string // Resource field
Multi bool // True if multi-variable: aws_instance.foo.*.id
Index int // Index for multi-variable: aws_instance.foo.1.id == 1
key string
2018-01-10 18:52:15 +00:00
varRange
2017-06-06 15:58:56 +00:00
}
// SelfVariable is a variable that is referencing the same resource
// it is running on: "${self.address}"
type SelfVariable struct {
Field string
key string
2018-01-10 18:52:15 +00:00
varRange
2017-06-06 15:58:56 +00:00
}
// SimpleVariable is an unprefixed variable, which can show up when users have
// strings they are passing down to resources that use interpolation
// internally. The template_file resource is an example of this.
type SimpleVariable struct {
Key string
2018-01-10 18:52:15 +00:00
varRange
2017-06-06 15:58:56 +00:00
}
// TerraformVariable is a "terraform."-prefixed variable used to access
// metadata about the Terraform run.
type TerraformVariable struct {
Field string
key string
2018-01-10 18:52:15 +00:00
varRange
2017-06-06 15:58:56 +00:00
}
// A UserVariable is a variable that is referencing a user variable
// that is inputted from outside the configuration. This looks like
// "${var.foo}"
type UserVariable struct {
Name string
Elem string
key string
2018-01-10 18:52:15 +00:00
varRange
}
// A LocalVariable is a variable that references a local value defined within
// the current module, via a "locals" block. This looks like "${local.foo}".
type LocalVariable struct {
Name string
varRange
2017-06-06 15:58:56 +00:00
}
func NewInterpolatedVariable ( v string ) ( InterpolatedVariable , error ) {
if strings . HasPrefix ( v , "count." ) {
return NewCountVariable ( v )
} else if strings . HasPrefix ( v , "path." ) {
return NewPathVariable ( v )
} else if strings . HasPrefix ( v , "self." ) {
return NewSelfVariable ( v )
} else if strings . HasPrefix ( v , "terraform." ) {
return NewTerraformVariable ( v )
} else if strings . HasPrefix ( v , "var." ) {
return NewUserVariable ( v )
2018-01-10 18:52:15 +00:00
} else if strings . HasPrefix ( v , "local." ) {
return NewLocalVariable ( v )
2017-06-06 15:58:56 +00:00
} else if strings . HasPrefix ( v , "module." ) {
return NewModuleVariable ( v )
} else if ! strings . ContainsRune ( v , '.' ) {
return NewSimpleVariable ( v )
} else {
return NewResourceVariable ( v )
}
}
func NewCountVariable ( key string ) ( * CountVariable , error ) {
var fieldType CountValueType
parts := strings . SplitN ( key , "." , 2 )
switch parts [ 1 ] {
case "index" :
fieldType = CountValueIndex
}
return & CountVariable {
Type : fieldType ,
key : key ,
} , nil
}
func ( c * CountVariable ) FullKey ( ) string {
return c . key
}
func NewModuleVariable ( key string ) ( * ModuleVariable , error ) {
parts := strings . SplitN ( key , "." , 3 )
if len ( parts ) < 3 {
return nil , fmt . Errorf (
"%s: module variables must be three parts: module.name.attr" ,
key )
}
return & ModuleVariable {
Name : parts [ 1 ] ,
Field : parts [ 2 ] ,
key : key ,
} , nil
}
func ( v * ModuleVariable ) FullKey ( ) string {
return v . key
}
func ( v * ModuleVariable ) GoString ( ) string {
return fmt . Sprintf ( "*%#v" , * v )
}
func NewPathVariable ( key string ) ( * PathVariable , error ) {
var fieldType PathValueType
parts := strings . SplitN ( key , "." , 2 )
switch parts [ 1 ] {
case "cwd" :
fieldType = PathValueCwd
case "module" :
fieldType = PathValueModule
case "root" :
fieldType = PathValueRoot
}
return & PathVariable {
Type : fieldType ,
key : key ,
} , nil
}
func ( v * PathVariable ) FullKey ( ) string {
return v . key
}
func NewResourceVariable ( key string ) ( * ResourceVariable , error ) {
var mode ResourceMode
var parts [ ] string
if strings . HasPrefix ( key , "data." ) {
mode = DataResourceMode
parts = strings . SplitN ( key , "." , 4 )
if len ( parts ) < 4 {
return nil , fmt . Errorf (
"%s: data variables must be four parts: data.TYPE.NAME.ATTR" ,
key )
}
// Don't actually need the "data." prefix for parsing, since it's
// always constant.
parts = parts [ 1 : ]
} else {
mode = ManagedResourceMode
parts = strings . SplitN ( key , "." , 3 )
if len ( parts ) < 3 {
return nil , fmt . Errorf (
"%s: resource variables must be three parts: TYPE.NAME.ATTR" ,
key )
}
}
field := parts [ 2 ]
multi := false
var index int
if idx := strings . Index ( field , "." ) ; idx != - 1 {
indexStr := field [ : idx ]
multi = indexStr == "*"
index = - 1
if ! multi {
indexInt , err := strconv . ParseInt ( indexStr , 0 , 0 )
if err == nil {
multi = true
index = int ( indexInt )
}
}
if multi {
field = field [ idx + 1 : ]
}
}
return & ResourceVariable {
Mode : mode ,
Type : parts [ 0 ] ,
Name : parts [ 1 ] ,
Field : field ,
Multi : multi ,
Index : index ,
key : key ,
} , nil
}
func ( v * ResourceVariable ) ResourceId ( ) string {
switch v . Mode {
case ManagedResourceMode :
return fmt . Sprintf ( "%s.%s" , v . Type , v . Name )
case DataResourceMode :
return fmt . Sprintf ( "data.%s.%s" , v . Type , v . Name )
default :
panic ( fmt . Errorf ( "unknown resource mode %s" , v . Mode ) )
}
}
func ( v * ResourceVariable ) FullKey ( ) string {
return v . key
}
func NewSelfVariable ( key string ) ( * SelfVariable , error ) {
field := key [ len ( "self." ) : ]
return & SelfVariable {
Field : field ,
key : key ,
} , nil
}
func ( v * SelfVariable ) FullKey ( ) string {
return v . key
}
func ( v * SelfVariable ) GoString ( ) string {
return fmt . Sprintf ( "*%#v" , * v )
}
func NewSimpleVariable ( key string ) ( * SimpleVariable , error ) {
2018-01-10 18:52:15 +00:00
return & SimpleVariable { Key : key } , nil
2017-06-06 15:58:56 +00:00
}
func ( v * SimpleVariable ) FullKey ( ) string {
return v . Key
}
func ( v * SimpleVariable ) GoString ( ) string {
return fmt . Sprintf ( "*%#v" , * v )
}
func NewTerraformVariable ( key string ) ( * TerraformVariable , error ) {
field := key [ len ( "terraform." ) : ]
return & TerraformVariable {
Field : field ,
key : key ,
} , nil
}
func ( v * TerraformVariable ) FullKey ( ) string {
return v . key
}
func ( v * TerraformVariable ) GoString ( ) string {
return fmt . Sprintf ( "*%#v" , * v )
}
func NewUserVariable ( key string ) ( * UserVariable , error ) {
name := key [ len ( "var." ) : ]
elem := ""
if idx := strings . Index ( name , "." ) ; idx > - 1 {
elem = name [ idx + 1 : ]
name = name [ : idx ]
}
if len ( elem ) > 0 {
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 )
}
return & UserVariable {
key : key ,
Name : name ,
Elem : elem ,
} , nil
}
func ( v * UserVariable ) FullKey ( ) string {
return v . key
}
func ( v * UserVariable ) GoString ( ) string {
return fmt . Sprintf ( "*%#v" , * v )
}
2018-01-10 18:52:15 +00:00
func NewLocalVariable ( key string ) ( * LocalVariable , error ) {
name := key [ len ( "local." ) : ]
if idx := strings . Index ( name , "." ) ; idx > - 1 {
return nil , fmt . Errorf ( "Can't use dot (.) attribute access in local.%s; use square bracket indexing" , name )
}
return & LocalVariable {
Name : name ,
} , nil
}
func ( v * LocalVariable ) FullKey ( ) string {
return fmt . Sprintf ( "local.%s" , v . Name )
}
func ( v * LocalVariable ) GoString ( ) string {
return fmt . Sprintf ( "*%#v" , * v )
}
2017-06-06 15:58:56 +00:00
// DetectVariables takes an AST root and returns all the interpolated
// variables that are detected in the AST tree.
func DetectVariables ( root ast . Node ) ( [ ] InterpolatedVariable , error ) {
var result [ ] InterpolatedVariable
var resultErr error
// Visitor callback
fn := func ( n ast . Node ) ast . Node {
if resultErr != nil {
return n
}
switch vn := n . ( type ) {
case * ast . VariableAccess :
v , err := NewInterpolatedVariable ( vn . Name )
if err != nil {
resultErr = err
return n
}
result = append ( result , v )
case * ast . Index :
if va , ok := vn . Target . ( * ast . VariableAccess ) ; ok {
v , err := NewInterpolatedVariable ( va . Name )
if err != nil {
resultErr = err
return n
}
result = append ( result , v )
}
if va , ok := vn . Key . ( * ast . VariableAccess ) ; ok {
v , err := NewInterpolatedVariable ( va . Name )
if err != nil {
resultErr = err
return n
}
result = append ( result , v )
}
default :
return n
}
return n
}
// Visitor pattern
root . Accept ( fn )
if resultErr != nil {
return nil , resultErr
}
return result , nil
}