mirror of
https://github.com/hashicorp/terraform.git
synced 2026-03-21 18:10:30 -04:00
278 lines
9.3 KiB
Go
278 lines
9.3 KiB
Go
// Copyright IBM Corp. 2014, 2026
|
|
// SPDX-License-Identifier: BUSL-1.1
|
|
|
|
package stackstate
|
|
|
|
import (
|
|
"encoding/json"
|
|
"testing"
|
|
|
|
"github.com/google/go-cmp/cmp"
|
|
"github.com/zclconf/go-cty/cty"
|
|
ctymsgpack "github.com/zclconf/go-cty/cty/msgpack"
|
|
"google.golang.org/protobuf/proto"
|
|
"google.golang.org/protobuf/testing/protocmp"
|
|
"google.golang.org/protobuf/types/known/anypb"
|
|
|
|
"github.com/hashicorp/terraform/internal/addrs"
|
|
"github.com/hashicorp/terraform/internal/configs/configschema"
|
|
"github.com/hashicorp/terraform/internal/plans/planproto"
|
|
"github.com/hashicorp/terraform/internal/providers"
|
|
"github.com/hashicorp/terraform/internal/rpcapi/terraform1/stacks"
|
|
"github.com/hashicorp/terraform/internal/stacks/stackaddrs"
|
|
"github.com/hashicorp/terraform/internal/stacks/tfstackdata1"
|
|
"github.com/hashicorp/terraform/internal/states"
|
|
)
|
|
|
|
func TestAppliedChangeAsProto(t *testing.T) {
|
|
tests := map[string]struct {
|
|
Receiver AppliedChange
|
|
Want *stacks.AppliedChange
|
|
}{
|
|
"resource instance": {
|
|
Receiver: &AppliedChangeResourceInstanceObject{
|
|
ResourceInstanceObjectAddr: stackaddrs.AbsResourceInstanceObject{
|
|
Component: stackaddrs.AbsComponentInstance{
|
|
Stack: stackaddrs.RootStackInstance.Child("a", addrs.StringKey("boop")),
|
|
Item: stackaddrs.ComponentInstance{
|
|
Component: stackaddrs.Component{Name: "foo"},
|
|
Key: addrs.StringKey("beep"),
|
|
},
|
|
},
|
|
Item: addrs.AbsResourceInstanceObject{
|
|
ResourceInstance: addrs.Resource{
|
|
Mode: addrs.ManagedResourceMode,
|
|
Type: "thingy",
|
|
Name: "thingamajig",
|
|
}.Instance(addrs.IntKey(1)).Absolute(
|
|
addrs.RootModuleInstance.Child("pizza", addrs.StringKey("chicken")),
|
|
),
|
|
},
|
|
},
|
|
ProviderConfigAddr: addrs.AbsProviderConfig{
|
|
Module: addrs.RootModule,
|
|
Provider: addrs.MustParseProviderSourceString("example.com/thingers/thingy"),
|
|
},
|
|
Schema: providers.Schema{
|
|
Body: &configschema.Block{
|
|
Attributes: map[string]*configschema.Attribute{
|
|
"id": {
|
|
Type: cty.String,
|
|
Required: true,
|
|
},
|
|
"secret": {
|
|
Type: cty.String,
|
|
Sensitive: true,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
NewStateSrc: &states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectReady,
|
|
AttrsJSON: []byte(`{"id":"bar","secret":"top"}`),
|
|
AttrSensitivePaths: []cty.Path{
|
|
cty.GetAttrPath("secret"),
|
|
},
|
|
},
|
|
},
|
|
Want: &stacks.AppliedChange{
|
|
Raw: []*stacks.AppliedChange_RawChange{
|
|
{
|
|
Key: `RSRCstack.a["boop"].component.foo["beep"],module.pizza["chicken"].thingy.thingamajig[1],cur`,
|
|
Value: mustMarshalAnyPb(t, &tfstackdata1.StateResourceInstanceObjectV1{
|
|
ValueJson: json.RawMessage(`{"id":"bar","secret":"top"}`),
|
|
SensitivePaths: []*planproto.Path{
|
|
{
|
|
Steps: []*planproto.Path_Step{{
|
|
Selector: &planproto.Path_Step_AttributeName{AttributeName: "secret"}}},
|
|
},
|
|
},
|
|
ProviderConfigAddr: `provider["example.com/thingers/thingy"]`,
|
|
Status: tfstackdata1.StateResourceInstanceObjectV1_READY,
|
|
}),
|
|
},
|
|
},
|
|
Descriptions: []*stacks.AppliedChange_ChangeDescription{
|
|
{
|
|
Key: `RSRCstack.a["boop"].component.foo["beep"],module.pizza["chicken"].thingy.thingamajig[1],cur`,
|
|
Description: &stacks.AppliedChange_ChangeDescription_ResourceInstance{
|
|
ResourceInstance: &stacks.AppliedChange_ResourceInstance{
|
|
Addr: &stacks.ResourceInstanceObjectInStackAddr{
|
|
ComponentInstanceAddr: `stack.a["boop"].component.foo["beep"]`,
|
|
ResourceInstanceAddr: `module.pizza["chicken"].thingy.thingamajig[1]`,
|
|
},
|
|
NewValue: &stacks.DynamicValue{
|
|
Msgpack: mustMsgpack(t, cty.ObjectVal(map[string]cty.Value{
|
|
"id": cty.StringVal("bar"),
|
|
"secret": cty.StringVal("top"),
|
|
}), cty.Object(map[string]cty.Type{"id": cty.String, "secret": cty.String})),
|
|
Sensitive: []*stacks.AttributePath{{
|
|
Steps: []*stacks.AttributePath_Step{{
|
|
Selector: &stacks.AttributePath_Step_AttributeName{AttributeName: "secret"},
|
|
}}},
|
|
},
|
|
},
|
|
ResourceMode: stacks.ResourceMode_MANAGED,
|
|
ResourceType: "thingy",
|
|
ProviderAddr: "example.com/thingers/thingy",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
"moved_resource instance": {
|
|
Receiver: &AppliedChangeResourceInstanceObject{
|
|
ResourceInstanceObjectAddr: stackaddrs.AbsResourceInstanceObject{
|
|
Component: stackaddrs.AbsComponentInstance{
|
|
Stack: stackaddrs.RootStackInstance.Child("a", addrs.StringKey("boop")),
|
|
Item: stackaddrs.ComponentInstance{
|
|
Component: stackaddrs.Component{Name: "foo"},
|
|
Key: addrs.StringKey("beep"),
|
|
},
|
|
},
|
|
Item: addrs.AbsResourceInstanceObject{
|
|
ResourceInstance: addrs.Resource{
|
|
Mode: addrs.ManagedResourceMode,
|
|
Type: "thingy",
|
|
Name: "thingamajig",
|
|
}.Instance(addrs.IntKey(1)).Absolute(
|
|
addrs.RootModuleInstance.Child("pizza", addrs.StringKey("chicken")),
|
|
),
|
|
},
|
|
},
|
|
PreviousResourceInstanceObjectAddr: &stackaddrs.AbsResourceInstanceObject{
|
|
Component: stackaddrs.AbsComponentInstance{
|
|
Stack: stackaddrs.RootStackInstance.Child("a", addrs.StringKey("boop")),
|
|
Item: stackaddrs.ComponentInstance{
|
|
Component: stackaddrs.Component{Name: "foo"},
|
|
Key: addrs.StringKey("beep"),
|
|
},
|
|
},
|
|
Item: addrs.AbsResourceInstanceObject{
|
|
ResourceInstance: addrs.Resource{
|
|
Mode: addrs.ManagedResourceMode,
|
|
Type: "thingy",
|
|
Name: "previous_thingamajig",
|
|
}.Instance(addrs.IntKey(1)).Absolute(
|
|
addrs.RootModuleInstance.Child("pizza", addrs.StringKey("chicken")),
|
|
),
|
|
},
|
|
},
|
|
ProviderConfigAddr: addrs.AbsProviderConfig{
|
|
Module: addrs.RootModule,
|
|
Provider: addrs.MustParseProviderSourceString("example.com/thingers/thingy"),
|
|
},
|
|
Schema: providers.Schema{
|
|
Body: &configschema.Block{
|
|
Attributes: map[string]*configschema.Attribute{
|
|
"id": {
|
|
Type: cty.String,
|
|
Required: true,
|
|
},
|
|
"secret": {
|
|
Type: cty.String,
|
|
Sensitive: true,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
NewStateSrc: &states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectReady,
|
|
AttrsJSON: []byte(`{"id":"bar","secret":"top"}`),
|
|
AttrSensitivePaths: []cty.Path{
|
|
cty.GetAttrPath("secret"),
|
|
},
|
|
},
|
|
},
|
|
Want: &stacks.AppliedChange{
|
|
Raw: []*stacks.AppliedChange_RawChange{
|
|
{
|
|
Key: `RSRCstack.a["boop"].component.foo["beep"],module.pizza["chicken"].thingy.previous_thingamajig[1],cur`,
|
|
Value: nil,
|
|
},
|
|
{
|
|
Key: `RSRCstack.a["boop"].component.foo["beep"],module.pizza["chicken"].thingy.thingamajig[1],cur`,
|
|
Value: mustMarshalAnyPb(t, &tfstackdata1.StateResourceInstanceObjectV1{
|
|
ValueJson: json.RawMessage(`{"id":"bar","secret":"top"}`),
|
|
SensitivePaths: []*planproto.Path{
|
|
{
|
|
Steps: []*planproto.Path_Step{{
|
|
Selector: &planproto.Path_Step_AttributeName{AttributeName: "secret"}}},
|
|
},
|
|
},
|
|
ProviderConfigAddr: `provider["example.com/thingers/thingy"]`,
|
|
Status: tfstackdata1.StateResourceInstanceObjectV1_READY,
|
|
}),
|
|
},
|
|
},
|
|
Descriptions: []*stacks.AppliedChange_ChangeDescription{
|
|
{
|
|
Key: `RSRCstack.a["boop"].component.foo["beep"],module.pizza["chicken"].thingy.previous_thingamajig[1],cur`,
|
|
Description: &stacks.AppliedChange_ChangeDescription_Moved{
|
|
Moved: &stacks.AppliedChange_Nothing{},
|
|
},
|
|
},
|
|
{
|
|
Key: `RSRCstack.a["boop"].component.foo["beep"],module.pizza["chicken"].thingy.thingamajig[1],cur`,
|
|
Description: &stacks.AppliedChange_ChangeDescription_ResourceInstance{
|
|
ResourceInstance: &stacks.AppliedChange_ResourceInstance{
|
|
Addr: &stacks.ResourceInstanceObjectInStackAddr{
|
|
ComponentInstanceAddr: `stack.a["boop"].component.foo["beep"]`,
|
|
ResourceInstanceAddr: `module.pizza["chicken"].thingy.thingamajig[1]`,
|
|
},
|
|
NewValue: &stacks.DynamicValue{
|
|
Msgpack: mustMsgpack(t, cty.ObjectVal(map[string]cty.Value{
|
|
"id": cty.StringVal("bar"),
|
|
"secret": cty.StringVal("top"),
|
|
}), cty.Object(map[string]cty.Type{"id": cty.String, "secret": cty.String})),
|
|
Sensitive: []*stacks.AttributePath{{
|
|
Steps: []*stacks.AttributePath_Step{{
|
|
Selector: &stacks.AttributePath_Step_AttributeName{AttributeName: "secret"},
|
|
}}},
|
|
},
|
|
},
|
|
ResourceMode: stacks.ResourceMode_MANAGED,
|
|
ResourceType: "thingy",
|
|
ProviderAddr: "example.com/thingers/thingy",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
for name, test := range tests {
|
|
t.Run(name, func(t *testing.T) {
|
|
got, err := test.Receiver.AppliedChangeProto()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if diff := cmp.Diff(test.Want, got, protocmp.Transform()); diff != "" {
|
|
t.Errorf("wrong result\n%s", diff)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func mustMarshalAnyPb(t *testing.T, msg proto.Message) *anypb.Any {
|
|
var ret anypb.Any
|
|
err := anypb.MarshalFrom(&ret, msg, proto.MarshalOptions{})
|
|
if err != nil {
|
|
t.Fatalf("error marshalling anypb: %q", err)
|
|
}
|
|
return &ret
|
|
}
|
|
|
|
func mustMsgpack(t *testing.T, v cty.Value, ty cty.Type) []byte {
|
|
t.Helper()
|
|
|
|
ret, err := ctymsgpack.Marshal(v, ty)
|
|
if err != nil {
|
|
t.Fatalf("error marshalling %#v: %s", v, err)
|
|
}
|
|
|
|
return ret
|
|
}
|