opentofu/terraform/resource_test.go
James Bardin 16597d4a55 Nested lists and maps fail in GetRaw
When referencing a list of maps variable from within a resource, only
the first list element is included the plan. This is because GetRaw
can't access the interpolated values. Add some tests to document this
behavior for both Get and GetRaw.
2016-11-28 09:04:12 -05:00

729 lines
14 KiB
Go

package terraform
import (
"fmt"
"reflect"
"testing"
"github.com/hashicorp/hil"
"github.com/hashicorp/hil/ast"
"github.com/hashicorp/terraform/config"
"github.com/mitchellh/reflectwalk"
)
func TestInstanceInfo(t *testing.T) {
cases := []struct {
Info *InstanceInfo
Result string
}{
{
&InstanceInfo{
Id: "foo",
},
"foo",
},
{
&InstanceInfo{
Id: "foo",
ModulePath: rootModulePath,
},
"foo",
},
{
&InstanceInfo{
Id: "foo",
ModulePath: []string{"root", "consul"},
},
"module.consul.foo",
},
}
for i, tc := range cases {
actual := tc.Info.HumanId()
if actual != tc.Result {
t.Fatalf("%d: %s", i, actual)
}
}
}
func TestResourceConfigGet(t *testing.T) {
cases := []struct {
Config map[string]interface{}
Vars map[string]interface{}
Key string
Value interface{}
}{
{
Config: nil,
Key: "foo",
Value: nil,
},
{
Config: map[string]interface{}{
"foo": "bar",
},
Key: "foo",
Value: "bar",
},
{
Config: map[string]interface{}{
"foo": "${var.foo}",
},
Key: "foo",
Value: "${var.foo}",
},
{
Config: map[string]interface{}{
"foo": "${var.foo}",
},
Vars: map[string]interface{}{"foo": unknownValue()},
Key: "foo",
Value: "${var.foo}",
},
{
Config: map[string]interface{}{
"foo": []interface{}{1, 2, 5},
},
Key: "foo.0",
Value: 1,
},
{
Config: map[string]interface{}{
"foo": []interface{}{1, 2, 5},
},
Key: "foo.5",
Value: nil,
},
// get from map
{
Config: map[string]interface{}{
"mapname": []map[string]interface{}{
map[string]interface{}{"key": 1},
},
},
Key: "mapname.0.key",
Value: 1,
},
// get from map with dot in key
{
Config: map[string]interface{}{
"mapname": []map[string]interface{}{
map[string]interface{}{"key.name": 1},
},
},
Key: "mapname.0.key.name",
Value: 1,
},
// get from map with overlapping key names
{
Config: map[string]interface{}{
"mapname": []map[string]interface{}{
map[string]interface{}{
"key.name": 1,
"key.name.2": 2,
},
},
},
Key: "mapname.0.key.name.2",
Value: 2,
},
{
Config: map[string]interface{}{
"mapname": []map[string]interface{}{
map[string]interface{}{
"key.name": 1,
"key.name.foo": 2,
},
},
},
Key: "mapname.0.key.name",
Value: 1,
},
{
Config: map[string]interface{}{
"mapname": []map[string]interface{}{
map[string]interface{}{
"listkey": []map[string]interface{}{
{"key": 3},
},
},
},
},
Key: "mapname.0.listkey.0.key",
Value: 3,
},
// A map assigned to a list via interpolation should Get a non-existent
// value. The test code now also checks that Get doesn't return (nil,
// true), which it previously did for this configuration.
{
Config: map[string]interface{}{
"maplist": "${var.maplist}",
},
Key: "maplist.0",
Value: nil,
},
// Reference list of maps variable.
// This does not work from GetRaw.
{
Vars: map[string]interface{}{
"maplist": []interface{}{
map[string]interface{}{
"key": "a",
},
map[string]interface{}{
"key": "b",
},
},
},
Config: map[string]interface{}{
"maplist": "${var.maplist}",
},
Key: "maplist.0",
Value: map[string]interface{}{"key": "a"},
},
// Reference a map-of-lists variable.
// This does not work from GetRaw.
{
Vars: map[string]interface{}{
"listmap": map[string]interface{}{
"key1": []interface{}{"a", "b"},
"key2": []interface{}{"c", "d"},
},
},
Config: map[string]interface{}{
"listmap": "${var.listmap}",
},
Key: "listmap.key1",
Value: []interface{}{"a", "b"},
},
// FIXME: this is ambiguous, and matches the nested map
// leaving here to catch this behaviour if it changes.
{
Config: map[string]interface{}{
"mapname": []map[string]interface{}{
map[string]interface{}{
"key.name": 1,
"key.name.0": 2,
"key": map[string]interface{}{"name": 3},
},
},
},
Key: "mapname.0.key.name",
Value: 3,
},
/*
// TODO: can't access this nested list at all.
// FIXME: key with name matching substring of nested list can panic
{
Config: map[string]interface{}{
"mapname": []map[string]interface{}{
map[string]interface{}{
"key.name": []map[string]interface{}{
{"subkey": 1},
},
"key": 3,
},
},
},
Key: "mapname.0.key.name.0.subkey",
Value: 3,
},
*/
}
for i, tc := range cases {
var rawC *config.RawConfig
if tc.Config != nil {
var err error
rawC, err = config.NewRawConfig(tc.Config)
if err != nil {
t.Fatalf("err: %s", err)
}
}
if tc.Vars != nil {
vs := make(map[string]ast.Variable)
for k, v := range tc.Vars {
hilVar, err := hil.InterfaceToVariable(v)
if err != nil {
t.Fatalf("%#v to var: %s", v, err)
}
vs["var."+k] = hilVar
}
if err := rawC.Interpolate(vs); err != nil {
t.Fatalf("err: %s", err)
}
}
rc := NewResourceConfig(rawC)
rc.interpolateForce()
// Test getting a key
t.Run(fmt.Sprintf("get-%d", i), func(t *testing.T) {
v, ok := rc.Get(tc.Key)
if ok && v == nil {
t.Fatal("(nil, true) returned from Get")
}
if !reflect.DeepEqual(v, tc.Value) {
t.Fatalf("%d bad: %#v", i, v)
}
})
// If we have vars, we don't test copying
if len(tc.Vars) > 0 {
continue
}
// Test copying and equality
t.Run(fmt.Sprintf("copy-and-equal-%d", i), func(t *testing.T) {
copy := rc.DeepCopy()
if !reflect.DeepEqual(copy, rc) {
t.Fatalf("bad:\n\n%#v\n\n%#v", copy, rc)
}
if !copy.Equal(rc) {
t.Fatalf("copy != rc:\n\n%#v\n\n%#v", copy, rc)
}
if !rc.Equal(copy) {
t.Fatalf("rc != copy:\n\n%#v\n\n%#v", copy, rc)
}
})
}
}
func TestResourceConfigGetRaw(t *testing.T) {
cases := []struct {
Config map[string]interface{}
Vars map[string]interface{}
Key string
Value interface{}
}{
// Referencing a list-of-maps variable doesn't work from GetRaw.
// The ConfigFieldReader currently catches this case and looks up the
// variable in the config.
{
Vars: map[string]interface{}{
"maplist": []interface{}{
map[string]interface{}{
"key": "a",
},
map[string]interface{}{
"key": "b",
},
},
},
Config: map[string]interface{}{
"maplist": "${var.maplist}",
},
Key: "maplist.0",
Value: nil,
},
// Reference a map-of-lists variable.
// The ConfigFieldReader currently catches this case and looks up the
// variable in the config.
{
Vars: map[string]interface{}{
"listmap": map[string]interface{}{
"key1": []interface{}{"a", "b"},
"key2": []interface{}{"c", "d"},
},
},
Config: map[string]interface{}{
"listmap": "${var.listmap}",
},
Key: "listmap.key1",
Value: nil,
},
}
for i, tc := range cases {
var rawC *config.RawConfig
if tc.Config != nil {
var err error
rawC, err = config.NewRawConfig(tc.Config)
if err != nil {
t.Fatalf("err: %s", err)
}
}
if tc.Vars != nil {
vs := make(map[string]ast.Variable)
for k, v := range tc.Vars {
hilVar, err := hil.InterfaceToVariable(v)
if err != nil {
t.Fatalf("%#v to var: %s", v, err)
}
vs["var."+k] = hilVar
}
if err := rawC.Interpolate(vs); err != nil {
t.Fatalf("err: %s", err)
}
}
rc := NewResourceConfig(rawC)
rc.interpolateForce()
// Test getting a key
t.Run(fmt.Sprintf("get-%d", i), func(t *testing.T) {
v, ok := rc.GetRaw(tc.Key)
if ok && v == nil {
t.Fatal("(nil, true) returned from GetRaw")
}
if !reflect.DeepEqual(v, tc.Value) {
t.Fatalf("%d bad: %#v", i, v)
}
})
}
}
func TestResourceConfigIsComputed(t *testing.T) {
cases := []struct {
Name string
Config map[string]interface{}
Vars map[string]interface{}
Key string
Result bool
}{
{
Name: "basic value",
Config: map[string]interface{}{
"foo": "${var.foo}",
},
Vars: map[string]interface{}{
"foo": unknownValue(),
},
Key: "foo",
Result: true,
},
{
Name: "set with a computed element",
Config: map[string]interface{}{
"foo": "${var.foo}",
},
Vars: map[string]interface{}{
"foo": []string{
"a",
unknownValue(),
},
},
Key: "foo",
Result: true,
},
{
Name: "set with no computed elements",
Config: map[string]interface{}{
"foo": "${var.foo}",
},
Vars: map[string]interface{}{
"foo": []string{
"a",
"b",
},
},
Key: "foo",
Result: false,
},
/*
{
Name: "set count with computed elements",
Config: map[string]interface{}{
"foo": "${var.foo}",
},
Vars: map[string]interface{}{
"foo": []string{
"a",
unknownValue(),
},
},
Key: "foo.#",
Result: true,
},
*/
{
Name: "set count with computed elements",
Config: map[string]interface{}{
"foo": []interface{}{"${var.foo}"},
},
Vars: map[string]interface{}{
"foo": []string{
"a",
unknownValue(),
},
},
Key: "foo.#",
Result: true,
},
{
Name: "nested set with computed elements",
Config: map[string]interface{}{
"route": []map[string]interface{}{
map[string]interface{}{
"index": "1",
"gateway": []interface{}{"${var.foo}"},
},
},
},
Vars: map[string]interface{}{
"foo": unknownValue(),
},
Key: "route.0.gateway",
Result: true,
},
}
for i, tc := range cases {
t.Run(fmt.Sprintf("%d-%s", i, tc.Name), func(t *testing.T) {
var rawC *config.RawConfig
if tc.Config != nil {
var err error
rawC, err = config.NewRawConfig(tc.Config)
if err != nil {
t.Fatalf("err: %s", err)
}
}
if tc.Vars != nil {
vs := make(map[string]ast.Variable)
for k, v := range tc.Vars {
hilVar, err := hil.InterfaceToVariable(v)
if err != nil {
t.Fatalf("%#v to var: %s", v, err)
}
vs["var."+k] = hilVar
}
if err := rawC.Interpolate(vs); err != nil {
t.Fatalf("err: %s", err)
}
}
rc := NewResourceConfig(rawC)
rc.interpolateForce()
t.Logf("Config: %#v", rc)
actual := rc.IsComputed(tc.Key)
if actual != tc.Result {
t.Fatalf("bad: %#v", actual)
}
})
}
}
func TestResourceConfigCheckSet(t *testing.T) {
cases := []struct {
Name string
Config map[string]interface{}
Vars map[string]interface{}
Input []string
Errs bool
}{
{
Name: "computed basic",
Config: map[string]interface{}{
"foo": "${var.foo}",
},
Vars: map[string]interface{}{
"foo": unknownValue(),
},
Input: []string{"foo"},
Errs: false,
},
{
Name: "basic",
Config: map[string]interface{}{
"foo": "bar",
},
Vars: nil,
Input: []string{"foo"},
Errs: false,
},
{
Name: "basic with not set",
Config: map[string]interface{}{
"foo": "bar",
},
Vars: nil,
Input: []string{"foo", "bar"},
Errs: true,
},
{
Name: "basic with one computed",
Config: map[string]interface{}{
"foo": "bar",
"bar": "${var.foo}",
},
Vars: map[string]interface{}{
"foo": unknownValue(),
},
Input: []string{"foo", "bar"},
Errs: false,
},
}
for i, tc := range cases {
t.Run(fmt.Sprintf("%d-%s", i, tc.Name), func(t *testing.T) {
var rawC *config.RawConfig
if tc.Config != nil {
var err error
rawC, err = config.NewRawConfig(tc.Config)
if err != nil {
t.Fatalf("err: %s", err)
}
}
if tc.Vars != nil {
vs := make(map[string]ast.Variable)
for k, v := range tc.Vars {
hilVar, err := hil.InterfaceToVariable(v)
if err != nil {
t.Fatalf("%#v to var: %s", v, err)
}
vs["var."+k] = hilVar
}
if err := rawC.Interpolate(vs); err != nil {
t.Fatalf("err: %s", err)
}
}
rc := NewResourceConfig(rawC)
rc.interpolateForce()
t.Logf("Config: %#v", rc)
errs := rc.CheckSet(tc.Input)
if tc.Errs != (len(errs) > 0) {
t.Fatalf("bad: %#v", errs)
}
})
}
}
func TestResourceConfigDeepCopy_nil(t *testing.T) {
var nilRc *ResourceConfig
actual := nilRc.DeepCopy()
if actual != nil {
t.Fatalf("bad: %#v", actual)
}
}
func TestResourceConfigDeepCopy_nilComputed(t *testing.T) {
rc := &ResourceConfig{}
actual := rc.DeepCopy()
if actual.ComputedKeys != nil {
t.Fatalf("bad: %#v", actual)
}
}
func TestResourceConfigEqual_nil(t *testing.T) {
var nilRc *ResourceConfig
notNil := NewResourceConfig(nil)
if nilRc.Equal(notNil) {
t.Fatal("should not be equal")
}
if notNil.Equal(nilRc) {
t.Fatal("should not be equal")
}
}
func TestResourceConfigEqual_computedKeyOrder(t *testing.T) {
c := map[string]interface{}{"foo": "${a.b.c}"}
rc := NewResourceConfig(config.TestRawConfig(t, c))
rc2 := NewResourceConfig(config.TestRawConfig(t, c))
// Set the computed keys manual
rc.ComputedKeys = []string{"foo", "bar"}
rc2.ComputedKeys = []string{"bar", "foo"}
if !rc.Equal(rc2) {
t.Fatal("should be equal")
}
}
func TestUnknownCheckWalker(t *testing.T) {
cases := []struct {
Name string
Input interface{}
Result bool
}{
{
"primitive",
42,
false,
},
{
"primitive computed",
unknownValue(),
true,
},
{
"list",
[]interface{}{"foo", unknownValue()},
true,
},
{
"nested list",
[]interface{}{
"foo",
[]interface{}{unknownValue()},
},
true,
},
}
for i, tc := range cases {
t.Run(fmt.Sprintf("%d-%s", i, tc.Name), func(t *testing.T) {
var w unknownCheckWalker
if err := reflectwalk.Walk(tc.Input, &w); err != nil {
t.Fatalf("err: %s", err)
}
if w.Unknown != tc.Result {
t.Fatalf("bad: %v", w.Unknown)
}
})
}
}
func testResourceConfig(
t *testing.T, c map[string]interface{}) *ResourceConfig {
raw, err := config.NewRawConfig(c)
if err != nil {
t.Fatalf("err: %s", err)
}
return NewResourceConfig(raw)
}