675 lines
16 KiB
Go
675 lines
16 KiB
Go
package terraform
|
|
|
|
import (
|
|
"fmt"
|
|
"reflect"
|
|
"testing"
|
|
|
|
"github.com/hashicorp/terraform/internal/configs/configschema"
|
|
"github.com/zclconf/go-cty/cty"
|
|
|
|
"github.com/hashicorp/terraform/internal/configs/hcl2shim"
|
|
"github.com/mitchellh/reflectwalk"
|
|
)
|
|
|
|
func TestResourceConfigGet(t *testing.T) {
|
|
fooStringSchema := &configschema.Block{
|
|
Attributes: map[string]*configschema.Attribute{
|
|
"foo": {Type: cty.String, Optional: true},
|
|
},
|
|
}
|
|
fooListSchema := &configschema.Block{
|
|
Attributes: map[string]*configschema.Attribute{
|
|
"foo": {Type: cty.List(cty.Number), Optional: true},
|
|
},
|
|
}
|
|
|
|
cases := []struct {
|
|
Config cty.Value
|
|
Schema *configschema.Block
|
|
Key string
|
|
Value interface{}
|
|
}{
|
|
{
|
|
Config: cty.ObjectVal(map[string]cty.Value{
|
|
"foo": cty.StringVal("bar"),
|
|
}),
|
|
Schema: fooStringSchema,
|
|
Key: "foo",
|
|
Value: "bar",
|
|
},
|
|
|
|
{
|
|
Config: cty.ObjectVal(map[string]cty.Value{
|
|
"foo": cty.UnknownVal(cty.String),
|
|
}),
|
|
Schema: fooStringSchema,
|
|
Key: "foo",
|
|
Value: hcl2shim.UnknownVariableValue,
|
|
},
|
|
|
|
{
|
|
Config: cty.ObjectVal(map[string]cty.Value{
|
|
"foo": cty.ListVal([]cty.Value{
|
|
cty.NumberIntVal(1),
|
|
cty.NumberIntVal(2),
|
|
cty.NumberIntVal(5),
|
|
}),
|
|
}),
|
|
Schema: fooListSchema,
|
|
Key: "foo.0",
|
|
Value: 1,
|
|
},
|
|
|
|
{
|
|
Config: cty.ObjectVal(map[string]cty.Value{
|
|
"foo": cty.ListVal([]cty.Value{
|
|
cty.NumberIntVal(1),
|
|
cty.NumberIntVal(2),
|
|
cty.NumberIntVal(5),
|
|
}),
|
|
}),
|
|
Schema: fooListSchema,
|
|
Key: "foo.5",
|
|
Value: nil,
|
|
},
|
|
|
|
{
|
|
Config: cty.ObjectVal(map[string]cty.Value{
|
|
"foo": cty.ListVal([]cty.Value{
|
|
cty.NumberIntVal(1),
|
|
cty.NumberIntVal(2),
|
|
cty.NumberIntVal(5),
|
|
}),
|
|
}),
|
|
Schema: fooListSchema,
|
|
Key: "foo.-1",
|
|
Value: nil,
|
|
},
|
|
|
|
// get from map
|
|
{
|
|
Config: cty.ObjectVal(map[string]cty.Value{
|
|
"mapname": cty.ListVal([]cty.Value{
|
|
cty.MapVal(map[string]cty.Value{
|
|
"key": cty.NumberIntVal(1),
|
|
}),
|
|
}),
|
|
}),
|
|
Schema: &configschema.Block{
|
|
Attributes: map[string]*configschema.Attribute{
|
|
"mapname": {Type: cty.List(cty.Map(cty.Number)), Optional: true},
|
|
},
|
|
},
|
|
Key: "mapname.0.key",
|
|
Value: 1,
|
|
},
|
|
|
|
// get from map with dot in key
|
|
{
|
|
Config: cty.ObjectVal(map[string]cty.Value{
|
|
"mapname": cty.ListVal([]cty.Value{
|
|
cty.MapVal(map[string]cty.Value{
|
|
"key.name": cty.NumberIntVal(1),
|
|
}),
|
|
}),
|
|
}),
|
|
Schema: &configschema.Block{
|
|
Attributes: map[string]*configschema.Attribute{
|
|
"mapname": {Type: cty.List(cty.Map(cty.Number)), Optional: true},
|
|
},
|
|
},
|
|
Key: "mapname.0.key.name",
|
|
Value: 1,
|
|
},
|
|
|
|
// get from map with overlapping key names
|
|
{
|
|
Config: cty.ObjectVal(map[string]cty.Value{
|
|
"mapname": cty.ListVal([]cty.Value{
|
|
cty.MapVal(map[string]cty.Value{
|
|
"key.name": cty.NumberIntVal(1),
|
|
"key.name.2": cty.NumberIntVal(2),
|
|
}),
|
|
}),
|
|
}),
|
|
Schema: &configschema.Block{
|
|
Attributes: map[string]*configschema.Attribute{
|
|
"mapname": {Type: cty.List(cty.Map(cty.Number)), Optional: true},
|
|
},
|
|
},
|
|
Key: "mapname.0.key.name.2",
|
|
Value: 2,
|
|
},
|
|
{
|
|
Config: cty.ObjectVal(map[string]cty.Value{
|
|
"mapname": cty.ListVal([]cty.Value{
|
|
cty.MapVal(map[string]cty.Value{
|
|
"key.name": cty.NumberIntVal(1),
|
|
"key.name.foo": cty.NumberIntVal(2),
|
|
}),
|
|
}),
|
|
}),
|
|
Schema: &configschema.Block{
|
|
Attributes: map[string]*configschema.Attribute{
|
|
"mapname": {Type: cty.List(cty.Map(cty.Number)), Optional: true},
|
|
},
|
|
},
|
|
Key: "mapname.0.key.name",
|
|
Value: 1,
|
|
},
|
|
{
|
|
Config: cty.ObjectVal(map[string]cty.Value{
|
|
"mapname": cty.ListVal([]cty.Value{
|
|
cty.MapVal(map[string]cty.Value{
|
|
"listkey": cty.ListVal([]cty.Value{
|
|
cty.MapVal(map[string]cty.Value{
|
|
"key": cty.NumberIntVal(3),
|
|
}),
|
|
}),
|
|
}),
|
|
}),
|
|
}),
|
|
Schema: &configschema.Block{
|
|
Attributes: map[string]*configschema.Attribute{
|
|
"mapname": {Type: cty.List(cty.Map(cty.List(cty.Map(cty.Number)))), Optional: true},
|
|
},
|
|
},
|
|
Key: "mapname.0.listkey.0.key",
|
|
Value: 3,
|
|
},
|
|
}
|
|
|
|
for i, tc := range cases {
|
|
rc := NewResourceConfigShimmed(tc.Config, tc.Schema)
|
|
|
|
// 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)
|
|
}
|
|
})
|
|
|
|
// 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 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 := NewResourceConfigShimmed(cty.EmptyObjectVal, &configschema.Block{})
|
|
|
|
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) {
|
|
v := cty.ObjectVal(map[string]cty.Value{
|
|
"foo": cty.UnknownVal(cty.String),
|
|
})
|
|
schema := &configschema.Block{
|
|
Attributes: map[string]*configschema.Attribute{
|
|
"foo": {Type: cty.String, Optional: true},
|
|
},
|
|
}
|
|
rc := NewResourceConfigShimmed(v, schema)
|
|
rc2 := NewResourceConfigShimmed(v, schema)
|
|
|
|
// Set the computed keys manually to force ordering to differ
|
|
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",
|
|
hcl2shim.UnknownVariableValue,
|
|
true,
|
|
},
|
|
|
|
{
|
|
"list",
|
|
[]interface{}{"foo", hcl2shim.UnknownVariableValue},
|
|
true,
|
|
},
|
|
|
|
{
|
|
"nested list",
|
|
[]interface{}{
|
|
"foo",
|
|
[]interface{}{hcl2shim.UnknownVariableValue},
|
|
},
|
|
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 TestNewResourceConfigShimmed(t *testing.T) {
|
|
for _, tc := range []struct {
|
|
Name string
|
|
Val cty.Value
|
|
Schema *configschema.Block
|
|
Expected *ResourceConfig
|
|
}{
|
|
{
|
|
Name: "empty object",
|
|
Val: cty.NullVal(cty.EmptyObject),
|
|
Schema: &configschema.Block{
|
|
Attributes: map[string]*configschema.Attribute{
|
|
"foo": {
|
|
Type: cty.String,
|
|
Optional: true,
|
|
},
|
|
},
|
|
},
|
|
Expected: &ResourceConfig{
|
|
Raw: map[string]interface{}{},
|
|
Config: map[string]interface{}{},
|
|
},
|
|
},
|
|
{
|
|
Name: "basic",
|
|
Val: cty.ObjectVal(map[string]cty.Value{
|
|
"foo": cty.StringVal("bar"),
|
|
}),
|
|
Schema: &configschema.Block{
|
|
Attributes: map[string]*configschema.Attribute{
|
|
"foo": {
|
|
Type: cty.String,
|
|
Optional: true,
|
|
},
|
|
},
|
|
},
|
|
Expected: &ResourceConfig{
|
|
Raw: map[string]interface{}{
|
|
"foo": "bar",
|
|
},
|
|
Config: map[string]interface{}{
|
|
"foo": "bar",
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Name: "null string",
|
|
Val: cty.ObjectVal(map[string]cty.Value{
|
|
"foo": cty.NullVal(cty.String),
|
|
}),
|
|
Schema: &configschema.Block{
|
|
Attributes: map[string]*configschema.Attribute{
|
|
"foo": {
|
|
Type: cty.String,
|
|
Optional: true,
|
|
},
|
|
},
|
|
},
|
|
Expected: &ResourceConfig{
|
|
Raw: map[string]interface{}{},
|
|
Config: map[string]interface{}{},
|
|
},
|
|
},
|
|
{
|
|
Name: "unknown string",
|
|
Val: cty.ObjectVal(map[string]cty.Value{
|
|
"foo": cty.UnknownVal(cty.String),
|
|
}),
|
|
Schema: &configschema.Block{
|
|
Attributes: map[string]*configschema.Attribute{
|
|
"foo": {
|
|
Type: cty.String,
|
|
Optional: true,
|
|
},
|
|
},
|
|
},
|
|
Expected: &ResourceConfig{
|
|
ComputedKeys: []string{"foo"},
|
|
Raw: map[string]interface{}{
|
|
"foo": hcl2shim.UnknownVariableValue,
|
|
},
|
|
Config: map[string]interface{}{
|
|
"foo": hcl2shim.UnknownVariableValue,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Name: "unknown collections",
|
|
Val: cty.ObjectVal(map[string]cty.Value{
|
|
"bar": cty.UnknownVal(cty.Map(cty.String)),
|
|
"baz": cty.UnknownVal(cty.List(cty.String)),
|
|
}),
|
|
Schema: &configschema.Block{
|
|
Attributes: map[string]*configschema.Attribute{
|
|
"bar": {
|
|
Type: cty.Map(cty.String),
|
|
Required: true,
|
|
},
|
|
"baz": {
|
|
Type: cty.List(cty.String),
|
|
Optional: true,
|
|
},
|
|
},
|
|
},
|
|
Expected: &ResourceConfig{
|
|
ComputedKeys: []string{"bar", "baz"},
|
|
Raw: map[string]interface{}{
|
|
"bar": hcl2shim.UnknownVariableValue,
|
|
"baz": hcl2shim.UnknownVariableValue,
|
|
},
|
|
Config: map[string]interface{}{
|
|
"bar": hcl2shim.UnknownVariableValue,
|
|
"baz": hcl2shim.UnknownVariableValue,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Name: "null collections",
|
|
Val: cty.ObjectVal(map[string]cty.Value{
|
|
"bar": cty.NullVal(cty.Map(cty.String)),
|
|
"baz": cty.NullVal(cty.List(cty.String)),
|
|
}),
|
|
Schema: &configschema.Block{
|
|
Attributes: map[string]*configschema.Attribute{
|
|
"bar": {
|
|
Type: cty.Map(cty.String),
|
|
Required: true,
|
|
},
|
|
"baz": {
|
|
Type: cty.List(cty.String),
|
|
Optional: true,
|
|
},
|
|
},
|
|
},
|
|
Expected: &ResourceConfig{
|
|
Raw: map[string]interface{}{},
|
|
Config: map[string]interface{}{},
|
|
},
|
|
},
|
|
{
|
|
Name: "unknown blocks",
|
|
Val: cty.ObjectVal(map[string]cty.Value{
|
|
"bar": cty.UnknownVal(cty.Map(cty.String)),
|
|
"baz": cty.UnknownVal(cty.List(cty.String)),
|
|
}),
|
|
Schema: &configschema.Block{
|
|
BlockTypes: map[string]*configschema.NestedBlock{
|
|
"bar": {
|
|
Block: configschema.Block{},
|
|
Nesting: configschema.NestingList,
|
|
},
|
|
"baz": {
|
|
Block: configschema.Block{},
|
|
Nesting: configschema.NestingSet,
|
|
},
|
|
},
|
|
},
|
|
Expected: &ResourceConfig{
|
|
ComputedKeys: []string{"bar", "baz"},
|
|
Raw: map[string]interface{}{
|
|
"bar": hcl2shim.UnknownVariableValue,
|
|
"baz": hcl2shim.UnknownVariableValue,
|
|
},
|
|
Config: map[string]interface{}{
|
|
"bar": hcl2shim.UnknownVariableValue,
|
|
"baz": hcl2shim.UnknownVariableValue,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Name: "unknown in nested blocks",
|
|
Val: cty.ObjectVal(map[string]cty.Value{
|
|
"bar": cty.ListVal([]cty.Value{
|
|
cty.ObjectVal(map[string]cty.Value{
|
|
"baz": cty.ListVal([]cty.Value{
|
|
cty.ObjectVal(map[string]cty.Value{
|
|
"list": cty.UnknownVal(cty.List(cty.String)),
|
|
}),
|
|
}),
|
|
}),
|
|
}),
|
|
}),
|
|
Schema: &configschema.Block{
|
|
BlockTypes: map[string]*configschema.NestedBlock{
|
|
"bar": {
|
|
Block: configschema.Block{
|
|
BlockTypes: map[string]*configschema.NestedBlock{
|
|
"baz": {
|
|
Block: configschema.Block{
|
|
Attributes: map[string]*configschema.Attribute{
|
|
"list": {Type: cty.List(cty.String),
|
|
Optional: true,
|
|
},
|
|
},
|
|
},
|
|
Nesting: configschema.NestingList,
|
|
},
|
|
},
|
|
},
|
|
Nesting: configschema.NestingList,
|
|
},
|
|
},
|
|
},
|
|
Expected: &ResourceConfig{
|
|
ComputedKeys: []string{"bar.0.baz.0.list"},
|
|
Raw: map[string]interface{}{
|
|
"bar": []interface{}{map[string]interface{}{
|
|
"baz": []interface{}{map[string]interface{}{
|
|
"list": "74D93920-ED26-11E3-AC10-0800200C9A66",
|
|
}},
|
|
}},
|
|
},
|
|
Config: map[string]interface{}{
|
|
"bar": []interface{}{map[string]interface{}{
|
|
"baz": []interface{}{map[string]interface{}{
|
|
"list": "74D93920-ED26-11E3-AC10-0800200C9A66",
|
|
}},
|
|
}},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Name: "unknown in set",
|
|
Val: cty.ObjectVal(map[string]cty.Value{
|
|
"bar": cty.SetVal([]cty.Value{
|
|
cty.ObjectVal(map[string]cty.Value{
|
|
"val": cty.UnknownVal(cty.String),
|
|
}),
|
|
}),
|
|
}),
|
|
Schema: &configschema.Block{
|
|
BlockTypes: map[string]*configschema.NestedBlock{
|
|
"bar": {
|
|
Block: configschema.Block{
|
|
Attributes: map[string]*configschema.Attribute{
|
|
"val": {
|
|
Type: cty.String,
|
|
Optional: true,
|
|
},
|
|
},
|
|
},
|
|
Nesting: configschema.NestingSet,
|
|
},
|
|
},
|
|
},
|
|
Expected: &ResourceConfig{
|
|
ComputedKeys: []string{"bar.0.val"},
|
|
Raw: map[string]interface{}{
|
|
"bar": []interface{}{map[string]interface{}{
|
|
"val": "74D93920-ED26-11E3-AC10-0800200C9A66",
|
|
}},
|
|
},
|
|
Config: map[string]interface{}{
|
|
"bar": []interface{}{map[string]interface{}{
|
|
"val": "74D93920-ED26-11E3-AC10-0800200C9A66",
|
|
}},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Name: "unknown in attribute sets",
|
|
Val: cty.ObjectVal(map[string]cty.Value{
|
|
"bar": cty.SetVal([]cty.Value{
|
|
cty.ObjectVal(map[string]cty.Value{
|
|
"val": cty.UnknownVal(cty.String),
|
|
}),
|
|
}),
|
|
"baz": cty.SetVal([]cty.Value{
|
|
cty.ObjectVal(map[string]cty.Value{
|
|
"obj": cty.UnknownVal(cty.Object(map[string]cty.Type{
|
|
"attr": cty.List(cty.String),
|
|
})),
|
|
}),
|
|
cty.ObjectVal(map[string]cty.Value{
|
|
"obj": cty.ObjectVal(map[string]cty.Value{
|
|
"attr": cty.UnknownVal(cty.List(cty.String)),
|
|
}),
|
|
}),
|
|
}),
|
|
}),
|
|
Schema: &configschema.Block{
|
|
Attributes: map[string]*configschema.Attribute{
|
|
"bar": &configschema.Attribute{
|
|
Type: cty.Set(cty.Object(map[string]cty.Type{
|
|
"val": cty.String,
|
|
})),
|
|
},
|
|
"baz": &configschema.Attribute{
|
|
Type: cty.Set(cty.Object(map[string]cty.Type{
|
|
"obj": cty.Object(map[string]cty.Type{
|
|
"attr": cty.List(cty.String),
|
|
}),
|
|
})),
|
|
},
|
|
},
|
|
},
|
|
Expected: &ResourceConfig{
|
|
ComputedKeys: []string{"bar.0.val", "baz.0.obj.attr", "baz.1.obj"},
|
|
Raw: map[string]interface{}{
|
|
"bar": []interface{}{map[string]interface{}{
|
|
"val": "74D93920-ED26-11E3-AC10-0800200C9A66",
|
|
}},
|
|
"baz": []interface{}{
|
|
map[string]interface{}{
|
|
"obj": map[string]interface{}{
|
|
"attr": "74D93920-ED26-11E3-AC10-0800200C9A66",
|
|
},
|
|
},
|
|
map[string]interface{}{
|
|
"obj": "74D93920-ED26-11E3-AC10-0800200C9A66",
|
|
},
|
|
},
|
|
},
|
|
Config: map[string]interface{}{
|
|
"bar": []interface{}{map[string]interface{}{
|
|
"val": "74D93920-ED26-11E3-AC10-0800200C9A66",
|
|
}},
|
|
"baz": []interface{}{
|
|
map[string]interface{}{
|
|
"obj": map[string]interface{}{
|
|
"attr": "74D93920-ED26-11E3-AC10-0800200C9A66",
|
|
},
|
|
},
|
|
map[string]interface{}{
|
|
"obj": "74D93920-ED26-11E3-AC10-0800200C9A66",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Name: "null blocks",
|
|
Val: cty.ObjectVal(map[string]cty.Value{
|
|
"bar": cty.NullVal(cty.Map(cty.String)),
|
|
"baz": cty.NullVal(cty.List(cty.String)),
|
|
}),
|
|
Schema: &configschema.Block{
|
|
BlockTypes: map[string]*configschema.NestedBlock{
|
|
"bar": {
|
|
Block: configschema.Block{},
|
|
Nesting: configschema.NestingMap,
|
|
},
|
|
"baz": {
|
|
Block: configschema.Block{},
|
|
Nesting: configschema.NestingSingle,
|
|
},
|
|
},
|
|
},
|
|
Expected: &ResourceConfig{
|
|
Raw: map[string]interface{}{},
|
|
Config: map[string]interface{}{},
|
|
},
|
|
},
|
|
} {
|
|
t.Run(tc.Name, func(*testing.T) {
|
|
cfg := NewResourceConfigShimmed(tc.Val, tc.Schema)
|
|
if !tc.Expected.Equal(cfg) {
|
|
t.Fatalf("expected:\n%#v\ngot:\n%#v", tc.Expected, cfg)
|
|
}
|
|
})
|
|
}
|
|
}
|