mirror of
https://github.com/opentffoundation/opentf.git
synced 2026-03-05 02:47:43 -05:00
This is part of a general effort to move all of Terraform's non-library package surface under internal in order to reinforce that these are for internal use within Terraform only. If you were previously importing packages under this prefix into an external codebase, you could pin to an earlier release tag as an interim solution until you've make a plan to achieve the same functionality some other way.
327 lines
7.3 KiB
Go
327 lines
7.3 KiB
Go
package jsonplan
|
|
|
|
import (
|
|
"encoding/json"
|
|
"reflect"
|
|
"testing"
|
|
|
|
"github.com/hashicorp/terraform/internal/addrs"
|
|
"github.com/hashicorp/terraform/internal/configs/configschema"
|
|
"github.com/hashicorp/terraform/internal/plans"
|
|
"github.com/hashicorp/terraform/internal/terraform"
|
|
"github.com/zclconf/go-cty/cty"
|
|
)
|
|
|
|
func TestMarshalAttributeValues(t *testing.T) {
|
|
tests := []struct {
|
|
Attr cty.Value
|
|
Schema *configschema.Block
|
|
Want attributeValues
|
|
}{
|
|
{
|
|
cty.NilVal,
|
|
&configschema.Block{
|
|
Attributes: map[string]*configschema.Attribute{
|
|
"foo": {
|
|
Type: cty.String,
|
|
Optional: true,
|
|
},
|
|
},
|
|
},
|
|
nil,
|
|
},
|
|
{
|
|
cty.NullVal(cty.String),
|
|
&configschema.Block{
|
|
Attributes: map[string]*configschema.Attribute{
|
|
"foo": {
|
|
Type: cty.String,
|
|
Optional: true,
|
|
},
|
|
},
|
|
},
|
|
nil,
|
|
},
|
|
{
|
|
cty.ObjectVal(map[string]cty.Value{
|
|
"foo": cty.StringVal("bar"),
|
|
}),
|
|
&configschema.Block{
|
|
Attributes: map[string]*configschema.Attribute{
|
|
"foo": {
|
|
Type: cty.String,
|
|
Optional: true,
|
|
},
|
|
},
|
|
},
|
|
attributeValues{"foo": json.RawMessage(`"bar"`)},
|
|
},
|
|
{
|
|
cty.ObjectVal(map[string]cty.Value{
|
|
"foo": cty.NullVal(cty.String),
|
|
}),
|
|
&configschema.Block{
|
|
Attributes: map[string]*configschema.Attribute{
|
|
"foo": {
|
|
Type: cty.String,
|
|
Optional: true,
|
|
},
|
|
},
|
|
},
|
|
attributeValues{"foo": json.RawMessage(`null`)},
|
|
},
|
|
{
|
|
cty.ObjectVal(map[string]cty.Value{
|
|
"bar": cty.MapVal(map[string]cty.Value{
|
|
"hello": cty.StringVal("world"),
|
|
}),
|
|
"baz": cty.ListVal([]cty.Value{
|
|
cty.StringVal("goodnight"),
|
|
cty.StringVal("moon"),
|
|
}),
|
|
}),
|
|
&configschema.Block{
|
|
Attributes: map[string]*configschema.Attribute{
|
|
"bar": {
|
|
Type: cty.Map(cty.String),
|
|
Required: true,
|
|
},
|
|
"baz": {
|
|
Type: cty.List(cty.String),
|
|
Optional: true,
|
|
},
|
|
},
|
|
},
|
|
attributeValues{
|
|
"bar": json.RawMessage(`{"hello":"world"}`),
|
|
"baz": json.RawMessage(`["goodnight","moon"]`),
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
got := marshalAttributeValues(test.Attr, test.Schema)
|
|
eq := reflect.DeepEqual(got, test.Want)
|
|
if !eq {
|
|
t.Fatalf("wrong result:\nGot: %#v\nWant: %#v\n", got, test.Want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestMarshalPlannedOutputs(t *testing.T) {
|
|
after, _ := plans.NewDynamicValue(cty.StringVal("after"), cty.DynamicPseudoType)
|
|
|
|
tests := []struct {
|
|
Changes *plans.Changes
|
|
Want map[string]output
|
|
Err bool
|
|
}{
|
|
{
|
|
&plans.Changes{},
|
|
nil,
|
|
false,
|
|
},
|
|
{
|
|
&plans.Changes{
|
|
Outputs: []*plans.OutputChangeSrc{
|
|
{
|
|
Addr: addrs.OutputValue{Name: "bar"}.Absolute(addrs.RootModuleInstance),
|
|
ChangeSrc: plans.ChangeSrc{
|
|
Action: plans.Create,
|
|
After: after,
|
|
},
|
|
Sensitive: false,
|
|
},
|
|
},
|
|
},
|
|
map[string]output{
|
|
"bar": {
|
|
Sensitive: false,
|
|
Value: json.RawMessage(`"after"`),
|
|
},
|
|
},
|
|
false,
|
|
},
|
|
{ // Delete action
|
|
&plans.Changes{
|
|
Outputs: []*plans.OutputChangeSrc{
|
|
{
|
|
Addr: addrs.OutputValue{Name: "bar"}.Absolute(addrs.RootModuleInstance),
|
|
ChangeSrc: plans.ChangeSrc{
|
|
Action: plans.Delete,
|
|
},
|
|
Sensitive: false,
|
|
},
|
|
},
|
|
},
|
|
map[string]output{},
|
|
false,
|
|
},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
got, err := marshalPlannedOutputs(test.Changes)
|
|
if test.Err {
|
|
if err == nil {
|
|
t.Fatal("succeeded; want error")
|
|
}
|
|
return
|
|
} else if err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
|
|
eq := reflect.DeepEqual(got, test.Want)
|
|
if !eq {
|
|
t.Fatalf("wrong result:\nGot: %#v\nWant: %#v\n", got, test.Want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestMarshalPlanResources(t *testing.T) {
|
|
tests := map[string]struct {
|
|
Action plans.Action
|
|
Before cty.Value
|
|
After cty.Value
|
|
Want []resource
|
|
Err bool
|
|
}{
|
|
"create with unknowns": {
|
|
Action: plans.Create,
|
|
Before: cty.NullVal(cty.EmptyObject),
|
|
After: cty.ObjectVal(map[string]cty.Value{
|
|
"woozles": cty.UnknownVal(cty.String),
|
|
"foozles": cty.UnknownVal(cty.String),
|
|
}),
|
|
Want: []resource{resource{
|
|
Address: "test_thing.example",
|
|
Mode: "managed",
|
|
Type: "test_thing",
|
|
Name: "example",
|
|
Index: addrs.InstanceKey(nil),
|
|
ProviderName: "registry.terraform.io/hashicorp/test",
|
|
SchemaVersion: 1,
|
|
AttributeValues: attributeValues{},
|
|
}},
|
|
Err: false,
|
|
},
|
|
"delete": {
|
|
Action: plans.Delete,
|
|
Before: cty.NullVal(cty.EmptyObject),
|
|
After: cty.NilVal,
|
|
Want: nil,
|
|
Err: false,
|
|
},
|
|
"update without unknowns": {
|
|
Action: plans.Update,
|
|
Before: cty.ObjectVal(map[string]cty.Value{
|
|
"woozles": cty.StringVal("foo"),
|
|
"foozles": cty.StringVal("bar"),
|
|
}),
|
|
After: cty.ObjectVal(map[string]cty.Value{
|
|
"woozles": cty.StringVal("baz"),
|
|
"foozles": cty.StringVal("bat"),
|
|
}),
|
|
Want: []resource{resource{
|
|
Address: "test_thing.example",
|
|
Mode: "managed",
|
|
Type: "test_thing",
|
|
Name: "example",
|
|
Index: addrs.InstanceKey(nil),
|
|
ProviderName: "registry.terraform.io/hashicorp/test",
|
|
SchemaVersion: 1,
|
|
AttributeValues: attributeValues{
|
|
|
|
"woozles": json.RawMessage(`"baz"`),
|
|
"foozles": json.RawMessage(`"bat"`),
|
|
},
|
|
}},
|
|
Err: false,
|
|
},
|
|
}
|
|
|
|
for name, test := range tests {
|
|
t.Run(name, func(t *testing.T) {
|
|
before, err := plans.NewDynamicValue(test.Before, test.Before.Type())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
after, err := plans.NewDynamicValue(test.After, test.After.Type())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
testChange := &plans.Changes{
|
|
Resources: []*plans.ResourceInstanceChangeSrc{
|
|
{
|
|
Addr: addrs.Resource{
|
|
Mode: addrs.ManagedResourceMode,
|
|
Type: "test_thing",
|
|
Name: "example",
|
|
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
|
|
ProviderAddr: addrs.AbsProviderConfig{
|
|
Provider: addrs.NewDefaultProvider("test"),
|
|
Module: addrs.RootModule,
|
|
},
|
|
ChangeSrc: plans.ChangeSrc{
|
|
Action: test.Action,
|
|
Before: before,
|
|
After: after,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
ris := testResourceAddrs()
|
|
|
|
got, err := marshalPlanResources(testChange, ris, testSchemas())
|
|
if test.Err {
|
|
if err == nil {
|
|
t.Fatal("succeeded; want error")
|
|
}
|
|
return
|
|
} else if err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
|
|
eq := reflect.DeepEqual(got, test.Want)
|
|
if !eq {
|
|
t.Fatalf("wrong result:\nGot: %#v\nWant: %#v\n", got, test.Want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func testSchemas() *terraform.Schemas {
|
|
return &terraform.Schemas{
|
|
Providers: map[addrs.Provider]*terraform.ProviderSchema{
|
|
addrs.NewDefaultProvider("test"): &terraform.ProviderSchema{
|
|
ResourceTypes: map[string]*configschema.Block{
|
|
"test_thing": {
|
|
Attributes: map[string]*configschema.Attribute{
|
|
"woozles": {Type: cty.String, Optional: true, Computed: true},
|
|
"foozles": {Type: cty.String, Optional: true},
|
|
},
|
|
},
|
|
},
|
|
ResourceTypeSchemaVersions: map[string]uint64{
|
|
"test_thing": 1,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
func testResourceAddrs() []addrs.AbsResourceInstance {
|
|
return []addrs.AbsResourceInstance{
|
|
mustAddr("test_thing.example"),
|
|
}
|
|
}
|
|
|
|
func mustAddr(str string) addrs.AbsResourceInstance {
|
|
addr, diags := addrs.ParseAbsResourceInstanceStr(str)
|
|
if diags.HasErrors() {
|
|
panic(diags.Err())
|
|
}
|
|
return addr
|
|
}
|