2015-02-06 01:47:06 +01:00
|
|
|
package terraform
|
|
|
|
|
|
|
|
import (
|
2015-04-15 02:42:23 +02:00
|
|
|
"fmt"
|
2015-02-06 01:47:06 +01:00
|
|
|
"os"
|
|
|
|
"reflect"
|
|
|
|
"sync"
|
|
|
|
"testing"
|
|
|
|
|
2016-06-03 22:57:04 +02:00
|
|
|
"github.com/davecgh/go-spew/spew"
|
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
|
|
|
"github.com/hashicorp/hil"
|
2016-01-31 08:43:41 +01:00
|
|
|
"github.com/hashicorp/hil/ast"
|
2015-02-06 01:47:06 +01:00
|
|
|
"github.com/hashicorp/terraform/config"
|
|
|
|
)
|
|
|
|
|
2016-10-28 21:20:25 +02:00
|
|
|
func TestInterpolater_simpleVar(t *testing.T) {
|
|
|
|
i := &Interpolater{}
|
|
|
|
scope := &InterpolationScope{}
|
|
|
|
testInterpolateErr(t, i, scope, "simple")
|
|
|
|
}
|
|
|
|
|
2015-02-06 01:47:06 +01:00
|
|
|
func TestInterpolater_countIndex(t *testing.T) {
|
|
|
|
i := &Interpolater{}
|
|
|
|
|
|
|
|
scope := &InterpolationScope{
|
|
|
|
Path: rootModulePath,
|
|
|
|
Resource: &Resource{CountIndex: 42},
|
|
|
|
}
|
|
|
|
|
|
|
|
testInterpolate(t, i, scope, "count.index", ast.Variable{
|
|
|
|
Value: 42,
|
|
|
|
Type: ast.TypeInt,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2015-04-15 02:42:23 +02:00
|
|
|
func TestInterpolater_countIndexInWrongContext(t *testing.T) {
|
|
|
|
i := &Interpolater{}
|
|
|
|
|
|
|
|
scope := &InterpolationScope{
|
|
|
|
Path: rootModulePath,
|
|
|
|
}
|
|
|
|
|
|
|
|
n := "count.index"
|
|
|
|
|
|
|
|
v, err := config.NewInterpolatedVariable(n)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("err: %s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
expectedErr := fmt.Errorf("foo: count.index is only valid within resources")
|
|
|
|
|
|
|
|
_, err = i.Values(scope, map[string]config.InterpolatedVariable{
|
|
|
|
"foo": v,
|
|
|
|
})
|
|
|
|
|
|
|
|
if !reflect.DeepEqual(expectedErr, err) {
|
|
|
|
t.Fatalf("expected: %#v, got %#v", expectedErr, err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-02-06 01:47:06 +01:00
|
|
|
func TestInterpolater_moduleVariable(t *testing.T) {
|
|
|
|
lock := new(sync.RWMutex)
|
|
|
|
state := &State{
|
|
|
|
Modules: []*ModuleState{
|
|
|
|
&ModuleState{
|
|
|
|
Path: rootModulePath,
|
|
|
|
Resources: map[string]*ResourceState{
|
|
|
|
"aws_instance.web": &ResourceState{
|
|
|
|
Type: "aws_instance",
|
|
|
|
Primary: &InstanceState{
|
|
|
|
ID: "bar",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
&ModuleState{
|
|
|
|
Path: []string{RootModuleName, "child"},
|
2016-05-12 02:05:02 +02:00
|
|
|
Outputs: map[string]*OutputState{
|
|
|
|
"foo": &OutputState{
|
|
|
|
Type: "string",
|
|
|
|
Value: "bar",
|
|
|
|
},
|
2015-02-06 01:47:06 +01:00
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
i := &Interpolater{
|
|
|
|
State: state,
|
|
|
|
StateLock: lock,
|
|
|
|
}
|
|
|
|
|
|
|
|
scope := &InterpolationScope{
|
|
|
|
Path: rootModulePath,
|
|
|
|
}
|
|
|
|
|
|
|
|
testInterpolate(t, i, scope, "module.child.foo", ast.Variable{
|
|
|
|
Value: "bar",
|
|
|
|
Type: ast.TypeString,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestInterpolater_pathCwd(t *testing.T) {
|
|
|
|
i := &Interpolater{}
|
|
|
|
scope := &InterpolationScope{}
|
|
|
|
|
|
|
|
expected, err := os.Getwd()
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("err: %s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
testInterpolate(t, i, scope, "path.cwd", ast.Variable{
|
|
|
|
Value: expected,
|
|
|
|
Type: ast.TypeString,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestInterpolater_pathModule(t *testing.T) {
|
|
|
|
mod := testModule(t, "interpolate-path-module")
|
|
|
|
i := &Interpolater{
|
|
|
|
Module: mod,
|
|
|
|
}
|
|
|
|
scope := &InterpolationScope{
|
|
|
|
Path: []string{RootModuleName, "child"},
|
|
|
|
}
|
|
|
|
|
|
|
|
path := mod.Child([]string{"child"}).Config().Dir
|
|
|
|
testInterpolate(t, i, scope, "path.module", ast.Variable{
|
|
|
|
Value: path,
|
|
|
|
Type: ast.TypeString,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestInterpolater_pathRoot(t *testing.T) {
|
|
|
|
mod := testModule(t, "interpolate-path-module")
|
|
|
|
i := &Interpolater{
|
|
|
|
Module: mod,
|
|
|
|
}
|
|
|
|
scope := &InterpolationScope{
|
|
|
|
Path: []string{RootModuleName, "child"},
|
|
|
|
}
|
|
|
|
|
|
|
|
path := mod.Config().Dir
|
|
|
|
testInterpolate(t, i, scope, "path.root", ast.Variable{
|
|
|
|
Value: path,
|
|
|
|
Type: ast.TypeString,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2016-06-11 13:27:39 +02:00
|
|
|
func TestInterpolater_resourceVariableMap(t *testing.T) {
|
|
|
|
lock := new(sync.RWMutex)
|
|
|
|
state := &State{
|
|
|
|
Modules: []*ModuleState{
|
|
|
|
&ModuleState{
|
|
|
|
Path: rootModulePath,
|
|
|
|
Resources: map[string]*ResourceState{
|
|
|
|
"aws_instance.web": &ResourceState{
|
|
|
|
Type: "aws_instance",
|
|
|
|
Primary: &InstanceState{
|
|
|
|
ID: "bar",
|
|
|
|
Attributes: map[string]string{
|
|
|
|
"amap.%": "3",
|
|
|
|
"amap.key1": "value1",
|
|
|
|
"amap.key2": "value2",
|
|
|
|
"amap.key3": "value3",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
i := &Interpolater{
|
|
|
|
Module: testModule(t, "interpolate-resource-variable"),
|
|
|
|
State: state,
|
|
|
|
StateLock: lock,
|
|
|
|
}
|
|
|
|
|
|
|
|
scope := &InterpolationScope{
|
|
|
|
Path: rootModulePath,
|
|
|
|
}
|
|
|
|
|
|
|
|
expected := map[string]interface{}{
|
|
|
|
"key1": "value1",
|
|
|
|
"key2": "value2",
|
|
|
|
"key3": "value3",
|
|
|
|
}
|
|
|
|
|
|
|
|
testInterpolate(t, i, scope, "aws_instance.web.amap",
|
|
|
|
interfaceToVariableSwallowError(expected))
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestInterpolater_resourceVariableComplexMap(t *testing.T) {
|
|
|
|
lock := new(sync.RWMutex)
|
|
|
|
state := &State{
|
|
|
|
Modules: []*ModuleState{
|
|
|
|
&ModuleState{
|
|
|
|
Path: rootModulePath,
|
|
|
|
Resources: map[string]*ResourceState{
|
|
|
|
"aws_instance.web": &ResourceState{
|
|
|
|
Type: "aws_instance",
|
|
|
|
Primary: &InstanceState{
|
|
|
|
ID: "bar",
|
|
|
|
Attributes: map[string]string{
|
|
|
|
"amap.%": "2",
|
|
|
|
"amap.key1.#": "2",
|
|
|
|
"amap.key1.0": "hello",
|
|
|
|
"amap.key1.1": "world",
|
|
|
|
"amap.key2.#": "1",
|
|
|
|
"amap.key2.0": "foo",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
i := &Interpolater{
|
|
|
|
Module: testModule(t, "interpolate-resource-variable"),
|
|
|
|
State: state,
|
|
|
|
StateLock: lock,
|
|
|
|
}
|
|
|
|
|
|
|
|
scope := &InterpolationScope{
|
|
|
|
Path: rootModulePath,
|
|
|
|
}
|
|
|
|
|
|
|
|
expected := map[string]interface{}{
|
|
|
|
"key1": []interface{}{"hello", "world"},
|
|
|
|
"key2": []interface{}{"foo"},
|
|
|
|
}
|
|
|
|
|
|
|
|
testInterpolate(t, i, scope, "aws_instance.web.amap",
|
|
|
|
interfaceToVariableSwallowError(expected))
|
|
|
|
}
|
|
|
|
|
2015-07-20 02:27:38 +02:00
|
|
|
func TestInterpolater_resourceVariable(t *testing.T) {
|
|
|
|
lock := new(sync.RWMutex)
|
|
|
|
state := &State{
|
|
|
|
Modules: []*ModuleState{
|
|
|
|
&ModuleState{
|
|
|
|
Path: rootModulePath,
|
|
|
|
Resources: map[string]*ResourceState{
|
|
|
|
"aws_instance.web": &ResourceState{
|
|
|
|
Type: "aws_instance",
|
|
|
|
Primary: &InstanceState{
|
|
|
|
ID: "bar",
|
|
|
|
Attributes: map[string]string{
|
|
|
|
"foo": "bar",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
i := &Interpolater{
|
|
|
|
Module: testModule(t, "interpolate-resource-variable"),
|
|
|
|
State: state,
|
|
|
|
StateLock: lock,
|
|
|
|
}
|
|
|
|
|
|
|
|
scope := &InterpolationScope{
|
|
|
|
Path: rootModulePath,
|
|
|
|
}
|
|
|
|
|
|
|
|
testInterpolate(t, i, scope, "aws_instance.web.foo", ast.Variable{
|
|
|
|
Value: "bar",
|
|
|
|
Type: ast.TypeString,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2016-05-14 18:25:03 +02:00
|
|
|
func TestInterpolater_resourceVariableMissingDuringInput(t *testing.T) {
|
|
|
|
// During the input walk, computed resource attributes may be entirely
|
|
|
|
// absent since we've not yet produced diffs that tell us what computed
|
|
|
|
// attributes to expect. In that case, interpolator tolerates it and
|
|
|
|
// indicates the value is computed.
|
|
|
|
|
|
|
|
lock := new(sync.RWMutex)
|
|
|
|
state := &State{
|
|
|
|
Modules: []*ModuleState{
|
|
|
|
&ModuleState{
|
|
|
|
Path: rootModulePath,
|
|
|
|
Resources: map[string]*ResourceState{
|
|
|
|
// No resources at all yet, because we're still dealing
|
|
|
|
// with input and so the resources haven't been created.
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
{
|
|
|
|
i := &Interpolater{
|
|
|
|
Operation: walkInput,
|
|
|
|
Module: testModule(t, "interpolate-resource-variable"),
|
|
|
|
State: state,
|
|
|
|
StateLock: lock,
|
|
|
|
}
|
|
|
|
|
|
|
|
scope := &InterpolationScope{
|
|
|
|
Path: rootModulePath,
|
|
|
|
}
|
|
|
|
|
|
|
|
testInterpolate(t, i, scope, "aws_instance.web.foo", ast.Variable{
|
|
|
|
Value: config.UnknownVariableValue,
|
2016-10-27 06:00:24 +02:00
|
|
|
Type: ast.TypeUnknown,
|
2016-05-14 18:25:03 +02:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
// This doesn't apply during other walks, like plan
|
|
|
|
{
|
|
|
|
i := &Interpolater{
|
|
|
|
Operation: walkPlan,
|
|
|
|
Module: testModule(t, "interpolate-resource-variable"),
|
|
|
|
State: state,
|
|
|
|
StateLock: lock,
|
|
|
|
}
|
|
|
|
|
|
|
|
scope := &InterpolationScope{
|
|
|
|
Path: rootModulePath,
|
|
|
|
}
|
|
|
|
|
|
|
|
testInterpolateErr(t, i, scope, "aws_instance.web.foo")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-07-20 02:27:38 +02:00
|
|
|
func TestInterpolater_resourceVariableMulti(t *testing.T) {
|
|
|
|
lock := new(sync.RWMutex)
|
|
|
|
state := &State{
|
|
|
|
Modules: []*ModuleState{
|
|
|
|
&ModuleState{
|
|
|
|
Path: rootModulePath,
|
|
|
|
Resources: map[string]*ResourceState{
|
|
|
|
"aws_instance.web": &ResourceState{
|
|
|
|
Type: "aws_instance",
|
|
|
|
Primary: &InstanceState{
|
|
|
|
ID: "bar",
|
|
|
|
Attributes: map[string]string{
|
|
|
|
"foo": config.UnknownVariableValue,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
i := &Interpolater{
|
|
|
|
Module: testModule(t, "interpolate-resource-variable"),
|
|
|
|
State: state,
|
|
|
|
StateLock: lock,
|
|
|
|
}
|
|
|
|
|
|
|
|
scope := &InterpolationScope{
|
|
|
|
Path: rootModulePath,
|
|
|
|
}
|
|
|
|
|
|
|
|
testInterpolate(t, i, scope, "aws_instance.web.*.foo", ast.Variable{
|
|
|
|
Value: config.UnknownVariableValue,
|
2016-10-27 06:00:24 +02:00
|
|
|
Type: ast.TypeUnknown,
|
2015-07-20 02:27:38 +02:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2016-12-11 01:17:29 +01:00
|
|
|
// When a splat reference is made to an attribute that is a computed list,
|
|
|
|
// the result should be unknown.
|
|
|
|
func TestInterpolater_resourceVariableMultiList(t *testing.T) {
|
|
|
|
lock := new(sync.RWMutex)
|
|
|
|
state := &State{
|
|
|
|
Modules: []*ModuleState{
|
|
|
|
&ModuleState{
|
|
|
|
Path: rootModulePath,
|
|
|
|
Resources: map[string]*ResourceState{
|
|
|
|
"aws_instance.web.0": &ResourceState{
|
|
|
|
Type: "aws_instance",
|
|
|
|
Primary: &InstanceState{
|
|
|
|
ID: "bar",
|
|
|
|
Attributes: map[string]string{
|
|
|
|
"ip.#": config.UnknownVariableValue,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
"aws_instance.web.1": &ResourceState{
|
|
|
|
Type: "aws_instance",
|
|
|
|
Primary: &InstanceState{
|
|
|
|
ID: "bar",
|
|
|
|
Attributes: map[string]string{
|
|
|
|
"ip.#": "0",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
i := &Interpolater{
|
|
|
|
Module: testModule(t, "interpolate-resource-variable"),
|
|
|
|
State: state,
|
|
|
|
StateLock: lock,
|
|
|
|
}
|
|
|
|
|
|
|
|
scope := &InterpolationScope{
|
|
|
|
Path: rootModulePath,
|
|
|
|
}
|
|
|
|
|
|
|
|
testInterpolate(t, i, scope, "aws_instance.web.*.ip", ast.Variable{
|
|
|
|
Value: config.UnknownVariableValue,
|
|
|
|
Type: ast.TypeUnknown,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2016-10-14 02:57:11 +02:00
|
|
|
func TestInterpolater_resourceVariableMulti_interpolated(t *testing.T) {
|
|
|
|
lock := new(sync.RWMutex)
|
|
|
|
state := &State{
|
|
|
|
Modules: []*ModuleState{
|
|
|
|
&ModuleState{
|
|
|
|
Path: rootModulePath,
|
|
|
|
Resources: map[string]*ResourceState{
|
|
|
|
"aws_instance.web.0": &ResourceState{
|
|
|
|
Type: "aws_instance",
|
|
|
|
Primary: &InstanceState{
|
|
|
|
ID: "a",
|
|
|
|
Attributes: map[string]string{"foo": "a"},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
"aws_instance.web.1": &ResourceState{
|
|
|
|
Type: "aws_instance",
|
|
|
|
Primary: &InstanceState{
|
|
|
|
ID: "b",
|
|
|
|
Attributes: map[string]string{"foo": "b"},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
i := &Interpolater{
|
|
|
|
Operation: walkApply,
|
|
|
|
Module: testModule(t, "interpolate-multi-interp"),
|
|
|
|
State: state,
|
|
|
|
StateLock: lock,
|
|
|
|
}
|
|
|
|
|
|
|
|
scope := &InterpolationScope{
|
|
|
|
Path: rootModulePath,
|
|
|
|
}
|
|
|
|
|
|
|
|
expected := []interface{}{"a", "b"}
|
|
|
|
testInterpolate(t, i, scope, "aws_instance.web.*.foo",
|
|
|
|
interfaceToVariableSwallowError(expected))
|
|
|
|
}
|
|
|
|
|
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
|
|
|
func interfaceToVariableSwallowError(input interface{}) ast.Variable {
|
|
|
|
variable, _ := hil.InterfaceToVariable(input)
|
|
|
|
return variable
|
|
|
|
}
|
|
|
|
|
2015-07-05 16:26:01 +02:00
|
|
|
func TestInterpolator_resourceMultiAttributes(t *testing.T) {
|
|
|
|
lock := new(sync.RWMutex)
|
|
|
|
state := &State{
|
|
|
|
Modules: []*ModuleState{
|
2016-06-11 13:27:39 +02:00
|
|
|
{
|
2015-07-05 16:26:01 +02:00
|
|
|
Path: rootModulePath,
|
|
|
|
Resources: map[string]*ResourceState{
|
2016-06-11 13:27:39 +02:00
|
|
|
"aws_route53_zone.yada": {
|
2015-07-05 16:26:01 +02:00
|
|
|
Type: "aws_route53_zone",
|
|
|
|
Dependencies: []string{},
|
|
|
|
Primary: &InstanceState{
|
|
|
|
ID: "AAABBBCCCDDDEEE",
|
|
|
|
Attributes: map[string]string{
|
|
|
|
"name_servers.#": "4",
|
|
|
|
"name_servers.0": "ns-1334.awsdns-38.org",
|
|
|
|
"name_servers.1": "ns-1680.awsdns-18.co.uk",
|
|
|
|
"name_servers.2": "ns-498.awsdns-62.com",
|
|
|
|
"name_servers.3": "ns-601.awsdns-11.net",
|
|
|
|
"listeners.#": "1",
|
|
|
|
"listeners.0": "red",
|
core: Use .% instead of .# for maps in state
The flatmapped representation of state prior to this commit encoded maps
and lists (and therefore by extension, sets) with a key corresponding to
the number of elements, or the unknown variable indicator under a .# key
and then individual items. For example, the list ["a", "b", "c"] would
have been encoded as:
listname.# = 3
listname.0 = "a"
listname.1 = "b"
listname.2 = "c"
And the map {"key1": "value1", "key2", "value2"} would have been encoded
as:
mapname.# = 2
mapname.key1 = "value1"
mapname.key2 = "value2"
Sets use the hash code as the key - for example a set with a (fictional)
hashcode calculation may look like:
setname.# = 2
setname.12312512 = "value1"
setname.56345233 = "value2"
Prior to the work done to extend the type system, this was sufficient
since the internal representation of these was effectively the same.
However, following the separation of maps and lists into distinct
first-class types, this encoding presents a problem: given a state file,
it is impossible to tell the encoding of an empty list and an empty map
apart. This presents problems for the type checker during interpolation,
as many interpolation functions will operate on only one of these two
structures.
This commit therefore changes the representation in state of maps to use
a "%" as the key for the number of elements. Consequently the map above
will now be encoded as:
mapname.% = 2
mapname.key1 = "value1"
mapname.key2 = "value2"
This has the effect of an empty list (or set) now being encoded as:
listname.# = 0
And an empty map now being encoded as:
mapname.% = 0
Therefore we can eliminate some nasty guessing logic from the resource
variable supplier for interpolation, at the cost of having to migrate
state up front (to follow in a subsequent commit).
In order to reduce the number of potential situations in which resources
would be "forced new", we continue to accept "#" as the count key when
reading maps via helper/schema. There is no situation under which we can
allow "#" as an actual map key in any case, as it would not be
distinguishable from a list or set in state.
2016-06-05 10:34:43 +02:00
|
|
|
"tags.%": "1",
|
2015-07-05 16:26:01 +02:00
|
|
|
"tags.Name": "reindeer",
|
|
|
|
"nothing.#": "0",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
i := &Interpolater{
|
|
|
|
Module: testModule(t, "interpolate-multi-vars"),
|
|
|
|
StateLock: lock,
|
|
|
|
State: state,
|
|
|
|
}
|
|
|
|
|
|
|
|
scope := &InterpolationScope{
|
|
|
|
Path: rootModulePath,
|
|
|
|
}
|
|
|
|
|
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
|
|
|
name_servers := []interface{}{
|
2015-07-05 16:26:01 +02:00
|
|
|
"ns-1334.awsdns-38.org",
|
|
|
|
"ns-1680.awsdns-18.co.uk",
|
|
|
|
"ns-498.awsdns-62.com",
|
|
|
|
"ns-601.awsdns-11.net",
|
|
|
|
}
|
|
|
|
|
|
|
|
// More than 1 element
|
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
|
|
|
testInterpolate(t, i, scope, "aws_route53_zone.yada.name_servers",
|
|
|
|
interfaceToVariableSwallowError(name_servers))
|
2015-07-05 16:26:01 +02:00
|
|
|
|
|
|
|
// Exactly 1 element
|
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
|
|
|
testInterpolate(t, i, scope, "aws_route53_zone.yada.listeners",
|
|
|
|
interfaceToVariableSwallowError([]interface{}{"red"}))
|
2015-07-05 16:26:01 +02:00
|
|
|
|
|
|
|
// Zero elements
|
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
|
|
|
testInterpolate(t, i, scope, "aws_route53_zone.yada.nothing",
|
|
|
|
interfaceToVariableSwallowError([]interface{}{}))
|
2015-07-05 16:26:01 +02:00
|
|
|
|
|
|
|
// Maps still need to work
|
|
|
|
testInterpolate(t, i, scope, "aws_route53_zone.yada.tags.Name", ast.Variable{
|
|
|
|
Value: "reindeer",
|
|
|
|
Type: ast.TypeString,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestInterpolator_resourceMultiAttributesWithResourceCount(t *testing.T) {
|
|
|
|
i := getInterpolaterFixture(t)
|
|
|
|
scope := &InterpolationScope{
|
|
|
|
Path: rootModulePath,
|
|
|
|
}
|
|
|
|
|
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
|
|
|
name_servers := []interface{}{
|
2015-07-05 16:26:01 +02:00
|
|
|
"ns-1334.awsdns-38.org",
|
|
|
|
"ns-1680.awsdns-18.co.uk",
|
|
|
|
"ns-498.awsdns-62.com",
|
|
|
|
"ns-601.awsdns-11.net",
|
|
|
|
"ns-000.awsdns-38.org",
|
|
|
|
"ns-444.awsdns-18.co.uk",
|
|
|
|
"ns-999.awsdns-62.com",
|
2016-06-11 13:27:39 +02:00
|
|
|
"ns-666.awsdns-11.net",
|
2015-07-05 16:26:01 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// More than 1 element
|
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
|
|
|
testInterpolate(t, i, scope, "aws_route53_zone.terra.0.name_servers",
|
2016-07-11 19:40:21 +02:00
|
|
|
interfaceToVariableSwallowError(name_servers[:4]))
|
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
|
|
|
|
2015-07-05 16:26:01 +02:00
|
|
|
// More than 1 element in both
|
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
|
|
|
testInterpolate(t, i, scope, "aws_route53_zone.terra.*.name_servers",
|
2016-07-11 19:40:21 +02:00
|
|
|
interfaceToVariableSwallowError([]interface{}{name_servers[:4], name_servers[4:]}))
|
2015-07-05 16:26:01 +02:00
|
|
|
|
|
|
|
// Exactly 1 element
|
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
|
|
|
testInterpolate(t, i, scope, "aws_route53_zone.terra.0.listeners",
|
|
|
|
interfaceToVariableSwallowError([]interface{}{"red"}))
|
|
|
|
|
2015-07-05 16:26:01 +02:00
|
|
|
// Exactly 1 element in both
|
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
|
|
|
testInterpolate(t, i, scope, "aws_route53_zone.terra.*.listeners",
|
2016-07-11 19:40:21 +02:00
|
|
|
interfaceToVariableSwallowError([]interface{}{[]interface{}{"red"}, []interface{}{"blue"}}))
|
2015-07-05 16:26:01 +02:00
|
|
|
|
|
|
|
// Zero elements
|
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
|
|
|
testInterpolate(t, i, scope, "aws_route53_zone.terra.0.nothing",
|
|
|
|
interfaceToVariableSwallowError([]interface{}{}))
|
|
|
|
|
2015-07-05 16:26:01 +02:00
|
|
|
// Zero + 1 element
|
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
|
|
|
testInterpolate(t, i, scope, "aws_route53_zone.terra.*.special",
|
2016-07-11 19:40:21 +02:00
|
|
|
interfaceToVariableSwallowError([]interface{}{[]interface{}{"extra"}}))
|
2015-07-05 16:26:01 +02:00
|
|
|
|
|
|
|
// Maps still need to work
|
|
|
|
testInterpolate(t, i, scope, "aws_route53_zone.terra.0.tags.Name", ast.Variable{
|
|
|
|
Value: "reindeer",
|
|
|
|
Type: ast.TypeString,
|
|
|
|
})
|
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
|
|
|
|
2015-07-05 16:26:01 +02:00
|
|
|
// Maps still need to work in both
|
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
|
|
|
testInterpolate(t, i, scope, "aws_route53_zone.terra.*.tags.Name",
|
|
|
|
interfaceToVariableSwallowError([]interface{}{"reindeer", "white-hart"}))
|
2015-07-05 16:26:01 +02:00
|
|
|
}
|
|
|
|
|
2016-01-26 20:18:00 +01:00
|
|
|
func TestInterpolator_resourceMultiAttributesComputed(t *testing.T) {
|
|
|
|
lock := new(sync.RWMutex)
|
|
|
|
// The state would never be written with an UnknownVariableValue in it, but
|
|
|
|
// it can/does exist that way in memory during the plan phase.
|
|
|
|
state := &State{
|
|
|
|
Modules: []*ModuleState{
|
|
|
|
&ModuleState{
|
|
|
|
Path: rootModulePath,
|
|
|
|
Resources: map[string]*ResourceState{
|
|
|
|
"aws_route53_zone.yada": &ResourceState{
|
|
|
|
Type: "aws_route53_zone",
|
|
|
|
Primary: &InstanceState{
|
|
|
|
ID: "z-abc123",
|
|
|
|
Attributes: map[string]string{
|
|
|
|
"name_servers.#": config.UnknownVariableValue,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
i := &Interpolater{
|
|
|
|
Module: testModule(t, "interpolate-multi-vars"),
|
|
|
|
StateLock: lock,
|
|
|
|
State: state,
|
|
|
|
}
|
|
|
|
|
|
|
|
scope := &InterpolationScope{
|
|
|
|
Path: rootModulePath,
|
|
|
|
}
|
|
|
|
|
|
|
|
testInterpolate(t, i, scope, "aws_route53_zone.yada.name_servers", ast.Variable{
|
|
|
|
Value: config.UnknownVariableValue,
|
2016-10-27 06:00:24 +02:00
|
|
|
Type: ast.TypeUnknown,
|
2016-01-26 20:18:00 +01:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2016-11-11 02:14:20 +01:00
|
|
|
func TestInterpolator_resourceAttributeComputed(t *testing.T) {
|
|
|
|
lock := new(sync.RWMutex)
|
|
|
|
// The state would never be written with an UnknownVariableValue in it, but
|
|
|
|
// it can/does exist that way in memory during the plan phase.
|
|
|
|
state := &State{
|
|
|
|
Modules: []*ModuleState{
|
|
|
|
&ModuleState{
|
|
|
|
Path: rootModulePath,
|
|
|
|
Resources: map[string]*ResourceState{
|
|
|
|
"aws_route53_zone.yada": &ResourceState{
|
|
|
|
Type: "aws_route53_zone",
|
|
|
|
Primary: &InstanceState{
|
|
|
|
ID: "z-abc123",
|
|
|
|
Attributes: map[string]string{
|
|
|
|
"id": config.UnknownVariableValue,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
i := &Interpolater{
|
|
|
|
Module: testModule(t, "interpolate-multi-vars"),
|
|
|
|
StateLock: lock,
|
|
|
|
State: state,
|
|
|
|
}
|
|
|
|
|
|
|
|
scope := &InterpolationScope{
|
|
|
|
Path: rootModulePath,
|
|
|
|
}
|
|
|
|
|
|
|
|
testInterpolate(t, i, scope, "aws_route53_zone.yada.id", ast.Variable{
|
|
|
|
Value: config.UnknownVariableValue,
|
|
|
|
Type: ast.TypeUnknown,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2016-02-23 18:42:55 +01:00
|
|
|
func TestInterpolater_selfVarWithoutResource(t *testing.T) {
|
|
|
|
i := &Interpolater{}
|
|
|
|
|
|
|
|
scope := &InterpolationScope{
|
|
|
|
Path: rootModulePath,
|
|
|
|
}
|
|
|
|
|
|
|
|
v, err := config.NewInterpolatedVariable("self.name")
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("err: %s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
_, err = i.Values(scope, map[string]config.InterpolatedVariable{"foo": v})
|
|
|
|
if err == nil {
|
|
|
|
t.Fatalf("expected err, got none")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-12-15 20:10:00 +01:00
|
|
|
func TestInterpolator_interpolatedListOrder(t *testing.T) {
|
|
|
|
state := &State{
|
|
|
|
Modules: []*ModuleState{
|
|
|
|
&ModuleState{
|
|
|
|
Path: rootModulePath,
|
|
|
|
Resources: map[string]*ResourceState{
|
|
|
|
"aws_route53_zone.list": &ResourceState{
|
|
|
|
Type: "aws_route53_zone",
|
|
|
|
Dependencies: []string{},
|
|
|
|
Primary: &InstanceState{
|
|
|
|
ID: "null",
|
|
|
|
Attributes: map[string]string{
|
|
|
|
"foo.#": "12",
|
|
|
|
"foo.0": "a",
|
|
|
|
"foo.1": "b",
|
|
|
|
"foo.2": "c",
|
|
|
|
"foo.3": "d",
|
|
|
|
"foo.4": "e",
|
|
|
|
"foo.5": "f",
|
|
|
|
"foo.6": "g",
|
|
|
|
"foo.7": "h",
|
|
|
|
"foo.8": "i",
|
|
|
|
"foo.9": "j",
|
|
|
|
"foo.10": "k",
|
|
|
|
"foo.11": "l",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
i := &Interpolater{
|
|
|
|
Module: testModule(t, "interpolate-multi-vars"),
|
|
|
|
StateLock: new(sync.RWMutex),
|
|
|
|
State: state,
|
|
|
|
}
|
|
|
|
|
|
|
|
scope := &InterpolationScope{
|
|
|
|
Path: rootModulePath,
|
|
|
|
}
|
|
|
|
|
|
|
|
list := []interface{}{"a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l"}
|
|
|
|
|
|
|
|
testInterpolate(t, i, scope, "aws_route53_zone.list.foo",
|
|
|
|
interfaceToVariableSwallowError(list))
|
|
|
|
}
|
|
|
|
|
2015-07-05 16:26:01 +02:00
|
|
|
func getInterpolaterFixture(t *testing.T) *Interpolater {
|
|
|
|
lock := new(sync.RWMutex)
|
|
|
|
state := &State{
|
|
|
|
Modules: []*ModuleState{
|
|
|
|
&ModuleState{
|
|
|
|
Path: rootModulePath,
|
|
|
|
Resources: map[string]*ResourceState{
|
|
|
|
"aws_route53_zone.terra.0": &ResourceState{
|
|
|
|
Type: "aws_route53_zone",
|
|
|
|
Dependencies: []string{},
|
|
|
|
Primary: &InstanceState{
|
|
|
|
ID: "AAABBBCCCDDDEEE",
|
|
|
|
Attributes: map[string]string{
|
|
|
|
"name_servers.#": "4",
|
|
|
|
"name_servers.0": "ns-1334.awsdns-38.org",
|
|
|
|
"name_servers.1": "ns-1680.awsdns-18.co.uk",
|
|
|
|
"name_servers.2": "ns-498.awsdns-62.com",
|
|
|
|
"name_servers.3": "ns-601.awsdns-11.net",
|
|
|
|
"listeners.#": "1",
|
|
|
|
"listeners.0": "red",
|
2016-06-10 17:07:17 +02:00
|
|
|
"tags.%": "1",
|
2015-07-05 16:26:01 +02:00
|
|
|
"tags.Name": "reindeer",
|
|
|
|
"nothing.#": "0",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
"aws_route53_zone.terra.1": &ResourceState{
|
|
|
|
Type: "aws_route53_zone",
|
|
|
|
Dependencies: []string{},
|
|
|
|
Primary: &InstanceState{
|
|
|
|
ID: "EEEFFFGGGHHHIII",
|
|
|
|
Attributes: map[string]string{
|
|
|
|
"name_servers.#": "4",
|
|
|
|
"name_servers.0": "ns-000.awsdns-38.org",
|
|
|
|
"name_servers.1": "ns-444.awsdns-18.co.uk",
|
|
|
|
"name_servers.2": "ns-999.awsdns-62.com",
|
|
|
|
"name_servers.3": "ns-666.awsdns-11.net",
|
|
|
|
"listeners.#": "1",
|
|
|
|
"listeners.0": "blue",
|
|
|
|
"special.#": "1",
|
|
|
|
"special.0": "extra",
|
2016-06-10 17:07:17 +02:00
|
|
|
"tags.%": "1",
|
2015-07-05 16:26:01 +02:00
|
|
|
"tags.Name": "white-hart",
|
|
|
|
"nothing.#": "0",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
return &Interpolater{
|
|
|
|
Module: testModule(t, "interpolate-multi-vars"),
|
|
|
|
StateLock: lock,
|
|
|
|
State: state,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-12-16 16:31:02 +01:00
|
|
|
func TestInterpolator_nestedMapsAndLists(t *testing.T) {
|
|
|
|
state := &State{
|
|
|
|
Modules: []*ModuleState{
|
|
|
|
&ModuleState{
|
|
|
|
Path: rootModulePath,
|
|
|
|
Resources: map[string]*ResourceState{
|
|
|
|
"aws_route53_zone.yada": &ResourceState{
|
|
|
|
Type: "aws_route53_zone",
|
|
|
|
Dependencies: []string{},
|
|
|
|
Primary: &InstanceState{
|
|
|
|
ID: "null",
|
|
|
|
Attributes: map[string]string{
|
|
|
|
"list_of_map.#": "2",
|
|
|
|
"list_of_map.0.%": "1",
|
|
|
|
"list_of_map.0.a": "1",
|
|
|
|
"list_of_map.1.%": "1",
|
|
|
|
"list_of_map.1.b": "2",
|
|
|
|
"map_of_list.%": "2",
|
|
|
|
"map_of_list.list2.#": "1",
|
|
|
|
"map_of_list.list2.0": "b",
|
|
|
|
"map_of_list.list1.#": "1",
|
|
|
|
"map_of_list.list1.0": "a",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
i := &Interpolater{
|
|
|
|
Module: testModule(t, "interpolate-multi-vars"),
|
|
|
|
StateLock: new(sync.RWMutex),
|
|
|
|
State: state,
|
|
|
|
}
|
|
|
|
|
|
|
|
scope := &InterpolationScope{
|
|
|
|
Path: rootModulePath,
|
|
|
|
}
|
|
|
|
|
|
|
|
listOfMap := []interface{}{
|
|
|
|
map[string]interface{}{"a": "1"},
|
|
|
|
map[string]interface{}{"b": "2"},
|
|
|
|
}
|
|
|
|
|
|
|
|
mapOfList := map[string]interface{}{
|
|
|
|
"list1": []interface{}{"a"},
|
|
|
|
"list2": []interface{}{"b"},
|
|
|
|
}
|
|
|
|
|
|
|
|
testInterpolate(t, i, scope, "aws_route53_zone.yada.list_of_map",
|
|
|
|
interfaceToVariableSwallowError(listOfMap))
|
|
|
|
testInterpolate(t, i, scope, `aws_route53_zone.yada.map_of_list`,
|
|
|
|
interfaceToVariableSwallowError(mapOfList))
|
|
|
|
}
|
|
|
|
|
Fix string representation of sets during interpolation
The change in #10787 used flatmap.Expand to fix interpolation of nested
maps, but it broke interpolation of sets such that their elements were
not represented. For example, the expected string representation of a
splatted aws_network_interface.whatever.*.private_ips should be:
```
[{Variable (TypeList): [{Variable (TypeString): 10.41.17.25}]} {Variable (TypeList): [{Variable (TypeString): 10.41.22.236}]}]
```
But instead it became:
```
[{Variable (TypeList): [{Variable (TypeString): }]} {Variable (TypeList): [{Variable (TypeString): }]}]
```
This is because the expandArray function of expand.go treated arrays to
exclusively be lists, e.g. not sets. The old code used to match for
numeric keys, so it would work for sets, whereas expandArray just
assumed keys started at 0 and ascended incrementally. Remember that
sets' keys are numeric, but since they are hashes, they can be any
integer. The result of assuming that the keys start at 0 led to the
recursive call to flatmap.Expand not matching any keys of the set, and
returning nil, which is why the above example has nothing where the IP
addresses used to be.
So we bring back that matching behavior, but we move it to expandArray
instead. We've modified it to not reconstruct the data structures like
it used to when it was in the Interpolator, and to use the standard int
sorter rather than implementing a custom sorter since a custom one is no
longer necessary thanks to the use of flatmap.Expand.
Fixes #10908, and restores the viability of the workaround I posted in #8696.
Big thanks to @jszwedko for helping me with this fix. I was able to
diagnose the problem along, but couldn't fix it without his help.
2016-12-23 23:59:18 +01:00
|
|
|
func TestInterpolator_sets(t *testing.T) {
|
|
|
|
state := &State{
|
|
|
|
Modules: []*ModuleState{
|
|
|
|
&ModuleState{
|
|
|
|
Path: rootModulePath,
|
|
|
|
Resources: map[string]*ResourceState{
|
|
|
|
"aws_network_interface.set": &ResourceState{
|
|
|
|
Type: "aws_network_interface",
|
|
|
|
Dependencies: []string{},
|
|
|
|
Primary: &InstanceState{
|
|
|
|
ID: "null",
|
|
|
|
Attributes: map[string]string{
|
|
|
|
"private_ips.#": "1",
|
|
|
|
"private_ips.3977356764": "10.42.16.179",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
i := &Interpolater{
|
|
|
|
Module: testModule(t, "interpolate-multi-vars"),
|
|
|
|
StateLock: new(sync.RWMutex),
|
|
|
|
State: state,
|
|
|
|
}
|
|
|
|
|
|
|
|
scope := &InterpolationScope{
|
|
|
|
Path: rootModulePath,
|
|
|
|
}
|
|
|
|
|
|
|
|
set := []interface{}{"10.42.16.179"}
|
|
|
|
|
|
|
|
testInterpolate(t, i, scope, "aws_network_interface.set.private_ips",
|
|
|
|
interfaceToVariableSwallowError(set))
|
|
|
|
}
|
|
|
|
|
2015-02-06 01:47:06 +01:00
|
|
|
func testInterpolate(
|
|
|
|
t *testing.T, i *Interpolater,
|
|
|
|
scope *InterpolationScope,
|
|
|
|
n string, expectedVar ast.Variable) {
|
|
|
|
v, err := config.NewInterpolatedVariable(n)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("err: %s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
actual, err := i.Values(scope, map[string]config.InterpolatedVariable{
|
|
|
|
"foo": v,
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("err: %s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
expected := map[string]ast.Variable{
|
|
|
|
"foo": expectedVar,
|
|
|
|
}
|
|
|
|
if !reflect.DeepEqual(actual, expected) {
|
2016-06-03 22:57:04 +02:00
|
|
|
spew.Config.DisableMethods = true
|
core: Use .% instead of .# for maps in state
The flatmapped representation of state prior to this commit encoded maps
and lists (and therefore by extension, sets) with a key corresponding to
the number of elements, or the unknown variable indicator under a .# key
and then individual items. For example, the list ["a", "b", "c"] would
have been encoded as:
listname.# = 3
listname.0 = "a"
listname.1 = "b"
listname.2 = "c"
And the map {"key1": "value1", "key2", "value2"} would have been encoded
as:
mapname.# = 2
mapname.key1 = "value1"
mapname.key2 = "value2"
Sets use the hash code as the key - for example a set with a (fictional)
hashcode calculation may look like:
setname.# = 2
setname.12312512 = "value1"
setname.56345233 = "value2"
Prior to the work done to extend the type system, this was sufficient
since the internal representation of these was effectively the same.
However, following the separation of maps and lists into distinct
first-class types, this encoding presents a problem: given a state file,
it is impossible to tell the encoding of an empty list and an empty map
apart. This presents problems for the type checker during interpolation,
as many interpolation functions will operate on only one of these two
structures.
This commit therefore changes the representation in state of maps to use
a "%" as the key for the number of elements. Consequently the map above
will now be encoded as:
mapname.% = 2
mapname.key1 = "value1"
mapname.key2 = "value2"
This has the effect of an empty list (or set) now being encoded as:
listname.# = 0
And an empty map now being encoded as:
mapname.% = 0
Therefore we can eliminate some nasty guessing logic from the resource
variable supplier for interpolation, at the cost of having to migrate
state up front (to follow in a subsequent commit).
In order to reduce the number of potential situations in which resources
would be "forced new", we continue to accept "#" as the count key when
reading maps via helper/schema. There is no situation under which we can
allow "#" as an actual map key in any case, as it would not be
distinguishable from a list or set in state.
2016-06-05 10:34:43 +02:00
|
|
|
t.Fatalf("%q:\n\n actual: %#v\nexpected: %#v\n\n%s\n\n%s\n\n", n, actual, expected,
|
2016-06-03 22:57:04 +02:00
|
|
|
spew.Sdump(actual), spew.Sdump(expected))
|
2015-02-06 01:47:06 +01:00
|
|
|
}
|
|
|
|
}
|
2016-05-14 18:25:03 +02:00
|
|
|
|
|
|
|
func testInterpolateErr(
|
|
|
|
t *testing.T, i *Interpolater,
|
|
|
|
scope *InterpolationScope,
|
|
|
|
n string) {
|
|
|
|
v, err := config.NewInterpolatedVariable(n)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("err: %s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
_, err = i.Values(scope, map[string]config.InterpolatedVariable{
|
|
|
|
"foo": v,
|
|
|
|
})
|
|
|
|
if err == nil {
|
|
|
|
t.Fatalf("%q: succeeded, but wanted error", n)
|
|
|
|
}
|
|
|
|
}
|