2015-02-11 19:01:08 -06:00
|
|
|
package terraform
|
|
|
|
|
|
|
|
import (
|
2015-02-20 11:40:41 -06:00
|
|
|
"fmt"
|
2016-06-22 15:52:12 -05:00
|
|
|
"reflect"
|
2016-04-15 14:07:54 -05:00
|
|
|
"strings"
|
2015-02-20 11:40:41 -06:00
|
|
|
|
2015-02-11 19:01:08 -06:00
|
|
|
"github.com/hashicorp/terraform/config"
|
2016-04-15 14:07:54 -05:00
|
|
|
"github.com/hashicorp/terraform/config/module"
|
2016-06-03 15:57:04 -05:00
|
|
|
"github.com/hashicorp/terraform/helper/hilmapstructure"
|
2015-02-11 19:01:08 -06:00
|
|
|
)
|
|
|
|
|
2016-04-15 14:07:54 -05:00
|
|
|
// EvalTypeCheckVariable is an EvalNode which ensures that the variable
|
|
|
|
// values which are assigned as inputs to a module (including the root)
|
|
|
|
// match the types which are either declared for the variables explicitly
|
|
|
|
// or inferred from the default values.
|
|
|
|
//
|
|
|
|
// In order to achieve this three things are required:
|
|
|
|
// - a map of the proposed variable values
|
|
|
|
// - the configuration tree of the module in which the variable is
|
|
|
|
// declared
|
|
|
|
// - the path to the module (so we know which part of the tree to
|
|
|
|
// compare the values against).
|
|
|
|
//
|
|
|
|
// Currently since the type system is simple, we currently do not make
|
|
|
|
// use of the values since it is only valid to pass string values. The
|
|
|
|
// structure is in place for extension of the type system, however.
|
|
|
|
type EvalTypeCheckVariable struct {
|
2016-04-11 12:40:06 -05:00
|
|
|
Variables map[string]interface{}
|
2016-04-15 14:07:54 -05:00
|
|
|
ModulePath []string
|
|
|
|
ModuleTree *module.Tree
|
|
|
|
}
|
|
|
|
|
|
|
|
func (n *EvalTypeCheckVariable) Eval(ctx EvalContext) (interface{}, error) {
|
|
|
|
currentTree := n.ModuleTree
|
|
|
|
for _, pathComponent := range n.ModulePath[1:] {
|
|
|
|
currentTree = currentTree.Children()[pathComponent]
|
|
|
|
}
|
|
|
|
targetConfig := currentTree.Config()
|
|
|
|
|
|
|
|
prototypes := make(map[string]config.VariableType)
|
|
|
|
for _, variable := range targetConfig.Variables {
|
|
|
|
prototypes[variable.Name] = variable.Type()
|
|
|
|
}
|
|
|
|
|
2016-04-11 12:40:06 -05:00
|
|
|
// Only display a module in an error message if we are not in the root module
|
|
|
|
modulePathDescription := fmt.Sprintf(" in module %s", strings.Join(n.ModulePath[1:], "."))
|
|
|
|
if len(n.ModulePath) == 1 {
|
|
|
|
modulePathDescription = ""
|
|
|
|
}
|
|
|
|
|
2016-04-15 14:07:54 -05:00
|
|
|
for name, declaredType := range prototypes {
|
|
|
|
// This is only necessary when we _actually_ check. It is left as a reminder
|
|
|
|
// that at the current time we are dealing with a type system consisting only
|
|
|
|
// of strings and maps - where the only valid inter-module variable type is
|
|
|
|
// string.
|
2016-04-11 12:40:06 -05:00
|
|
|
proposedValue, ok := n.Variables[name]
|
2016-04-21 23:30:34 -05:00
|
|
|
if !ok {
|
|
|
|
// This means the default value should be used as no overriding value
|
|
|
|
// has been set. Therefore we should continue as no check is necessary.
|
|
|
|
continue
|
|
|
|
}
|
2016-04-15 14:07:54 -05: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-21 19:03:24 -05:00
|
|
|
if proposedValue == config.UnknownVariableValue {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2016-04-15 14:07:54 -05:00
|
|
|
switch declaredType {
|
|
|
|
case config.VariableTypeString:
|
|
|
|
// This will need actual verification once we aren't dealing with
|
|
|
|
// a map[string]string but this is sufficient for now.
|
2016-04-11 12:40:06 -05:00
|
|
|
switch proposedValue.(type) {
|
|
|
|
case string:
|
|
|
|
continue
|
|
|
|
default:
|
2016-06-22 15:52:12 -05:00
|
|
|
return nil, fmt.Errorf("variable %s%s should be type %s, got %s",
|
|
|
|
name, modulePathDescription, declaredType.Printable(), hclTypeName(proposedValue))
|
2016-04-11 12:40:06 -05:00
|
|
|
}
|
|
|
|
case config.VariableTypeMap:
|
|
|
|
switch proposedValue.(type) {
|
|
|
|
case map[string]interface{}:
|
|
|
|
continue
|
|
|
|
default:
|
2016-06-22 15:52:12 -05:00
|
|
|
return nil, fmt.Errorf("variable %s%s should be type %s, got %s",
|
|
|
|
name, modulePathDescription, declaredType.Printable(), hclTypeName(proposedValue))
|
2016-04-15 14:07:54 -05: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-21 19:03:24 -05:00
|
|
|
case config.VariableTypeList:
|
|
|
|
switch proposedValue.(type) {
|
|
|
|
case []interface{}:
|
|
|
|
continue
|
|
|
|
default:
|
2016-06-22 15:52:12 -05:00
|
|
|
return nil, fmt.Errorf("variable %s%s should be type %s, got %s",
|
|
|
|
name, modulePathDescription, declaredType.Printable(), hclTypeName(proposedValue))
|
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-21 19:03:24 -05:00
|
|
|
}
|
2016-04-11 12:40:06 -05:00
|
|
|
default:
|
2016-04-15 14:07:54 -05:00
|
|
|
// This will need the actual type substituting when we have more than
|
|
|
|
// just strings and maps.
|
|
|
|
return nil, fmt.Errorf("variable %s%s should be type %s, got type string",
|
|
|
|
name, modulePathDescription, declaredType.Printable())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
|
2015-02-11 19:01:08 -06:00
|
|
|
// EvalSetVariables is an EvalNode implementation that sets the variables
|
|
|
|
// explicitly for interpolation later.
|
|
|
|
type EvalSetVariables struct {
|
2015-05-01 18:29:19 -05:00
|
|
|
Module *string
|
2016-04-11 12:40:06 -05:00
|
|
|
Variables map[string]interface{}
|
2015-02-11 19:01:08 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
// TODO: test
|
2015-02-14 00:58:41 -06:00
|
|
|
func (n *EvalSetVariables) Eval(ctx EvalContext) (interface{}, error) {
|
2015-05-01 18:29:19 -05:00
|
|
|
ctx.SetVariables(*n.Module, n.Variables)
|
2015-02-11 19:01:08 -06:00
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// EvalVariableBlock is an EvalNode implementation that evaluates the
|
|
|
|
// given configuration, and uses the final values as a way to set the
|
|
|
|
// mapping.
|
|
|
|
type EvalVariableBlock struct {
|
2016-04-11 12:40:06 -05:00
|
|
|
Config **ResourceConfig
|
|
|
|
VariableValues map[string]interface{}
|
2015-02-11 19:01:08 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
// TODO: test
|
2015-02-14 00:58:41 -06:00
|
|
|
func (n *EvalVariableBlock) Eval(ctx EvalContext) (interface{}, error) {
|
2015-02-11 19:01:08 -06:00
|
|
|
// Clear out the existing mapping
|
2016-04-11 12:40:06 -05:00
|
|
|
for k, _ := range n.VariableValues {
|
|
|
|
delete(n.VariableValues, k)
|
2015-02-11 19:01:08 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
// Get our configuration
|
2015-02-14 00:58:41 -06:00
|
|
|
rc := *n.Config
|
2015-02-11 19:01:08 -06:00
|
|
|
for k, v := range rc.Config {
|
2016-04-11 12:40:06 -05:00
|
|
|
var vString string
|
2016-06-03 15:57:04 -05:00
|
|
|
if err := hilmapstructure.WeakDecode(v, &vString); err == nil {
|
2016-04-11 12:40:06 -05:00
|
|
|
n.VariableValues[k] = vString
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
var vMap map[string]interface{}
|
2016-06-03 15:57:04 -05:00
|
|
|
if err := hilmapstructure.WeakDecode(v, &vMap); err == nil {
|
2016-04-11 12:40:06 -05:00
|
|
|
n.VariableValues[k] = vMap
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
var vSlice []interface{}
|
2016-06-03 15:57:04 -05:00
|
|
|
if err := hilmapstructure.WeakDecode(v, &vSlice); err == nil {
|
2016-04-11 12:40:06 -05:00
|
|
|
n.VariableValues[k] = vSlice
|
|
|
|
continue
|
2015-02-20 11:40:41 -06:00
|
|
|
}
|
|
|
|
|
2016-04-11 12:40:06 -05:00
|
|
|
return nil, fmt.Errorf("Variable value for %s is not a string, list or map type", k)
|
2015-02-11 19:01:08 -06:00
|
|
|
}
|
|
|
|
for k, _ := range rc.Raw {
|
2016-04-11 12:40:06 -05:00
|
|
|
if _, ok := n.VariableValues[k]; !ok {
|
|
|
|
n.VariableValues[k] = config.UnknownVariableValue
|
2015-02-11 19:01:08 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil, nil
|
|
|
|
}
|
2016-06-22 15:52:12 -05:00
|
|
|
|
|
|
|
// hclTypeName returns the name of the type that would represent this value in
|
|
|
|
// a config file, or falls back to the Go type name if there's no corresponding
|
|
|
|
// HCL type. This is used for formatted output, not for comparing types.
|
|
|
|
func hclTypeName(i interface{}) string {
|
|
|
|
switch k := reflect.Indirect(reflect.ValueOf(i)).Kind(); k {
|
|
|
|
case reflect.Bool:
|
|
|
|
return "boolean"
|
|
|
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
|
|
|
|
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32,
|
|
|
|
reflect.Uint64, reflect.Uintptr, reflect.Float32, reflect.Float64:
|
|
|
|
return "number"
|
|
|
|
case reflect.Array, reflect.Slice:
|
2016-06-24 06:42:39 -05:00
|
|
|
return "list"
|
2016-06-22 15:52:12 -05:00
|
|
|
case reflect.Map:
|
2016-06-24 06:42:39 -05:00
|
|
|
return "map"
|
2016-06-22 15:52:12 -05:00
|
|
|
case reflect.String:
|
|
|
|
return "string"
|
|
|
|
default:
|
|
|
|
// fall back to the Go type if there's no match
|
|
|
|
return k.String()
|
|
|
|
}
|
|
|
|
}
|