mirror of
https://github.com/opentofu/opentofu.git
synced 2025-02-20 11:48:24 -06:00
Our shims from new provider API to old can't populate the InstanceInfo fully since the new API only includes the type name, and so anyone depending on this method is now broken anyway. In practice only our own tests depend on this, and so we'll drop it to make it explicit that it no longer works (rather than having it return nonsense) and then fix up the remaining tests that were depending on it to use a different strategy.
760 lines
15 KiB
Go
760 lines
15 KiB
Go
package terraform
|
|
|
|
import (
|
|
"fmt"
|
|
"reflect"
|
|
"strconv"
|
|
"testing"
|
|
|
|
"github.com/hashicorp/hil"
|
|
"github.com/hashicorp/hil/ast"
|
|
"github.com/hashicorp/terraform/config"
|
|
"github.com/mitchellh/reflectwalk"
|
|
)
|
|
|
|
func TestInstanceInfoResourceAddress(t *testing.T) {
|
|
tests := []struct {
|
|
Input *InstanceInfo
|
|
Want string
|
|
}{
|
|
{
|
|
&InstanceInfo{
|
|
Id: "test_resource.baz",
|
|
},
|
|
"test_resource.baz",
|
|
},
|
|
{
|
|
&InstanceInfo{
|
|
Id: "test_resource.baz",
|
|
ModulePath: rootModulePath,
|
|
},
|
|
"test_resource.baz",
|
|
},
|
|
{
|
|
&InstanceInfo{
|
|
Id: "test_resource.baz",
|
|
ModulePath: []string{"root", "foo"},
|
|
},
|
|
"module.foo.test_resource.baz",
|
|
},
|
|
{
|
|
&InstanceInfo{
|
|
Id: "test_resource.baz",
|
|
ModulePath: []string{"root", "foo", "bar"},
|
|
},
|
|
"module.foo.module.bar.test_resource.baz",
|
|
},
|
|
{
|
|
&InstanceInfo{
|
|
Id: "test_resource.baz (tainted)",
|
|
},
|
|
"test_resource.baz.tainted",
|
|
},
|
|
{
|
|
&InstanceInfo{
|
|
Id: "test_resource.baz (deposed #0)",
|
|
},
|
|
"test_resource.baz.deposed",
|
|
},
|
|
}
|
|
|
|
for i, test := range tests {
|
|
t.Run(strconv.Itoa(i), func(t *testing.T) {
|
|
gotAddr := test.Input.ResourceAddress()
|
|
got := gotAddr.String()
|
|
if got != test.Want {
|
|
t.Fatalf("wrong result\ngot: %s\nwant: %s", got, test.Want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
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,
|
|
},
|
|
|
|
{
|
|
Config: map[string]interface{}{
|
|
"foo": []interface{}{1, 2, 5},
|
|
},
|
|
Key: "foo.-1",
|
|
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)
|
|
}
|