| // Copyright IBM Corp. 2014, 2026 | |
| // SPDX-License-Identifier: BUSL-1.1 | |
| package stackplan | |
| import ( | |
| "testing" | |
| "time" | |
| "github.com/google/go-cmp/cmp" | |
| "github.com/hashicorp/go-version" | |
| "github.com/zclconf/go-cty/cty" | |
| "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" | |
| "github.com/hashicorp/terraform/internal/configs/configschema" | |
| "github.com/hashicorp/terraform/internal/lang/marks" | |
| "github.com/hashicorp/terraform/internal/plans" | |
| "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" | |
| ) | |
| func TestPlannedChangeAsProto(t *testing.T) { | |
| emptyObjectForPlan, err := plans.NewDynamicValue(cty.EmptyObjectVal, cty.EmptyObject) | |
| if err != nil { | |
| t.Fatal(err) | |
| } | |
| nonEmptyType := cty.Map(cty.String) | |
| beforeObjectForPlan, err := plans.NewDynamicValue(cty.MapVal(map[string]cty.Value{ | |
| "foo": cty.StringVal("bar"), | |
| }), nonEmptyType) | |
| if err != nil { | |
| t.Fatal(err) | |
| } | |
| afterObjectForPlan, err := plans.NewDynamicValue(cty.MapVal(map[string]cty.Value{ | |
| "foo": cty.StringVal("baz"), | |
| }), nonEmptyType) | |
| if err != nil { | |
| t.Fatal(err) | |
| } | |
| nullObjectForPlan, err := plans.NewDynamicValue(cty.NullVal(cty.EmptyObject), cty.EmptyObject) | |
| if err != nil { | |
| t.Fatal(err) | |
| } | |
| fakePlanTimestamp, err := time.Parse(time.RFC3339, "2017-03-27T10:00:00-08:00") | |
| if err != nil { | |
| t.Fatal(err) | |
| } | |
| tests := map[string]struct { | |
| Receiver PlannedChange | |
| Want *stacks.PlannedChange | |
| WantErr string | |
| }{ | |
| "header": { | |
| Receiver: &PlannedChangeHeader{ | |
| TerraformVersion: version.Must(version.NewSemver("1.2.3-beta4")), | |
| }, | |
| Want: &stacks.PlannedChange{ | |
| Raw: []*anypb.Any{ | |
| mustMarshalAnyPb(&tfstackdata1.PlanHeader{ | |
| TerraformVersion: "1.2.3-beta4", | |
| }), | |
| }, | |
| }, | |
| }, | |
| "applyable true": { | |
| Receiver: &PlannedChangeApplyable{ | |
| Applyable: true, | |
| }, | |
| Want: &stacks.PlannedChange{ | |
| Raw: []*anypb.Any{ | |
| mustMarshalAnyPb(&tfstackdata1.PlanApplyable{ | |
| Applyable: true, | |
| }), | |
| }, | |
| Descriptions: []*stacks.PlannedChange_ChangeDescription{ | |
| { | |
| Description: &stacks.PlannedChange_ChangeDescription_PlanApplyable{ | |
| PlanApplyable: true, | |
| }, | |
| }, | |
| }, | |
| }, | |
| }, | |
| "applyable false": { | |
| Receiver: &PlannedChangeApplyable{ | |
| Applyable: false, | |
| }, | |
| Want: &stacks.PlannedChange{ | |
| Raw: []*anypb.Any{ | |
| mustMarshalAnyPb(&tfstackdata1.PlanApplyable{ | |
| // false is the default | |
| }), | |
| }, | |
| Descriptions: []*stacks.PlannedChange_ChangeDescription{ | |
| { | |
| Description: &stacks.PlannedChange_ChangeDescription_PlanApplyable{ | |
| PlanApplyable: false, | |
| }, | |
| }, | |
| }, | |
| }, | |
| }, | |
| "component instance create": { | |
| Receiver: &PlannedChangeComponentInstance{ | |
| Addr: stackaddrs.AbsComponentInstance{ | |
| Stack: stackaddrs.RootStackInstance, | |
| Item: stackaddrs.ComponentInstance{ | |
| Component: stackaddrs.Component{Name: "foo"}, | |
| }, | |
| }, | |
| Action: plans.Create, | |
| PlanTimestamp: fakePlanTimestamp, | |
| PlannedOutputValues: map[string]cty.Value{ | |
| "hello": cty.StringVal("world"), | |
| "goal_of_life": cty.UnknownVal(cty.String), | |
| }, | |
| }, | |
| Want: &stacks.PlannedChange{ | |
| Raw: []*anypb.Any{ | |
| mustMarshalAnyPb(&tfstackdata1.PlanComponentInstance{ | |
| ComponentInstanceAddr: "component.foo", | |
| PlanTimestamp: "2017-03-27T10:00:00-08:00", | |
| PlannedAction: planproto.Action_CREATE, | |
| PlannedOutputValues: map[string]*tfstackdata1.DynamicValue{ | |
| "hello": &tfstackdata1.DynamicValue{ | |
| Value: &planproto.DynamicValue{ | |
| Msgpack: mustMsgPack(t, cty.StringVal("world")), | |
| }, | |
| }, | |
| "goal_of_life": &tfstackdata1.DynamicValue{ | |
| Value: &planproto.DynamicValue{ | |
| Msgpack: mustMsgPack(t, cty.UnknownVal(cty.String)), | |
| }, | |
| }, | |
| }, | |
| }), | |
| }, | |
| Descriptions: []*stacks.PlannedChange_ChangeDescription{ | |
| { | |
| Description: &stacks.PlannedChange_ChangeDescription_ComponentInstancePlanned{ | |
| ComponentInstancePlanned: &stacks.PlannedChange_ComponentInstance{ | |
| Addr: &stacks.ComponentInstanceInStackAddr{ | |
| ComponentAddr: "component.foo", | |
| ComponentInstanceAddr: "component.foo", | |
| }, | |
| Actions: []stacks.ChangeType{stacks.ChangeType_CREATE}, | |
| OutputValues: map[string]*stacks.DynamicValue{ | |
| "hello": &stacks.DynamicValue{ | |
| Msgpack: mustMsgPack(t, cty.StringVal("world")), | |
| }, | |
| "goal_of_life": &stacks.DynamicValue{ | |
| Msgpack: mustMsgPack(t, cty.UnknownVal(cty.String)), | |
| }, | |
| }, | |
| }, | |
| }, | |
| }, | |
| }, | |
| }, | |
| }, | |
| "component instance noop": { | |
| Receiver: &PlannedChangeComponentInstance{ | |
| Addr: stackaddrs.AbsComponentInstance{ | |
| Stack: stackaddrs.RootStackInstance, | |
| Item: stackaddrs.ComponentInstance{ | |
| Component: stackaddrs.Component{Name: "foo"}, | |
| Key: addrs.StringKey("bar"), | |
| }, | |
| }, | |
| Action: plans.NoOp, | |
| PlanTimestamp: fakePlanTimestamp, | |
| PlannedOutputValues: map[string]cty.Value{ | |
| "hello": cty.StringVal("world"), | |
| "goal_of_life": cty.UnknownVal(cty.String), | |
| }, | |
| }, | |
| Want: &stacks.PlannedChange{ | |
| Raw: []*anypb.Any{ | |
| mustMarshalAnyPb(&tfstackdata1.PlanComponentInstance{ | |
| ComponentInstanceAddr: `component.foo["bar"]`, | |
| PlanTimestamp: "2017-03-27T10:00:00-08:00", | |
| PlannedOutputValues: map[string]*tfstackdata1.DynamicValue{ | |
| "hello": &tfstackdata1.DynamicValue{ | |
| Value: &planproto.DynamicValue{ | |
| Msgpack: mustMsgPack(t, cty.StringVal("world")), | |
| }, | |
| }, | |
| "goal_of_life": &tfstackdata1.DynamicValue{ | |
| Value: &planproto.DynamicValue{ | |
| Msgpack: mustMsgPack(t, cty.UnknownVal(cty.String)), | |
| }, | |
| }, | |
| }, | |
| }), | |
| }, | |
| Descriptions: []*stacks.PlannedChange_ChangeDescription{ | |
| { | |
| Description: &stacks.PlannedChange_ChangeDescription_ComponentInstancePlanned{ | |
| ComponentInstancePlanned: &stacks.PlannedChange_ComponentInstance{ | |
| Actions: []stacks.ChangeType{stacks.ChangeType_NOOP}, | |
| Addr: &stacks.ComponentInstanceInStackAddr{ | |
| ComponentAddr: "component.foo", | |
| ComponentInstanceAddr: `component.foo["bar"]`, | |
| }, | |
| OutputValues: map[string]*stacks.DynamicValue{ | |
| "hello": &stacks.DynamicValue{ | |
| Msgpack: mustMsgPack(t, cty.StringVal("world")), | |
| }, | |
| "goal_of_life": &stacks.DynamicValue{ | |
| Msgpack: mustMsgPack(t, cty.UnknownVal(cty.String)), | |
| }, | |
| }, | |
| }, | |
| }, | |
| }, | |
| }, | |
| }, | |
| }, | |
| "component instance delete": { | |
| Receiver: &PlannedChangeComponentInstance{ | |
| Addr: stackaddrs.AbsComponentInstance{ | |
| Stack: stackaddrs.RootStackInstance.Child("a", addrs.StringKey("boop")), | |
| Item: stackaddrs.ComponentInstance{ | |
| Component: stackaddrs.Component{Name: "foo"}, | |
| }, | |
| }, | |
| Action: plans.Delete, | |
| }, | |
| Want: &stacks.PlannedChange{ | |
| Raw: []*anypb.Any{ | |
| mustMarshalAnyPb(&tfstackdata1.PlanComponentInstance{ | |
| ComponentInstanceAddr: `stack.a["boop"].component.foo`, | |
| PlannedAction: planproto.Action_DELETE, | |
| }), | |
| }, | |
| Descriptions: []*stacks.PlannedChange_ChangeDescription{ | |
| { | |
| Description: &stacks.PlannedChange_ChangeDescription_ComponentInstancePlanned{ | |
| ComponentInstancePlanned: &stacks.PlannedChange_ComponentInstance{ | |
| Addr: &stacks.ComponentInstanceInStackAddr{ | |
| ComponentAddr: "stack.a.component.foo", | |
| ComponentInstanceAddr: `stack.a["boop"].component.foo`, | |
| }, | |
| Actions: []stacks.ChangeType{stacks.ChangeType_DELETE}, | |
| }, | |
| }, | |
| }, | |
| }, | |
| }, | |
| }, | |
| "resource instance deferred": { | |
| Receiver: &PlannedChangeDeferredResourceInstancePlanned{ | |
| ResourceInstancePlanned: PlannedChangeResourceInstancePlanned{ | |
| 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: "wotsit", | |
| }.Instance(addrs.IntKey(1)).Absolute( | |
| addrs.RootModuleInstance.Child("pizza", addrs.StringKey("chicken")), | |
| ), | |
| DeposedKey: addrs.DeposedKey("aaaaaaaa"), | |
| }, | |
| }, | |
| ProviderConfigAddr: addrs.AbsProviderConfig{ | |
| Module: addrs.RootModule, | |
| Provider: addrs.MustParseProviderSourceString("example.com/thingers/thingy"), | |
| }, | |
| ChangeSrc: &plans.ResourceInstanceChangeSrc{ | |
| Addr: addrs.Resource{ | |
| Mode: addrs.ManagedResourceMode, | |
| Type: "thingy", | |
| Name: "wotsit", | |
| }.Instance(addrs.IntKey(1)).Absolute( | |
| addrs.RootModuleInstance.Child("pizza", addrs.StringKey("chicken")), | |
| ), | |
| DeposedKey: addrs.DeposedKey("aaaaaaaa"), | |
| ProviderAddr: addrs.AbsProviderConfig{ | |
| Module: addrs.RootModule, | |
| Provider: addrs.MustParseProviderSourceString("example.com/thingers/thingy"), | |
| }, | |
| ChangeSrc: plans.ChangeSrc{ | |
| Action: plans.Create, | |
| Before: nullObjectForPlan, | |
| After: emptyObjectForPlan, | |
| }, | |
| }, | |
| }, | |
| DeferredReason: providers.DeferredReasonResourceConfigUnknown, | |
| }, | |
| Want: &stacks.PlannedChange{ | |
| Raw: []*anypb.Any{ | |
| mustMarshalAnyPb(&tfstackdata1.PlanDeferredResourceInstanceChange{ | |
| Change: &tfstackdata1.PlanResourceInstanceChangePlanned{ | |
| ComponentInstanceAddr: `stack.a["boop"].component.foo["beep"]`, | |
| ResourceInstanceAddr: `module.pizza["chicken"].thingy.wotsit[1]`, | |
| DeposedKey: "aaaaaaaa", | |
| ProviderConfigAddr: `provider["example.com/thingers/thingy"]`, | |
| Change: &planproto.ResourceInstanceChange{ | |
| Addr: `module.pizza["chicken"].thingy.wotsit[1]`, | |
| DeposedKey: "aaaaaaaa", | |
| Change: &planproto.Change{ | |
| Action: planproto.Action_CREATE, | |
| Values: []*planproto.DynamicValue{ | |
| {Msgpack: []byte{'\x80'}}, // zero-length mapping | |
| }, | |
| }, | |
| Provider: `provider["example.com/thingers/thingy"]`, | |
| }, | |
| }, | |
| Deferred: &planproto.Deferred{ | |
| Reason: planproto.DeferredReason_RESOURCE_CONFIG_UNKNOWN, | |
| }, | |
| }), | |
| }, | |
| Descriptions: []*stacks.PlannedChange_ChangeDescription{ | |
| { | |
| Description: &stacks.PlannedChange_ChangeDescription_ResourceInstanceDeferred{ | |
| ResourceInstanceDeferred: &stacks.PlannedChange_ResourceInstanceDeferred{ | |
| ResourceInstance: &stacks.PlannedChange_ResourceInstance{ | |
| Addr: &stacks.ResourceInstanceObjectInStackAddr{ | |
| ComponentInstanceAddr: `stack.a["boop"].component.foo["beep"]`, | |
| ResourceInstanceAddr: `module.pizza["chicken"].thingy.wotsit[1]`, | |
| DeposedKey: "aaaaaaaa", | |
| }, | |
| ResourceMode: stacks.ResourceMode_MANAGED, | |
| ResourceType: "thingy", | |
| ProviderAddr: "example.com/thingers/thingy", | |
| Actions: []stacks.ChangeType{stacks.ChangeType_CREATE}, | |
| ActionReason: "ResourceInstanceChangeNoReason", | |
| Index: &stacks.PlannedChange_ResourceInstance_Index{ | |
| Value: &stacks.DynamicValue{ | |
| Msgpack: []byte{0x92, 0xc4, 0x08, 0x22, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x22, 0x01}, // 1 | |
| }, | |
| }, | |
| ModuleAddr: `module.pizza["chicken"]`, | |
| ResourceName: "wotsit", | |
| Values: &stacks.DynamicValueChange{ | |
| Old: &stacks.DynamicValue{ | |
| Msgpack: []byte{'\xc0'}, // null | |
| }, | |
| New: &stacks.DynamicValue{ | |
| Msgpack: []byte{'\x80'}, // zero-length mapping | |
| }, | |
| }, | |
| }, | |
| Deferred: &stacks.Deferred{ | |
| Reason: stacks.Deferred_RESOURCE_CONFIG_UNKNOWN, | |
| }, | |
| }, | |
| }, | |
| }, | |
| }, | |
| }, | |
| }, | |
| "resource instance planned create": { | |
| Receiver: &PlannedChangeResourceInstancePlanned{ | |
| 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: "wotsit", | |
| }.Instance(addrs.IntKey(1)).Absolute( | |
| addrs.RootModuleInstance.Child("pizza", addrs.StringKey("chicken")), | |
| ), | |
| DeposedKey: addrs.DeposedKey("aaaaaaaa"), | |
| }, | |
| }, | |
| ProviderConfigAddr: addrs.AbsProviderConfig{ | |
| Module: addrs.RootModule, | |
| Provider: addrs.MustParseProviderSourceString("example.com/thingers/thingy"), | |
| }, | |
| ChangeSrc: &plans.ResourceInstanceChangeSrc{ | |
| Addr: addrs.Resource{ | |
| Mode: addrs.ManagedResourceMode, | |
| Type: "thingy", | |
| Name: "wotsit", | |
| }.Instance(addrs.IntKey(1)).Absolute( | |
| addrs.RootModuleInstance.Child("pizza", addrs.StringKey("chicken")), | |
| ), | |
| DeposedKey: addrs.DeposedKey("aaaaaaaa"), | |
| ProviderAddr: addrs.AbsProviderConfig{ | |
| Module: addrs.RootModule, | |
| Provider: addrs.MustParseProviderSourceString("example.com/thingers/thingy"), | |
| }, | |
| ChangeSrc: plans.ChangeSrc{ | |
| Action: plans.Create, | |
| Before: nullObjectForPlan, | |
| After: emptyObjectForPlan, | |
| }, | |
| }, | |
| }, | |
| Want: &stacks.PlannedChange{ | |
| Raw: []*anypb.Any{ | |
| mustMarshalAnyPb(&tfstackdata1.PlanResourceInstanceChangePlanned{ | |
| ComponentInstanceAddr: `stack.a["boop"].component.foo["beep"]`, | |
| ResourceInstanceAddr: `module.pizza["chicken"].thingy.wotsit[1]`, | |
| DeposedKey: "aaaaaaaa", | |
| ProviderConfigAddr: `provider["example.com/thingers/thingy"]`, | |
| Change: &planproto.ResourceInstanceChange{ | |
| Addr: `module.pizza["chicken"].thingy.wotsit[1]`, | |
| DeposedKey: "aaaaaaaa", | |
| Change: &planproto.Change{ | |
| Action: planproto.Action_CREATE, | |
| Values: []*planproto.DynamicValue{ | |
| {Msgpack: []byte{'\x80'}}, // zero-length mapping | |
| }, | |
| }, | |
| Provider: `provider["example.com/thingers/thingy"]`, | |
| }, | |
| }), | |
| }, | |
| Descriptions: []*stacks.PlannedChange_ChangeDescription{ | |
| { | |
| Description: &stacks.PlannedChange_ChangeDescription_ResourceInstancePlanned{ | |
| ResourceInstancePlanned: &stacks.PlannedChange_ResourceInstance{ | |
| Addr: &stacks.ResourceInstanceObjectInStackAddr{ | |
| ComponentInstanceAddr: `stack.a["boop"].component.foo["beep"]`, | |
| ResourceInstanceAddr: `module.pizza["chicken"].thingy.wotsit[1]`, | |
| DeposedKey: "aaaaaaaa", | |
| }, | |
| ResourceMode: stacks.ResourceMode_MANAGED, | |
| ResourceType: "thingy", | |
| ProviderAddr: "example.com/thingers/thingy", | |
| Actions: []stacks.ChangeType{stacks.ChangeType_CREATE}, | |
| ActionReason: "ResourceInstanceChangeNoReason", | |
| Index: &stacks.PlannedChange_ResourceInstance_Index{ | |
| Value: &stacks.DynamicValue{ | |
| Msgpack: []byte{0x92, 0xc4, 0x08, 0x22, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x22, 0x01}, // 1 | |
| }, | |
| }, | |
| ModuleAddr: `module.pizza["chicken"]`, | |
| ResourceName: "wotsit", | |
| Values: &stacks.DynamicValueChange{ | |
| Old: &stacks.DynamicValue{ | |
| Msgpack: []byte{'\xc0'}, // null | |
| }, | |
| New: &stacks.DynamicValue{ | |
| Msgpack: []byte{'\x80'}, // zero-length mapping | |
| }, | |
| }, | |
| }, | |
| }, | |
| }, | |
| }, | |
| }, | |
| }, | |
| "resource instance planned replace": { | |
| Receiver: &PlannedChangeResourceInstancePlanned{ | |
| 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: "wotsit", | |
| }.Instance(addrs.IntKey(1)).Absolute( | |
| addrs.RootModuleInstance.Child("pizza", addrs.StringKey("chicken")), | |
| ), | |
| DeposedKey: addrs.DeposedKey("aaaaaaaa"), | |
| }, | |
| }, | |
| ProviderConfigAddr: addrs.AbsProviderConfig{ | |
| Module: addrs.RootModule, | |
| Provider: addrs.MustParseProviderSourceString("example.com/thingers/thingy"), | |
| }, | |
| ChangeSrc: &plans.ResourceInstanceChangeSrc{ | |
| Addr: addrs.Resource{ | |
| Mode: addrs.ManagedResourceMode, | |
| Type: "thingy", | |
| Name: "wotsit", | |
| }.Instance(addrs.IntKey(1)).Absolute( | |
| addrs.RootModuleInstance.Child("pizza", addrs.StringKey("chicken")), | |
| ), | |
| DeposedKey: addrs.DeposedKey("aaaaaaaa"), | |
| ProviderAddr: addrs.AbsProviderConfig{ | |
| Module: addrs.RootModule, | |
| Provider: addrs.MustParseProviderSourceString("example.com/thingers/thingy"), | |
| }, | |
| ChangeSrc: plans.ChangeSrc{ | |
| Action: plans.DeleteThenCreate, | |
| Before: beforeObjectForPlan, | |
| After: afterObjectForPlan, | |
| }, | |
| RequiredReplace: cty.NewPathSet(cty.GetAttrPath("foo")), | |
| }, | |
| }, | |
| Want: &stacks.PlannedChange{ | |
| Raw: []*anypb.Any{ | |
| mustMarshalAnyPb(&tfstackdata1.PlanResourceInstanceChangePlanned{ | |
| ComponentInstanceAddr: `stack.a["boop"].component.foo["beep"]`, | |
| ResourceInstanceAddr: `module.pizza["chicken"].thingy.wotsit[1]`, | |
| DeposedKey: "aaaaaaaa", | |
| ProviderConfigAddr: `provider["example.com/thingers/thingy"]`, | |
| Change: &planproto.ResourceInstanceChange{ | |
| Addr: `module.pizza["chicken"].thingy.wotsit[1]`, | |
| DeposedKey: "aaaaaaaa", | |
| Change: &planproto.Change{ | |
| Action: planproto.Action_DELETE_THEN_CREATE, | |
| Values: []*planproto.DynamicValue{ | |
| {Msgpack: []byte("\x81\xa3foo\xa3bar")}, | |
| {Msgpack: []byte("\x81\xa3foo\xa3baz")}, | |
| }, | |
| }, | |
| Provider: `provider["example.com/thingers/thingy"]`, | |
| RequiredReplace: []*planproto.Path{ | |
| { | |
| Steps: []*planproto.Path_Step{ | |
| { | |
| Selector: &planproto.Path_Step_AttributeName{AttributeName: "foo"}, | |
| }, | |
| }, | |
| }, | |
| }, | |
| }, | |
| }), | |
| }, | |
| Descriptions: []*stacks.PlannedChange_ChangeDescription{ | |
| { | |
| Description: &stacks.PlannedChange_ChangeDescription_ResourceInstancePlanned{ | |
| ResourceInstancePlanned: &stacks.PlannedChange_ResourceInstance{ | |
| Addr: &stacks.ResourceInstanceObjectInStackAddr{ | |
| ComponentInstanceAddr: `stack.a["boop"].component.foo["beep"]`, | |
| ResourceInstanceAddr: `module.pizza["chicken"].thingy.wotsit[1]`, | |
| DeposedKey: "aaaaaaaa", | |
| }, | |
| ResourceMode: stacks.ResourceMode_MANAGED, | |
| ResourceType: "thingy", | |
| ProviderAddr: "example.com/thingers/thingy", | |
| Actions: []stacks.ChangeType{stacks.ChangeType_DELETE, stacks.ChangeType_CREATE}, | |
| ActionReason: "ResourceInstanceChangeNoReason", | |
| Index: &stacks.PlannedChange_ResourceInstance_Index{ | |
| Value: &stacks.DynamicValue{ | |
| Msgpack: []byte{0x92, 0xc4, 0x08, 0x22, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x22, 0x01}, // 1 | |
| }, | |
| }, | |
| ModuleAddr: `module.pizza["chicken"]`, | |
| ResourceName: "wotsit", | |
| Values: &stacks.DynamicValueChange{ | |
| Old: &stacks.DynamicValue{ | |
| Msgpack: []byte("\x81\xa3foo\xa3bar"), | |
| }, | |
| New: &stacks.DynamicValue{ | |
| Msgpack: []byte("\x81\xa3foo\xa3baz"), | |
| }, | |
| }, | |
| ReplacePaths: []*stacks.AttributePath{ | |
| { | |
| Steps: []*stacks.AttributePath_Step{ | |
| { | |
| Selector: &stacks.AttributePath_Step_AttributeName{ | |
| AttributeName: "foo", | |
| }, | |
| }, | |
| }, | |
| }, | |
| }, | |
| }, | |
| }, | |
| }, | |
| }, | |
| }, | |
| }, | |
| "resource instance planned import": { | |
| Receiver: &PlannedChangeResourceInstancePlanned{ | |
| 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: "wotsit", | |
| }.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"), | |
| }, | |
| ChangeSrc: &plans.ResourceInstanceChangeSrc{ | |
| Addr: addrs.Resource{ | |
| Mode: addrs.ManagedResourceMode, | |
| Type: "thingy", | |
| Name: "wotsit", | |
| }.Instance(addrs.IntKey(1)).Absolute( | |
| addrs.RootModuleInstance.Child("pizza", addrs.StringKey("chicken")), | |
| ), | |
| ProviderAddr: addrs.AbsProviderConfig{ | |
| Module: addrs.RootModule, | |
| Provider: addrs.MustParseProviderSourceString("example.com/thingers/thingy"), | |
| }, | |
| ChangeSrc: plans.ChangeSrc{ | |
| Action: plans.NoOp, | |
| Before: emptyObjectForPlan, | |
| After: emptyObjectForPlan, | |
| Importing: &plans.ImportingSrc{ | |
| ID: "bbbbbbb", | |
| }, | |
| }, | |
| }, | |
| }, | |
| Want: &stacks.PlannedChange{ | |
| Raw: []*anypb.Any{ | |
| mustMarshalAnyPb(&tfstackdata1.PlanResourceInstanceChangePlanned{ | |
| ComponentInstanceAddr: `stack.a["boop"].component.foo["beep"]`, | |
| ResourceInstanceAddr: `module.pizza["chicken"].thingy.wotsit[1]`, | |
| ProviderConfigAddr: `provider["example.com/thingers/thingy"]`, | |
| Change: &planproto.ResourceInstanceChange{ | |
| Addr: `module.pizza["chicken"].thingy.wotsit[1]`, | |
| Change: &planproto.Change{ | |
| Action: planproto.Action_NOOP, | |
| Values: []*planproto.DynamicValue{ | |
| {Msgpack: []byte{'\x80'}}, // zero-length mapping | |
| }, | |
| Importing: &planproto.Importing{ | |
| Id: "bbbbbbb", | |
| }, | |
| }, | |
| Provider: `provider["example.com/thingers/thingy"]`, | |
| }, | |
| }), | |
| }, | |
| Descriptions: []*stacks.PlannedChange_ChangeDescription{ | |
| { | |
| Description: &stacks.PlannedChange_ChangeDescription_ResourceInstancePlanned{ | |
| ResourceInstancePlanned: &stacks.PlannedChange_ResourceInstance{ | |
| Addr: &stacks.ResourceInstanceObjectInStackAddr{ | |
| ComponentInstanceAddr: `stack.a["boop"].component.foo["beep"]`, | |
| ResourceInstanceAddr: `module.pizza["chicken"].thingy.wotsit[1]`, | |
| }, | |
| ResourceMode: stacks.ResourceMode_MANAGED, | |
| ResourceType: "thingy", | |
| ProviderAddr: "example.com/thingers/thingy", | |
| Actions: []stacks.ChangeType{stacks.ChangeType_NOOP}, | |
| ActionReason: "ResourceInstanceChangeNoReason", | |
| Index: &stacks.PlannedChange_ResourceInstance_Index{ | |
| Value: &stacks.DynamicValue{ | |
| Msgpack: []byte{0x92, 0xc4, 0x08, 0x22, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x22, 0x01}, // 1 | |
| }, | |
| }, | |
| ModuleAddr: `module.pizza["chicken"]`, | |
| ResourceName: "wotsit", | |
| Values: &stacks.DynamicValueChange{ | |
| Old: &stacks.DynamicValue{ | |
| Msgpack: []byte{'\x80'}, // zero-length mapping | |
| }, | |
| New: &stacks.DynamicValue{ | |
| Msgpack: []byte{'\x80'}, // zero-length mapping | |
| }, | |
| }, | |
| Imported: &stacks.PlannedChange_ResourceInstance_Imported{ | |
| ImportId: "bbbbbbb", | |
| }, | |
| }, | |
| }, | |
| }, | |
| }, | |
| }, | |
| }, | |
| "resource instance planned moved": { | |
| Receiver: &PlannedChangeResourceInstancePlanned{ | |
| 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: "wotsit", | |
| }.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"), | |
| }, | |
| ChangeSrc: &plans.ResourceInstanceChangeSrc{ | |
| Addr: addrs.Resource{ | |
| Mode: addrs.ManagedResourceMode, | |
| Type: "thingy", | |
| Name: "wotsit", | |
| }.Instance(addrs.IntKey(1)).Absolute( | |
| addrs.RootModuleInstance.Child("pizza", addrs.StringKey("chicken")), | |
| ), | |
| PrevRunAddr: addrs.AbsResourceInstance{ | |
| Resource: addrs.Resource{ | |
| Mode: addrs.ManagedResourceMode, | |
| Type: "thingy", | |
| Name: "wotsit", | |
| }.Instance(addrs.NoKey), | |
| Module: addrs.RootModuleInstance.Child("pizza", addrs.StringKey("chicken")), | |
| }, | |
| ProviderAddr: addrs.AbsProviderConfig{ | |
| Module: addrs.RootModule, | |
| Provider: addrs.MustParseProviderSourceString("example.com/thingers/thingy"), | |
| }, | |
| ChangeSrc: plans.ChangeSrc{ | |
| Action: plans.NoOp, | |
| Before: emptyObjectForPlan, | |
| After: emptyObjectForPlan, | |
| }, | |
| }, | |
| }, | |
| Want: &stacks.PlannedChange{ | |
| Raw: []*anypb.Any{ | |
| mustMarshalAnyPb(&tfstackdata1.PlanResourceInstanceChangePlanned{ | |
| ComponentInstanceAddr: `stack.a["boop"].component.foo["beep"]`, | |
| ResourceInstanceAddr: `module.pizza["chicken"].thingy.wotsit[1]`, | |
| ProviderConfigAddr: `provider["example.com/thingers/thingy"]`, | |
| Change: &planproto.ResourceInstanceChange{ | |
| Addr: `module.pizza["chicken"].thingy.wotsit[1]`, | |
| PrevRunAddr: `module.pizza["chicken"].thingy.wotsit`, | |
| Change: &planproto.Change{ | |
| Action: planproto.Action_NOOP, | |
| Values: []*planproto.DynamicValue{ | |
| {Msgpack: []byte{'\x80'}}, // zero-length mapping | |
| }, | |
| }, | |
| Provider: `provider["example.com/thingers/thingy"]`, | |
| }, | |
| }), | |
| }, | |
| Descriptions: []*stacks.PlannedChange_ChangeDescription{ | |
| { | |
| Description: &stacks.PlannedChange_ChangeDescription_ResourceInstancePlanned{ | |
| ResourceInstancePlanned: &stacks.PlannedChange_ResourceInstance{ | |
| Addr: &stacks.ResourceInstanceObjectInStackAddr{ | |
| ComponentInstanceAddr: `stack.a["boop"].component.foo["beep"]`, | |
| ResourceInstanceAddr: `module.pizza["chicken"].thingy.wotsit[1]`, | |
| }, | |
| ResourceMode: stacks.ResourceMode_MANAGED, | |
| ResourceType: "thingy", | |
| ProviderAddr: "example.com/thingers/thingy", | |
| Actions: []stacks.ChangeType{stacks.ChangeType_NOOP}, | |
| ActionReason: "ResourceInstanceChangeNoReason", | |
| Index: &stacks.PlannedChange_ResourceInstance_Index{ | |
| Value: &stacks.DynamicValue{ | |
| Msgpack: []byte{0x92, 0xc4, 0x08, 0x22, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x22, 0x01}, // 1 | |
| }, | |
| }, | |
| ModuleAddr: `module.pizza["chicken"]`, | |
| ResourceName: "wotsit", | |
| Values: &stacks.DynamicValueChange{ | |
| Old: &stacks.DynamicValue{ | |
| Msgpack: []byte{'\x80'}, // zero-length mapping | |
| }, | |
| New: &stacks.DynamicValue{ | |
| Msgpack: []byte{'\x80'}, // zero-length mapping | |
| }, | |
| }, | |
| Moved: &stacks.PlannedChange_ResourceInstance_Moved{ | |
| PrevAddr: &stacks.ResourceInstanceInStackAddr{ | |
| ComponentInstanceAddr: `stack.a["boop"].component.foo["beep"]`, | |
| ResourceInstanceAddr: `module.pizza["chicken"].thingy.wotsit`, | |
| }, | |
| }, | |
| }, | |
| }, | |
| }, | |
| }, | |
| }, | |
| }, | |
| "output value updated": { | |
| Receiver: &PlannedChangeOutputValue{ | |
| Addr: stackaddrs.OutputValue{Name: "thingy_id"}, | |
| Action: plans.Update, | |
| // NOTE: This is a bit unrealistic since we're reporting an | |
| // update but there's no difference between these two values. | |
| // In a real planned change this situation would be a "no-op". | |
| Before: cty.EmptyObjectVal, | |
| After: cty.EmptyObjectVal, | |
| }, | |
| Want: &stacks.PlannedChange{ | |
| // Output value changes don't generate any raw representation; | |
| // the diff is only for the benefit of the operator and | |
| // other subsystems operating on their behalf. | |
| Descriptions: []*stacks.PlannedChange_ChangeDescription{ | |
| { | |
| Description: &stacks.PlannedChange_ChangeDescription_OutputValuePlanned{ | |
| OutputValuePlanned: &stacks.PlannedChange_OutputValue{ | |
| Name: "thingy_id", | |
| Actions: []stacks.ChangeType{stacks.ChangeType_UPDATE}, | |
| Values: &stacks.DynamicValueChange{ | |
| Old: &stacks.DynamicValue{ | |
| Msgpack: mustMsgPack(t, cty.EmptyObjectVal), | |
| }, | |
| New: &stacks.DynamicValue{ | |
| Msgpack: mustMsgPack(t, cty.EmptyObjectVal), | |
| }, | |
| }, | |
| }, | |
| }, | |
| }, | |
| }, | |
| }, | |
| }, | |
| "create sensitive root input variable": { | |
| Receiver: &PlannedChangeRootInputValue{ | |
| Addr: stackaddrs.InputVariable{Name: "thingy_id"}, | |
| Action: plans.Create, | |
| Before: cty.NullVal(cty.String), | |
| After: cty.StringVal("boop").Mark(marks.Sensitive), | |
| }, | |
| Want: &stacks.PlannedChange{ | |
| Raw: []*anypb.Any{ | |
| mustMarshalAnyPb(&tfstackdata1.PlanRootInputValue{ | |
| Name: "thingy_id", | |
| Value: &tfstackdata1.DynamicValue{ | |
| Value: &planproto.DynamicValue{ | |
| Msgpack: []byte("\x92\xc4\b\"string\"\xa4boop"), | |
| }, | |
| SensitivePaths: []*planproto.Path{ | |
| { | |
| Steps: make([]*planproto.Path_Step, 0), // no steps as it is the root value | |
| }, | |
| }, | |
| }, | |
| }), | |
| }, | |
| Descriptions: []*stacks.PlannedChange_ChangeDescription{ | |
| { | |
| Description: &stacks.PlannedChange_ChangeDescription_InputVariablePlanned{ | |
| InputVariablePlanned: &stacks.PlannedChange_InputVariable{ | |
| Name: "thingy_id", | |
| Actions: []stacks.ChangeType{stacks.ChangeType_CREATE}, | |
| Values: &stacks.DynamicValueChange{ | |
| Old: &stacks.DynamicValue{ | |
| Msgpack: mustMsgPack(t, cty.NullVal(cty.String)), | |
| }, | |
| New: &stacks.DynamicValue{ | |
| Msgpack: mustMsgPack(t, cty.StringVal("boop")), | |
| Sensitive: []*stacks.AttributePath{ | |
| { | |
| Steps: make([]*stacks.AttributePath_Step, 0), // no steps as it is the root value | |
| }, | |
| }, | |
| }, | |
| }, | |
| }, | |
| }, | |
| }, | |
| }, | |
| }, | |
| }, | |
| "ephemeral root input variable": { | |
| Receiver: &PlannedChangeRootInputValue{ | |
| Addr: stackaddrs.InputVariable{Name: "thingy_id"}, | |
| Action: plans.Create, | |
| Before: cty.NullVal(cty.String), | |
| After: cty.StringVal("boop").Mark(marks.Ephemeral), | |
| }, | |
| WantErr: "failed to encode after planned input variable var.thingy_id: : unhandled value marks cty.NewValueMarks(marks.Ephemeral) (this is a bug in Terraform)", // Ephemeral values should never make it this far. | |
| }, | |
| "update root input variable": { | |
| Receiver: &PlannedChangeRootInputValue{ | |
| Addr: stackaddrs.InputVariable{Name: "thingy_id"}, | |
| Action: plans.Update, | |
| Before: cty.StringVal("beep"), | |
| After: cty.StringVal("boop"), | |
| }, | |
| Want: &stacks.PlannedChange{ | |
| Raw: []*anypb.Any{ | |
| mustMarshalAnyPb(&tfstackdata1.PlanRootInputValue{ | |
| Name: "thingy_id", | |
| Value: &tfstackdata1.DynamicValue{ | |
| Value: &planproto.DynamicValue{ | |
| Msgpack: []byte("\x92\xc4\b\"string\"\xa4boop"), | |
| }, | |
| }, | |
| }), | |
| }, | |
| Descriptions: []*stacks.PlannedChange_ChangeDescription{ | |
| { | |
| Description: &stacks.PlannedChange_ChangeDescription_InputVariablePlanned{ | |
| InputVariablePlanned: &stacks.PlannedChange_InputVariable{ | |
| Name: "thingy_id", | |
| Actions: []stacks.ChangeType{stacks.ChangeType_UPDATE}, | |
| Values: &stacks.DynamicValueChange{ | |
| Old: &stacks.DynamicValue{ | |
| Msgpack: mustMsgPack(t, cty.StringVal("beep")), | |
| }, | |
| New: &stacks.DynamicValue{ | |
| Msgpack: mustMsgPack(t, cty.StringVal("boop")), | |
| }, | |
| }, | |
| }, | |
| }, | |
| }, | |
| }, | |
| }, | |
| }, | |
| "root input variable that must be re-supplied during apply": { | |
| Receiver: &PlannedChangeRootInputValue{ | |
| Addr: stackaddrs.InputVariable{Name: "thingy_id"}, | |
| Action: plans.Create, | |
| Before: cty.NullVal(cty.String), | |
| After: cty.NullVal(cty.String), | |
| RequiredOnApply: true, | |
| }, | |
| Want: &stacks.PlannedChange{ | |
| Raw: []*anypb.Any{ | |
| mustMarshalAnyPb(&tfstackdata1.PlanRootInputValue{ | |
| Name: "thingy_id", | |
| Value: &tfstackdata1.DynamicValue{ | |
| Value: &planproto.DynamicValue{ | |
| Msgpack: mustMsgPack(t, cty.NullVal(cty.String)), | |
| }, | |
| }, | |
| RequiredOnApply: true, | |
| }), | |
| }, | |
| Descriptions: []*stacks.PlannedChange_ChangeDescription{ | |
| { | |
| Description: &stacks.PlannedChange_ChangeDescription_InputVariablePlanned{ | |
| InputVariablePlanned: &stacks.PlannedChange_InputVariable{ | |
| Name: "thingy_id", | |
| Actions: []stacks.ChangeType{stacks.ChangeType_CREATE}, | |
| Values: &stacks.DynamicValueChange{ | |
| Old: &stacks.DynamicValue{ | |
| Msgpack: mustMsgPack(t, cty.NullVal(cty.String)), | |
| }, | |
| New: &stacks.DynamicValue{ | |
| Msgpack: mustMsgPack(t, cty.NullVal(cty.String)), | |
| }, | |
| }, | |
| RequiredDuringApply: true, | |
| }, | |
| }, | |
| }, | |
| }, | |
| }, | |
| }, | |
| "action invocation lifecycle trigger": { | |
| Receiver: &PlannedChangeActionInvocationInstancePlanned{ | |
| ActionInvocationAddr: stackaddrs.AbsActionInvocationInstance{ | |
| Component: stackaddrs.AbsComponentInstance{ | |
| Stack: stackaddrs.RootStackInstance, | |
| Item: stackaddrs.ComponentInstance{ | |
| Component: stackaddrs.Component{Name: "web"}, | |
| }, | |
| }, | |
| Item: addrs.AbsActionInstance{ | |
| Module: addrs.RootModuleInstance, | |
| Action: addrs.ActionInstance{ | |
| Action: addrs.Action{ | |
| Type: "webhook", | |
| Name: "notify", | |
| }, | |
| Key: addrs.NoKey, | |
| }, | |
| }, | |
| }, | |
| ProviderConfigAddr: addrs.AbsProviderConfig{ | |
| Module: addrs.RootModule, | |
| Provider: addrs.MustParseProviderSourceString("example.com/webhooks/http"), | |
| }, | |
| Invocation: &plans.ActionInvocationInstanceSrc{ | |
| Addr: addrs.AbsActionInstance{ | |
| Module: addrs.RootModuleInstance, | |
| Action: addrs.ActionInstance{ | |
| Action: addrs.Action{ | |
| Type: "webhook", | |
| Name: "notify", | |
| }, | |
| Key: addrs.NoKey, | |
| }, | |
| }, | |
| ActionTrigger: &plans.ResourceActionTrigger{ | |
| TriggeringResourceAddr: addrs.Resource{ | |
| Mode: addrs.ManagedResourceMode, | |
| Type: "example_resource", | |
| Name: "main", | |
| }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance), | |
| ActionTriggerEvent: configs.AfterCreate, | |
| ActionTriggerBlockIndex: 0, | |
| ActionsListIndex: 0, | |
| }, | |
| ConfigValue: emptyObjectForPlan, | |
| ProviderAddr: addrs.AbsProviderConfig{ | |
| Module: addrs.RootModule, | |
| Provider: addrs.MustParseProviderSourceString("example.com/webhooks/http"), | |
| }, | |
| }, | |
| Schema: providers.ActionSchema{ | |
| ConfigSchema: &configschema.Block{}, | |
| }, | |
| }, | |
| Want: &stacks.PlannedChange{ | |
| Raw: []*anypb.Any{ | |
| mustMarshalAnyPb(&tfstackdata1.PlanActionInvocationPlanned{ | |
| ComponentInstanceAddr: "component.web", | |
| ActionInvocationAddr: "action.webhook.notify", | |
| ProviderConfigAddr: "example.com/webhooks/http", | |
| Invocation: &planproto.ActionInvocationInstance{ | |
| Addr: "action.webhook.notify", | |
| Provider: `provider["example.com/webhooks/http"]`, | |
| ActionTrigger: &planproto.ActionInvocationInstance_ResourceActionTrigger{ | |
| ResourceActionTrigger: &planproto.ResourceActionTrigger{ | |
| TriggeringResourceAddr: "example_resource.main", | |
| TriggerEvent: planproto.ActionTriggerEvent_AFTER_CREATE, | |
| ActionTriggerBlockIndex: 0, | |
| ActionsListIndex: 0, | |
| }, | |
| }, | |
| ConfigValue: &planproto.DynamicValue{ | |
| Msgpack: emptyObjectForPlan, | |
| }, | |
| }, | |
| }), | |
| }, | |
| Descriptions: []*stacks.PlannedChange_ChangeDescription{ | |
| { | |
| Description: &stacks.PlannedChange_ChangeDescription_ActionInvocationPlanned{ | |
| ActionInvocationPlanned: &stacks.PlannedChange_ActionInvocationInstance{ | |
| Addr: &stacks.ActionInvocationInstanceInStackAddr{ | |
| ComponentInstanceAddr: "component.web", | |
| ActionInvocationInstanceAddr: "action.webhook.notify", | |
| }, | |
| ProviderAddr: "example.com/webhooks/http", | |
| ActionType: "webhook", | |
| ConfigValue: &stacks.DynamicValue{ | |
| Msgpack: emptyObjectForPlan, | |
| }, | |
| ActionTrigger: &stacks.PlannedChange_ActionInvocationInstance_ResourceActionTrigger{ | |
| ResourceActionTrigger: &stacks.PlannedChange_ResourceActionTrigger{ | |
| TriggeringResourceAddress: &stacks.ResourceInstanceInStackAddr{ | |
| ComponentInstanceAddr: "component.web", | |
| ResourceInstanceAddr: "example_resource.main", | |
| }, | |
| TriggerEvent: stacks.PlannedChange_AFTER_CREATE, | |
| ActionTriggerBlockIndex: 0, | |
| ActionsListIndex: 0, | |
| }, | |
| }, | |
| }, | |
| }, | |
| }, | |
| }, | |
| }, | |
| }, | |
| // TODO: Add test for "action invocation invoke trigger" when implementing | |
| // direct action invocation in a later iteration. The InvokeActionTrigger | |
| // case is mentioned in the interface for completeness but not yet implemented. | |
| } | |
| for name, test := range tests { | |
| t.Run(name, func(t *testing.T) { | |
| got, err := test.Receiver.PlannedChangeProto() | |
| if len(test.WantErr) > 0 { | |
| if diff := cmp.Diff(test.WantErr, err.Error()); diff != "" { | |
| t.Errorf("wrong error\n%s", diff) | |
| } | |
| if got != nil { | |
| t.Errorf("unexpected result: %v", got) | |
| } | |
| return | |
| } | |
| if err != nil { | |
| // All errors this can generate are caused by bugs in Terraform | |
| // because we're serializing content that we created, and so | |
| // there are no _expected_ error cases. | |
| t.Fatal(err) | |
| } | |
| if diff := cmp.Diff(test.Want, got, protocmp.Transform()); diff != "" { | |
| t.Errorf("wrong result\n%s", diff) | |
| } | |
| }) | |
| } | |
| } | |
| func mustMarshalAnyPb(msg proto.Message) *anypb.Any { | |
| var ret anypb.Any | |
| err := anypb.MarshalFrom(&ret, msg, proto.MarshalOptions{}) | |
| if err != nil { | |
| panic(err) | |
| } | |
| return &ret | |
| } | |
| func mustMsgPack(t *testing.T, value cty.Value) []byte { | |
| data, err := msgpack.Marshal(value, cty.DynamicPseudoType) | |
| if err != nil { | |
| t.Fatal(err) | |
| } | |
| return data | |
| } |