2014-07-21 19:39:55 +02:00
package config
import (
"fmt"
"strconv"
"strings"
2015-01-12 21:09:30 +01:00
2017-10-11 20:46:52 +02:00
"github.com/hashicorp/terraform/tfdiags"
2016-01-31 08:38:37 +01:00
"github.com/hashicorp/hil/ast"
2014-07-21 19:39:55 +02:00
)
// 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
2017-10-11 20:46:52 +02: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
}
2014-10-03 03:20:55 +02:00
// CountVariable is a variable for referencing information about
// the count.
type CountVariable struct {
Type CountValueType
key string
2017-10-11 20:46:52 +02:00
varRange
2014-10-03 03:20:55 +02:00
}
// CountValueType is the type of the count variable that is referenced.
type CountValueType byte
const (
CountValueInvalid CountValueType = iota
CountValueIndex
)
2014-09-15 20:40:25 +02:00
// 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
2017-10-11 20:46:52 +02:00
varRange
2014-09-15 20:40:25 +02:00
}
2014-10-08 03:03:11 +02:00
// A PathVariable is a variable that references path information about the
// module.
type PathVariable struct {
Type PathValueType
key string
2017-10-11 20:46:52 +02:00
varRange
2014-10-08 03:03:11 +02:00
}
type PathValueType byte
const (
PathValueInvalid PathValueType = iota
2014-10-08 05:09:30 +02:00
PathValueCwd
2014-10-08 03:03:11 +02:00
PathValueModule
2014-10-08 05:09:30 +02:00
PathValueRoot
2014-10-08 03:03:11 +02:00
)
2014-07-21 19:39:55 +02:00
// A ResourceVariable is a variable that is referencing the field
// of a resource, such as "${aws_instance.foo.ami}"
type ResourceVariable struct {
2016-05-02 00:53:42 +02:00
Mode ResourceMode
2014-07-21 19:39:55 +02:00
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
2017-10-11 20:46:52 +02:00
varRange
2014-07-21 19:39:55 +02:00
}
2015-02-23 23:34:25 +01:00
// SelfVariable is a variable that is referencing the same resource
// it is running on: "${self.address}"
type SelfVariable struct {
Field string
key string
2017-10-11 20:46:52 +02:00
varRange
2015-02-23 23:34:25 +01:00
}
2015-11-13 18:07:02 +01: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
2017-10-11 20:46:52 +02:00
varRange
2015-11-13 18:07:02 +01:00
}
2017-03-14 00:09:06 +01:00
// TerraformVariable is a "terraform."-prefixed variable used to access
// metadata about the Terraform run.
type TerraformVariable struct {
Field string
key string
2017-10-11 20:46:52 +02:00
varRange
2017-03-14 00:09:06 +01:00
}
2014-07-21 20:24:44 +02: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
2017-10-11 20:46:52 +02:00
varRange
2014-07-21 20:24:44 +02:00
}
2017-07-01 18:23:42 +02:00
// 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
2017-10-11 20:46:52 +02:00
varRange
2017-07-01 18:23:42 +02:00
}
2014-07-21 20:24:44 +02:00
func NewInterpolatedVariable ( v string ) ( InterpolatedVariable , error ) {
2014-10-03 03:20:55 +02:00
if strings . HasPrefix ( v , "count." ) {
return NewCountVariable ( v )
2014-10-08 03:03:11 +02:00
} else if strings . HasPrefix ( v , "path." ) {
return NewPathVariable ( v )
2015-02-23 23:34:25 +01:00
} else if strings . HasPrefix ( v , "self." ) {
return NewSelfVariable ( v )
2017-03-14 00:09:06 +01:00
} else if strings . HasPrefix ( v , "terraform." ) {
return NewTerraformVariable ( v )
2014-10-03 03:20:55 +02:00
} else if strings . HasPrefix ( v , "var." ) {
2014-09-15 20:40:25 +02:00
return NewUserVariable ( v )
2017-07-01 18:23:42 +02:00
} else if strings . HasPrefix ( v , "local." ) {
return NewLocalVariable ( v )
2014-09-15 20:40:25 +02:00
} else if strings . HasPrefix ( v , "module." ) {
return NewModuleVariable ( v )
2015-11-13 18:07:02 +01:00
} else if ! strings . ContainsRune ( v , '.' ) {
return NewSimpleVariable ( v )
2014-09-15 20:40:25 +02:00
} else {
2014-07-21 20:24:44 +02:00
return NewResourceVariable ( v )
}
}
2014-10-03 03:20:55 +02:00
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
}
2014-09-15 20:40:25 +02:00
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
}
2016-10-27 05:24:23 +02:00
func ( v * ModuleVariable ) GoString ( ) string {
return fmt . Sprintf ( "*%#v" , * v )
}
2014-10-08 03:03:11 +02:00
func NewPathVariable ( key string ) ( * PathVariable , error ) {
var fieldType PathValueType
parts := strings . SplitN ( key , "." , 2 )
switch parts [ 1 ] {
2014-10-08 05:09:30 +02:00
case "cwd" :
fieldType = PathValueCwd
2014-10-08 03:03:11 +02:00
case "module" :
fieldType = PathValueModule
2014-10-08 05:09:30 +02:00
case "root" :
fieldType = PathValueRoot
2014-10-08 03:03:11 +02:00
}
return & PathVariable {
Type : fieldType ,
key : key ,
} , nil
}
func ( v * PathVariable ) FullKey ( ) string {
return v . key
}
2014-07-21 19:39:55 +02:00
func NewResourceVariable ( key string ) ( * ResourceVariable , error ) {
2016-05-02 00:53:42 +02:00
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 )
}
2014-07-22 15:43:04 +02:00
}
2014-07-21 19:39:55 +02:00
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 {
2016-05-02 00:53:42 +02:00
Mode : mode ,
2014-07-21 19:39:55 +02:00
Type : parts [ 0 ] ,
Name : parts [ 1 ] ,
Field : field ,
Multi : multi ,
Index : index ,
key : key ,
} , nil
}
func ( v * ResourceVariable ) ResourceId ( ) string {
2016-05-02 00:53:42 +02:00
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 ) )
}
2014-07-21 19:39:55 +02:00
}
func ( v * ResourceVariable ) FullKey ( ) string {
return v . key
}
2015-02-23 23:34:25 +01:00
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 )
}
2015-11-13 18:07:02 +01:00
func NewSimpleVariable ( key string ) ( * SimpleVariable , error ) {
2017-10-11 20:46:52 +02:00
return & SimpleVariable { Key : key } , nil
2015-11-13 18:07:02 +01:00
}
func ( v * SimpleVariable ) FullKey ( ) string {
return v . Key
}
func ( v * SimpleVariable ) GoString ( ) string {
return fmt . Sprintf ( "*%#v" , * v )
}
2017-03-14 00:09:06 +01:00
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 )
}
2014-07-21 19:39:55 +02:00
func NewUserVariable ( key string ) ( * UserVariable , error ) {
name := key [ len ( " var . " ) : ]
2014-07-22 17:51:50 +02:00
elem := ""
if idx := strings . Index ( name , "." ) ; idx > - 1 {
elem = name [ idx + 1 : ]
name = name [ : idx ]
}
2016-06-12 17:30:45 +02:00
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 )
}
2014-07-21 19:39:55 +02:00
return & UserVariable {
2014-07-22 17:51:50 +02:00
key : key ,
2014-07-21 19:39:55 +02:00
Name : name ,
2014-07-22 17:51:50 +02:00
Elem : elem ,
2014-07-21 19:39:55 +02:00
} , nil
}
func ( v * UserVariable ) FullKey ( ) string {
return v . key
}
2014-07-21 21:56:03 +02:00
func ( v * UserVariable ) GoString ( ) string {
return fmt . Sprintf ( "*%#v" , * v )
}
2015-01-12 21:09:30 +01:00
2017-07-01 18:23:42 +02: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 )
}
2015-01-12 21:09:30 +01: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
2015-01-15 07:01:42 +01:00
fn := func ( n ast . Node ) ast . Node {
2015-01-12 21:09:30 +01:00
if resultErr != nil {
2015-01-15 07:01:42 +01:00
return n
2015-01-12 21:09:30 +01:00
}
core: support native list variables in config
This commit adds support for native list variables and outputs, building
up on the previous change to state. Interpolation functions now return
native lists in preference to StringList.
List variables are defined like this:
variable "test" {
# This can also be inferred
type = "list"
default = ["Hello", "World"]
}
output "test_out" {
value = "${var.a_list}"
}
This results in the following state:
```
...
"outputs": {
"test_out": [
"hello",
"world"
]
},
...
```
And the result of terraform output is as follows:
```
$ terraform output
test_out = [
hello
world
]
```
Using the output name, an xargs-friendly representation is output:
```
$ terraform output test_out
hello
world
```
The output command also supports indexing into the list (with
appropriate range checking and no wrapping):
```
$ terraform output test_out 1
world
```
Along with maps, list outputs from one module may be passed as variables
into another, removing the need for the `join(",", var.list_as_string)`
and `split(",", var.list_as_string)` which was previously necessary in
Terraform configuration.
This commit also updates the tests and implementations of built-in
interpolation functions to take and return native lists where
appropriate.
A backwards compatibility note: previously the concat interpolation
function was capable of concatenating either strings or lists. The
strings use case was deprectated a long time ago but still remained.
Because we cannot return `ast.TypeAny` from an interpolation function,
this use case is no longer supported for strings - `concat` is only
capable of concatenating lists. This should not be a huge issue - the
type checker picks up incorrect parameters, and the native HIL string
concatenation - or the `join` function - can be used to replicate the
missing behaviour.
2016-04-22 02:03:24 +02:00
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 :
2015-01-15 07:01:42 +01:00
return n
2015-01-12 21:09:30 +01:00
}
2015-01-15 07:01:42 +01:00
return n
2015-01-12 21:09:30 +01:00
}
// Visitor pattern
root . Accept ( fn )
if resultErr != nil {
return nil , resultErr
}
return result , nil
}