action: move action trigger into nested struct

this prepares the work on CLI / flag-driven invocations
actions/invoke-command-prototype
Daniel Schmidt 8 months ago
parent da76dba3dc
commit 219c31f4eb

@ -66,18 +66,18 @@ func precomputeDiffs(plan Plan, mode plans.Mode) diffs {
after := []jsonplan.ActionInvocation{}
for _, action := range plan.ActionInvocations {
if action.TriggeringResourceAddress != change.Address {
if action.LifecycleActionTrigger == nil || action.LifecycleActionTrigger.TriggeringResourceAddress != change.Address {
continue
}
switch action.TriggerEvent {
switch action.LifecycleActionTrigger.ActionTriggerEvent {
case configs.BeforeCreate.String(), configs.BeforeUpdate.String(), configs.BeforeDestroy.String():
before = append(before, action)
case configs.AfterCreate.String(), configs.AfterUpdate.String(), configs.AfterDestroy.String():
after = append(after, action)
default:
// The switch should be exhaustive.
panic(fmt.Sprintf("Unexpected triggering event when rendering action %s", action.TriggerEvent))
panic(fmt.Sprintf("Unexpected triggering event when rendering action %s", action.LifecycleActionTrigger.ActionTriggerEvent))
}
}

@ -8289,10 +8289,6 @@ func TestResourceChange_actions(t *testing.T) {
},
}
ptr := func(i int) *int {
return &i
}
for name, tc := range map[string]struct {
actionInvocations []jsonplan.ActionInvocation
output string
@ -8300,14 +8296,16 @@ func TestResourceChange_actions(t *testing.T) {
"before actions": {
actionInvocations: []jsonplan.ActionInvocation{
{
Address: "action.test_unlinked.hello",
Type: "test_unlinked",
Name: "hello",
ProviderName: "registry.terraform.io/hashicorp/test",
ActionTriggerBlockIndex: ptr(0),
ActionsListIndex: ptr(0),
TriggeringResourceAddress: triggeringResourceAddr.String(),
TriggerEvent: "BeforeCreate",
Address: "action.test_unlinked.hello",
Type: "test_unlinked",
Name: "hello",
ProviderName: "registry.terraform.io/hashicorp/test",
LifecycleActionTrigger: &jsonplan.LifecycleActionTrigger{
ActionTriggerBlockIndex: 0,
ActionsListIndex: 0,
TriggeringResourceAddress: triggeringResourceAddr.String(),
ActionTriggerEvent: "BeforeCreate",
},
ConfigValues: marshalConfigValues(cty.ObjectVal(map[string]cty.Value{
"id": cty.StringVal("1D5F5E9E-F2E5-401B-9ED5-692A215AC67E"),
"disk": cty.ObjectVal(map[string]cty.Value{
@ -8342,14 +8340,16 @@ func TestResourceChange_actions(t *testing.T) {
"after actions": {
actionInvocations: []jsonplan.ActionInvocation{
{
Address: "action.test_unlinked.hello",
Type: "test_unlinked",
Name: "hello",
ProviderName: "registry.terraform.io/hashicorp/test",
ActionTriggerBlockIndex: ptr(0),
ActionsListIndex: ptr(0),
TriggeringResourceAddress: triggeringResourceAddr.String(),
TriggerEvent: "AfterCreate",
Address: "action.test_unlinked.hello",
Type: "test_unlinked",
Name: "hello",
ProviderName: "registry.terraform.io/hashicorp/test",
LifecycleActionTrigger: &jsonplan.LifecycleActionTrigger{
ActionTriggerBlockIndex: 0,
ActionsListIndex: 0,
TriggeringResourceAddress: triggeringResourceAddr.String(),
ActionTriggerEvent: "AfterCreate",
},
ConfigValues: marshalConfigValues(cty.ObjectVal(map[string]cty.Value{
"id": cty.StringVal("1D5F5E9E-F2E5-401B-9ED5-692A215AC67E"),
"disk": cty.ObjectVal(map[string]cty.Value{
@ -8384,66 +8384,76 @@ func TestResourceChange_actions(t *testing.T) {
"before and after actions": {
actionInvocations: []jsonplan.ActionInvocation{
{
Address: "action.test_unlinked.hello",
Type: "test_unlinked",
Name: "hello",
ProviderName: "registry.terraform.io/hashicorp/test",
ActionTriggerBlockIndex: ptr(0),
ActionsListIndex: ptr(0),
TriggeringResourceAddress: triggeringResourceAddr.String(),
TriggerEvent: "BeforeCreate",
Address: "action.test_unlinked.hello",
Type: "test_unlinked",
Name: "hello",
ProviderName: "registry.terraform.io/hashicorp/test",
LifecycleActionTrigger: &jsonplan.LifecycleActionTrigger{
ActionTriggerBlockIndex: 0,
ActionsListIndex: 0,
TriggeringResourceAddress: triggeringResourceAddr.String(),
ActionTriggerEvent: "BeforeCreate",
},
ConfigValues: marshalConfigValues(cty.ObjectVal(map[string]cty.Value{
"id": cty.StringVal("first-block-and-action"),
})),
},
{
Address: "action.test_unlinked.hello",
Type: "test_unlinked",
Name: "hello",
ProviderName: "registry.terraform.io/hashicorp/test",
ActionTriggerBlockIndex: ptr(0),
ActionsListIndex: ptr(1),
TriggeringResourceAddress: triggeringResourceAddr.String(),
TriggerEvent: "BeforeCreate",
Address: "action.test_unlinked.hello",
Type: "test_unlinked",
Name: "hello",
ProviderName: "registry.terraform.io/hashicorp/test",
LifecycleActionTrigger: &jsonplan.LifecycleActionTrigger{
ActionTriggerBlockIndex: 0,
ActionsListIndex: 1,
TriggeringResourceAddress: triggeringResourceAddr.String(),
ActionTriggerEvent: "BeforeCreate",
},
ConfigValues: marshalConfigValues(cty.ObjectVal(map[string]cty.Value{
"id": cty.StringVal("first-block-second-action"),
})),
},
{
Address: "action.test_unlinked.hello",
Type: "test_unlinked",
Name: "hello",
ProviderName: "registry.terraform.io/hashicorp/test",
ActionTriggerBlockIndex: ptr(1),
ActionsListIndex: ptr(0),
TriggeringResourceAddress: triggeringResourceAddr.String(),
TriggerEvent: "AfterCreate",
Address: "action.test_unlinked.hello",
Type: "test_unlinked",
Name: "hello",
ProviderName: "registry.terraform.io/hashicorp/test",
LifecycleActionTrigger: &jsonplan.LifecycleActionTrigger{
ActionTriggerBlockIndex: 1,
ActionsListIndex: 0,
TriggeringResourceAddress: triggeringResourceAddr.String(),
ActionTriggerEvent: "AfterCreate",
},
ConfigValues: marshalConfigValues(cty.ObjectVal(map[string]cty.Value{
"id": cty.StringVal("second-block-first-action"),
})),
},
{
Address: "action.test_unlinked.hello",
Type: "test_unlinked",
Name: "hello",
ProviderName: "registry.terraform.io/hashicorp/test",
ActionTriggerBlockIndex: ptr(2),
ActionsListIndex: ptr(0),
TriggeringResourceAddress: triggeringResourceAddr.String(),
TriggerEvent: "AfterCreate",
Address: "action.test_unlinked.hello",
Type: "test_unlinked",
Name: "hello",
ProviderName: "registry.terraform.io/hashicorp/test",
LifecycleActionTrigger: &jsonplan.LifecycleActionTrigger{
ActionTriggerBlockIndex: 2,
ActionsListIndex: 0,
TriggeringResourceAddress: triggeringResourceAddr.String(),
ActionTriggerEvent: "AfterCreate",
},
ConfigValues: marshalConfigValues(cty.ObjectVal(map[string]cty.Value{
"id": cty.StringVal("third-block-first-action"),
})),
},
{
Address: "action.test_unlinked.hello",
Type: "test_unlinked",
Name: "hello",
ProviderName: "registry.terraform.io/hashicorp/test",
ActionTriggerBlockIndex: ptr(3),
ActionsListIndex: ptr(0),
TriggeringResourceAddress: triggeringResourceAddr.String(),
TriggerEvent: "BeforeCreate",
Address: "action.test_unlinked.hello",
Type: "test_unlinked",
Name: "hello",
ProviderName: "registry.terraform.io/hashicorp/test",
LifecycleActionTrigger: &jsonplan.LifecycleActionTrigger{
ActionTriggerBlockIndex: 3,
ActionsListIndex: 0,
TriggeringResourceAddress: triggeringResourceAddr.String(),
ActionTriggerEvent: "BeforeCreate",
},
ConfigValues: marshalConfigValues(cty.ObjectVal(map[string]cty.Value{
"id": cty.StringVal("fourth-block-first-action"),
})),
@ -8488,14 +8498,16 @@ func TestResourceChange_actions(t *testing.T) {
"no config value": {
actionInvocations: []jsonplan.ActionInvocation{
{
Address: "action.test_unlinked.hello",
Type: "test_unlinked",
Name: "hello",
ProviderName: "registry.terraform.io/hashicorp/test",
ActionTriggerBlockIndex: ptr(0),
ActionsListIndex: ptr(0),
TriggeringResourceAddress: triggeringResourceAddr.String(),
TriggerEvent: "BeforeCreate",
Address: "action.test_unlinked.hello",
Type: "test_unlinked",
Name: "hello",
ProviderName: "registry.terraform.io/hashicorp/test",
LifecycleActionTrigger: &jsonplan.LifecycleActionTrigger{
ActionTriggerBlockIndex: 0,
ActionsListIndex: 0,
TriggeringResourceAddress: triggeringResourceAddr.String(),
ActionTriggerEvent: "BeforeCreate",
},
},
},
output: ` # test_instance.example will be created

@ -31,39 +31,42 @@ type ActionInvocation struct {
// offering "google_compute_instance".
ProviderName string `json:"provider_name,omitempty"`
// These fields below are used for actions invoked during plan / apply, they are not applicable
// for terraform invoke.
// ActionTriggerBlockIndex is the index of the action trigger block
ActionTriggerBlockIndex *int `json:"action_trigger_block_index,omitempty"`
// ActionsListIndex is the index of the action in the actions list
ActionsListIndex *int `json:"actions_list_index,omitempty"`
// TriggeringResourceAddress is the address of the resource that triggered the action
LifecycleActionTrigger *LifecycleActionTrigger `json:"lifecycle_action_trigger,omitempty"`
InvokeCmdActionTrigger *InvokeCmdActionTrigger `json:"invoke_cmd_action_trigger,omitempty"`
}
type LifecycleActionTrigger struct {
TriggeringResourceAddress string `json:"triggering_resource_address,omitempty"`
// TriggerEvent is the event that triggered the action
TriggerEvent string `json:"trigger_event,omitempty"`
ActionTriggerEvent string `json:"action_trigger_event,omitempty"`
ActionTriggerBlockIndex int `json:"action_trigger_block_index,omitempty"`
ActionsListIndex int `json:"actions_list_index,omitempty"`
}
type InvokeCmdActionTrigger struct {
ActionTriggerEvent string `json:"action_trigger_event,omitempty"`
}
func ActionInvocationCompare(a, b ActionInvocation) int {
if a.TriggeringResourceAddress < b.TriggeringResourceAddress {
return -1
} else if a.TriggeringResourceAddress > b.TriggeringResourceAddress {
return 1
}
if a.LifecycleActionTrigger != nil && b.LifecycleActionTrigger != nil {
latA := *a.LifecycleActionTrigger
latB := *b.LifecycleActionTrigger
if a.ActionTriggerBlockIndex != nil && b.ActionTriggerBlockIndex != nil {
if *a.ActionTriggerBlockIndex < *b.ActionTriggerBlockIndex {
if latA.TriggeringResourceAddress < latB.TriggeringResourceAddress {
return -1
} else if *a.ActionTriggerBlockIndex > *b.ActionTriggerBlockIndex {
} else if latA.TriggeringResourceAddress > latB.TriggeringResourceAddress {
return 1
}
}
if a.ActionsListIndex != nil && b.ActionsListIndex != nil {
if *a.ActionsListIndex < *b.ActionsListIndex {
if latA.ActionTriggerBlockIndex < latB.ActionTriggerBlockIndex {
return -1
} else if latA.ActionTriggerBlockIndex > latB.ActionTriggerBlockIndex {
return 1
}
if latA.ActionsListIndex < latB.ActionsListIndex {
return -1
} else if *a.ActionsListIndex > *b.ActionsListIndex {
} else if latA.ActionsListIndex > latB.ActionsListIndex {
return 1
}
}
@ -111,13 +114,18 @@ func MarshalActionInvocations(actions []*plans.ActionInvocationInstanceSrc, sche
Type: action.Addr.Action.Action.Type,
Name: action.Addr.Action.Action.Name,
ProviderName: action.ProviderAddr.Provider.String(),
}
// These fields are only used for non-CLI actions. We will need to find another format
// once we support terraform invoke.
ActionTriggerBlockIndex: &action.ActionTriggerBlockIndex,
ActionsListIndex: &action.ActionsListIndex,
TriggeringResourceAddress: action.TriggeringResourceAddr.String(),
TriggerEvent: action.TriggerEvent.String(),
switch at := action.ActionTrigger.(type) {
case plans.LifecycleActionTrigger:
ai.LifecycleActionTrigger = &LifecycleActionTrigger{
TriggeringResourceAddress: at.TriggeringResourceAddr.String(),
ActionTriggerEvent: at.TriggerEvent().String(),
ActionTriggerBlockIndex: at.ActionTriggerBlockIndex,
ActionsListIndex: at.ActionsListIndex,
}
default:
return ret, fmt.Errorf("unsupported action trigger type: %T", at)
}
if actionDec.ConfigValue != cty.NilVal {

@ -14,6 +14,7 @@ import (
"github.com/zclconf/go-cty/cty"
"github.com/hashicorp/terraform/internal/addrs"
"github.com/hashicorp/terraform/internal/configs"
"github.com/hashicorp/terraform/internal/plans"
"github.com/hashicorp/terraform/internal/terminal"
"github.com/hashicorp/terraform/internal/terraform"
@ -32,10 +33,13 @@ func testJSONHookResourceID(addr addrs.AbsResourceInstance) terraform.HookResour
func testJSONHookActionID(actionAddr addrs.AbsActionInstance, triggeringResourceAddr addrs.AbsResourceInstance, actionTriggerIndex int, actionsListIndex int) terraform.HookActionIdentity {
return terraform.HookActionIdentity{
Addr: actionAddr,
TriggeringResourceAddr: triggeringResourceAddr,
ActionTriggerBlockIndex: actionTriggerIndex,
ActionsListIndex: actionsListIndex,
Addr: actionAddr,
ActionTrigger: plans.LifecycleActionTrigger{
TriggeringResourceAddr: triggeringResourceAddr,
ActionTriggerBlockIndex: actionTriggerIndex,
ActionsListIndex: actionsListIndex,
ActionTriggerEvent: configs.AfterCreate,
},
}
}

@ -373,10 +373,15 @@ func (h *actionStart) String() string {
}
func NewActionStart(id terraform.HookActionIdentity) Hook {
at, ok := id.ActionTrigger.(plans.LifecycleActionTrigger)
if !ok {
panic("invalid action trigger")
}
return &actionStart{
TriggeringResource: newResourceAddr(id.TriggeringResourceAddr),
TriggerIndex: id.ActionTriggerBlockIndex,
ActionsIndex: id.ActionsListIndex,
TriggeringResource: newResourceAddr(at.TriggeringResourceAddr),
TriggerIndex: at.ActionTriggerBlockIndex,
ActionsIndex: at.ActionsListIndex,
Action: newActionAddr(id.Addr),
}
}
@ -400,10 +405,14 @@ func (h *actionProgress) String() string {
}
func NewActionProgress(id terraform.HookActionIdentity, message string) Hook {
at, ok := id.ActionTrigger.(plans.LifecycleActionTrigger)
if !ok {
panic("invalid action trigger")
}
return &actionProgress{
TriggeringResource: newResourceAddr(id.TriggeringResourceAddr),
TriggerIndex: id.ActionTriggerBlockIndex,
ActionsIndex: id.ActionsListIndex,
TriggeringResource: newResourceAddr(at.TriggeringResourceAddr),
TriggerIndex: at.ActionTriggerBlockIndex,
ActionsIndex: at.ActionsListIndex,
Action: newActionAddr(id.Addr),
Message: message,
}
@ -427,10 +436,14 @@ func (h *actionComplete) String() string {
}
func NewActionComplete(id terraform.HookActionIdentity) Hook {
at, ok := id.ActionTrigger.(plans.LifecycleActionTrigger)
if !ok {
panic("invalid action trigger")
}
return &actionComplete{
TriggeringResource: newResourceAddr(id.TriggeringResourceAddr),
TriggerIndex: id.ActionTriggerBlockIndex,
ActionsIndex: id.ActionsListIndex,
TriggeringResource: newResourceAddr(at.TriggeringResourceAddr),
TriggerIndex: at.ActionTriggerBlockIndex,
ActionsIndex: at.ActionsListIndex,
Action: newActionAddr(id.Addr),
}
}
@ -454,10 +467,14 @@ func (h *actionErrored) String() string {
}
func NewActionErrored(id terraform.HookActionIdentity, err error) Hook {
at, ok := id.ActionTrigger.(plans.LifecycleActionTrigger)
if !ok {
panic("invalid action trigger")
}
return &actionErrored{
TriggeringResource: newResourceAddr(id.TriggeringResourceAddr),
TriggerIndex: id.ActionTriggerBlockIndex,
ActionsIndex: id.ActionsListIndex,
TriggeringResource: newResourceAddr(at.TriggeringResourceAddr),
TriggerIndex: at.ActionTriggerBlockIndex,
ActionsIndex: at.ActionsListIndex,
Action: newActionAddr(id.Addr),
Error: err.Error(),
}

@ -11,16 +11,9 @@ import (
)
type ActionInvocationInstance struct {
Addr addrs.AbsActionInstance
TriggeringResourceAddr addrs.AbsResourceInstance
Addr addrs.AbsActionInstance
// Information about the trigger
// The event that triggered this action invocation.
TriggerEvent configs.ActionTriggerEvent
// The index of the action_trigger block that triggered this invocation.
ActionTriggerBlockIndex int
// The index of the action in the evens list of the action_trigger block
ActionsListIndex int
ActionTrigger ActionTrigger
// Provider is the address of the provider configuration that was used
// to plan this action, and thus the configuration that must also be
@ -30,18 +23,59 @@ type ActionInvocationInstance struct {
ConfigValue cty.Value
}
type ActionTrigger interface {
actionTriggerSigil()
TriggerEvent() configs.ActionTriggerEvent
String() string
Equals(to ActionTrigger) bool
}
type LifecycleActionTrigger struct {
TriggeringResourceAddr addrs.AbsResourceInstance
// Information about the trigger
// The event that triggered this action invocation.
ActionTriggerEvent configs.ActionTriggerEvent
// The index of the action_trigger block that triggered this invocation.
ActionTriggerBlockIndex int
// The index of the action in the events list of the action_trigger block
ActionsListIndex int
}
func (t LifecycleActionTrigger) TriggerEvent() configs.ActionTriggerEvent {
return t.ActionTriggerEvent
}
func (t LifecycleActionTrigger) actionTriggerSigil() {}
func (t LifecycleActionTrigger) String() string {
return t.TriggeringResourceAddr.String()
}
func (t LifecycleActionTrigger) Equals(other ActionTrigger) bool {
o, ok := other.(LifecycleActionTrigger)
if !ok {
return false
}
return t.TriggeringResourceAddr.Equal(o.TriggeringResourceAddr) &&
t.ActionTriggerBlockIndex == o.ActionTriggerBlockIndex &&
t.ActionsListIndex == o.ActionsListIndex
}
var _ ActionTrigger = (*LifecycleActionTrigger)(nil)
// Encode produces a variant of the receiver that has its change values
// serialized so it can be written to a plan file. Pass the implied type of the
// corresponding resource type schema for correct operation.
func (ai *ActionInvocationInstance) Encode(schema *providers.ActionSchema) (*ActionInvocationInstanceSrc, error) {
ret := &ActionInvocationInstanceSrc{
Addr: ai.Addr,
TriggeringResourceAddr: ai.TriggeringResourceAddr,
TriggerEvent: ai.TriggerEvent,
ActionTriggerBlockIndex: ai.ActionTriggerBlockIndex,
ActionsListIndex: ai.ActionsListIndex,
ProviderAddr: ai.ProviderAddr,
Addr: ai.Addr,
ActionTrigger: ai.ActionTrigger,
ProviderAddr: ai.ProviderAddr,
}
if ai.ConfigValue != cty.NilVal {

@ -9,7 +9,6 @@ import (
"github.com/zclconf/go-cty/cty"
"github.com/hashicorp/terraform/internal/addrs"
"github.com/hashicorp/terraform/internal/configs"
"github.com/hashicorp/terraform/internal/genconfig"
"github.com/hashicorp/terraform/internal/lang/marks"
"github.com/hashicorp/terraform/internal/providers"
@ -566,11 +565,8 @@ func (c *ChangesSrc) AppendActionInvocationInstanceChange(action *ActionInvocati
}
type ActionInvocationInstanceSrc struct {
Addr addrs.AbsActionInstance
TriggeringResourceAddr addrs.AbsResourceInstance
TriggerEvent configs.ActionTriggerEvent
ActionTriggerBlockIndex int
ActionsListIndex int
Addr addrs.AbsActionInstance
ActionTrigger ActionTrigger
ConfigValue DynamicValue
@ -590,13 +586,10 @@ func (acs *ActionInvocationInstanceSrc) Decode(schema *providers.ActionSchema) (
}
ai := &ActionInvocationInstance{
Addr: acs.Addr,
TriggeringResourceAddr: acs.TriggeringResourceAddr,
TriggerEvent: acs.TriggerEvent,
ActionTriggerBlockIndex: acs.ActionTriggerBlockIndex,
ActionsListIndex: acs.ActionsListIndex,
ProviderAddr: acs.ProviderAddr,
ConfigValue: config,
Addr: acs.Addr,
ActionTrigger: acs.ActionTrigger,
ProviderAddr: acs.ProviderAddr,
ConfigValue: config,
}
return ai, nil
}

@ -241,7 +241,7 @@ func (cs *ChangesSync) RemoveOutputChange(addr addrs.AbsOutputValue) {
// GetActionInvocation gets an action invocation based on the action address, the triggering
// resource address, the action trigger block index, and the action list index.
func (cs *ChangesSync) GetActionInvocation(addr addrs.AbsActionInstance, triggeringResourceAddr addrs.AbsResourceInstance, triggerBlockIndex, actionListIndex int) *ActionInvocationInstance {
func (cs *ChangesSync) GetActionInvocation(addr addrs.AbsActionInstance, actionTrigger ActionTrigger) *ActionInvocationInstance {
if cs == nil {
panic("GetActionInvocation on nil ChangesSync")
}
@ -249,8 +249,10 @@ func (cs *ChangesSync) GetActionInvocation(addr addrs.AbsActionInstance, trigger
defer cs.lock.Unlock()
for _, a := range cs.changes.ActionInvocations {
if a.Addr.Equal(addr) && a.TriggeringResourceAddr.Equal(triggeringResourceAddr) && a.ActionTriggerBlockIndex == triggerBlockIndex && a.ActionsListIndex == actionListIndex {
return a
if a.Addr.Equal(addr) {
if a.ActionTrigger.Equals(actionTrigger) {
return a
}
}
}
return nil

@ -1259,30 +1259,41 @@ func actionInvocationFromTfplan(rawAction *planproto.ActionInvocationInstance) (
}
ret.Addr = actionAddr
ret.TriggeringResourceAddr, diags = addrs.ParseAbsResourceInstanceStr(rawAction.TriggeringResourceAddr)
if diags.HasErrors() {
return nil, fmt.Errorf("invalid resource instance address %q: %w", rawAction.TriggeringResourceAddr, diags.Err())
}
ret.ActionsListIndex = int(rawAction.ActionsListIndex)
ret.ActionTriggerBlockIndex = int(rawAction.ActionTriggerBlockIndex)
switch rawAction.TriggerEvent {
case planproto.ActionTriggerEvent_BEFORE_CERATE:
ret.TriggerEvent = configs.BeforeCreate
case planproto.ActionTriggerEvent_AFTER_CREATE:
ret.TriggerEvent = configs.AfterCreate
case planproto.ActionTriggerEvent_BEFORE_UPDATE:
ret.TriggerEvent = configs.BeforeUpdate
case planproto.ActionTriggerEvent_AFTER_UPDATE:
ret.TriggerEvent = configs.AfterUpdate
case planproto.ActionTriggerEvent_BEFORE_DESTROY:
ret.TriggerEvent = configs.BeforeDestroy
case planproto.ActionTriggerEvent_AFTER_DESTROY:
ret.TriggerEvent = configs.AfterDestroy
switch at := rawAction.ActionTrigger.(type) {
case *planproto.ActionInvocationInstance_LifecycleActionTrigger:
triggeringResourceAddrs, diags := addrs.ParseAbsResourceInstanceStr(at.LifecycleActionTrigger.TriggeringResourceAddr)
if diags.HasErrors() {
return nil, fmt.Errorf("invalid resource instance address %q: %w",
at.LifecycleActionTrigger.TriggeringResourceAddr, diags.Err())
}
var ate configs.ActionTriggerEvent
switch at.LifecycleActionTrigger.TriggerEvent {
case planproto.ActionTriggerEvent_BEFORE_CERATE:
ate = configs.BeforeCreate
case planproto.ActionTriggerEvent_AFTER_CREATE:
ate = configs.AfterCreate
case planproto.ActionTriggerEvent_BEFORE_UPDATE:
ate = configs.BeforeUpdate
case planproto.ActionTriggerEvent_AFTER_UPDATE:
ate = configs.AfterUpdate
case planproto.ActionTriggerEvent_BEFORE_DESTROY:
ate = configs.BeforeDestroy
case planproto.ActionTriggerEvent_AFTER_DESTROY:
ate = configs.AfterDestroy
default:
return nil, fmt.Errorf("invalid action trigger event %s", at.LifecycleActionTrigger.TriggerEvent)
}
ret.ActionTrigger = plans.LifecycleActionTrigger{
TriggeringResourceAddr: triggeringResourceAddrs,
ActionTriggerBlockIndex: int(at.LifecycleActionTrigger.ActionTriggerBlockIndex),
ActionsListIndex: int(at.LifecycleActionTrigger.ActionsListIndex),
ActionTriggerEvent: ate,
}
default:
return nil, fmt.Errorf("invalid action trigger event %s", rawAction.TriggerEvent)
// This should be exhaustive
return nil, fmt.Errorf("unsupported action trigger type %t", rawAction.ActionTrigger)
}
providerAddr, diags := addrs.ParseAbsProviderConfigStr(rawAction.Provider)
@ -1307,29 +1318,39 @@ func actionInvocationToTfPlan(action *plans.ActionInvocationInstanceSrc) (*planp
return nil, nil
}
triggerEvent := planproto.ActionTriggerEvent_INVALID_EVENT
switch action.TriggerEvent {
case configs.BeforeCreate:
triggerEvent = planproto.ActionTriggerEvent_BEFORE_CERATE
case configs.AfterCreate:
triggerEvent = planproto.ActionTriggerEvent_AFTER_CREATE
case configs.BeforeUpdate:
triggerEvent = planproto.ActionTriggerEvent_BEFORE_UPDATE
case configs.AfterUpdate:
triggerEvent = planproto.ActionTriggerEvent_AFTER_UPDATE
case configs.BeforeDestroy:
triggerEvent = planproto.ActionTriggerEvent_BEFORE_DESTROY
case configs.AfterDestroy:
triggerEvent = planproto.ActionTriggerEvent_AFTER_DESTROY
}
ret := &planproto.ActionInvocationInstance{
Addr: action.Addr.String(),
Provider: action.ProviderAddr.String(),
TriggeringResourceAddr: action.TriggeringResourceAddr.String(),
ActionsListIndex: int64(action.ActionsListIndex),
ActionTriggerBlockIndex: int64(action.ActionTriggerBlockIndex),
TriggerEvent: triggerEvent,
Addr: action.Addr.String(),
Provider: action.ProviderAddr.String(),
}
switch at := action.ActionTrigger.(type) {
case plans.LifecycleActionTrigger:
triggerEvent := planproto.ActionTriggerEvent_INVALID_EVENT
switch at.ActionTriggerEvent {
case configs.BeforeCreate:
triggerEvent = planproto.ActionTriggerEvent_BEFORE_CERATE
case configs.AfterCreate:
triggerEvent = planproto.ActionTriggerEvent_AFTER_CREATE
case configs.BeforeUpdate:
triggerEvent = planproto.ActionTriggerEvent_BEFORE_UPDATE
case configs.AfterUpdate:
triggerEvent = planproto.ActionTriggerEvent_AFTER_UPDATE
case configs.BeforeDestroy:
triggerEvent = planproto.ActionTriggerEvent_BEFORE_DESTROY
case configs.AfterDestroy:
triggerEvent = planproto.ActionTriggerEvent_AFTER_DESTROY
}
ret.ActionTrigger = &planproto.ActionInvocationInstance_LifecycleActionTrigger{
LifecycleActionTrigger: &planproto.LifecycleActionTrigger{
TriggerEvent: triggerEvent,
TriggeringResourceAddr: at.TriggeringResourceAddr.String(),
ActionTriggerBlockIndex: int64(at.ActionTriggerBlockIndex),
ActionsListIndex: int64(at.ActionsListIndex),
},
}
default:
// This should be exhaustive
return nil, fmt.Errorf("unsupported action trigger type: %T", at)
}
if action.ConfigValue != nil {

@ -305,28 +305,32 @@ func examplePlanForTest(t *testing.T) *plans.Plan {
},
ActionInvocations: []*plans.ActionInvocationInstanceSrc{
{
Addr: addrs.Action{Type: "example", Name: "foo"}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
ProviderAddr: provider,
TriggerEvent: configs.BeforeCreate,
ActionTriggerBlockIndex: 2,
ActionsListIndex: 0,
TriggeringResourceAddr: addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "test_thing",
Name: "woot",
}.Instance(addrs.IntKey(0)).Absolute(addrs.RootModuleInstance),
Addr: addrs.Action{Type: "example", Name: "foo"}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
ProviderAddr: provider,
ActionTrigger: plans.LifecycleActionTrigger{
ActionTriggerEvent: configs.BeforeCreate,
ActionTriggerBlockIndex: 2,
ActionsListIndex: 0,
TriggeringResourceAddr: addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "test_thing",
Name: "woot",
}.Instance(addrs.IntKey(0)).Absolute(addrs.RootModuleInstance),
},
},
{
Addr: addrs.Action{Type: "example", Name: "bar"}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
ProviderAddr: provider,
TriggerEvent: configs.BeforeCreate,
ActionTriggerBlockIndex: 2,
ActionsListIndex: 1,
TriggeringResourceAddr: addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "test_thing",
Name: "woot",
}.Instance(addrs.IntKey(0)).Absolute(addrs.RootModuleInstance),
Addr: addrs.Action{Type: "example", Name: "bar"}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
ProviderAddr: provider,
ActionTrigger: plans.LifecycleActionTrigger{
ActionTriggerEvent: configs.BeforeCreate,
ActionTriggerBlockIndex: 2,
ActionsListIndex: 1,
TriggeringResourceAddr: addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "test_thing",
Name: "woot",
}.Instance(addrs.IntKey(0)).Absolute(addrs.RootModuleInstance),
},
ConfigValue: mustNewDynamicValue(cty.ObjectVal(map[string]cty.Value{
"id": cty.StringVal("testing"),
}), objTy),

@ -1652,15 +1652,15 @@ type ActionInvocationInstance struct {
// provider is the address of the provider configuration that this change
// was planned with, and thus the configuration that must be used to
// apply it.
Provider string `protobuf:"bytes,2,opt,name=provider,proto3" json:"provider,omitempty"`
LinkedResources []*ResourceInstanceActionChange `protobuf:"bytes,3,rep,name=linked_resources,json=linkedResources,proto3" json:"linked_resources,omitempty"`
ConfigValue *DynamicValue `protobuf:"bytes,4,opt,name=config_value,json=configValue,proto3" json:"config_value,omitempty"`
TriggeringResourceAddr string `protobuf:"bytes,5,opt,name=triggering_resource_addr,json=triggeringResourceAddr,proto3" json:"triggering_resource_addr,omitempty"`
TriggerEvent ActionTriggerEvent `protobuf:"varint,6,opt,name=trigger_event,json=triggerEvent,proto3,enum=tfplan.ActionTriggerEvent" json:"trigger_event,omitempty"`
ActionTriggerBlockIndex int64 `protobuf:"varint,7,opt,name=action_trigger_block_index,json=actionTriggerBlockIndex,proto3" json:"action_trigger_block_index,omitempty"`
ActionsListIndex int64 `protobuf:"varint,8,opt,name=actions_list_index,json=actionsListIndex,proto3" json:"actions_list_index,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
Provider string `protobuf:"bytes,2,opt,name=provider,proto3" json:"provider,omitempty"`
LinkedResources []*ResourceInstanceActionChange `protobuf:"bytes,3,rep,name=linked_resources,json=linkedResources,proto3" json:"linked_resources,omitempty"`
ConfigValue *DynamicValue `protobuf:"bytes,4,opt,name=config_value,json=configValue,proto3" json:"config_value,omitempty"`
// Types that are valid to be assigned to ActionTrigger:
//
// *ActionInvocationInstance_LifecycleActionTrigger
ActionTrigger isActionInvocationInstance_ActionTrigger `protobuf_oneof:"action_trigger"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *ActionInvocationInstance) Reset() {
@ -1721,28 +1721,96 @@ func (x *ActionInvocationInstance) GetConfigValue() *DynamicValue {
return nil
}
func (x *ActionInvocationInstance) GetTriggeringResourceAddr() string {
func (x *ActionInvocationInstance) GetActionTrigger() isActionInvocationInstance_ActionTrigger {
if x != nil {
return x.ActionTrigger
}
return nil
}
func (x *ActionInvocationInstance) GetLifecycleActionTrigger() *LifecycleActionTrigger {
if x != nil {
if x, ok := x.ActionTrigger.(*ActionInvocationInstance_LifecycleActionTrigger); ok {
return x.LifecycleActionTrigger
}
}
return nil
}
type isActionInvocationInstance_ActionTrigger interface {
isActionInvocationInstance_ActionTrigger()
}
type ActionInvocationInstance_LifecycleActionTrigger struct {
LifecycleActionTrigger *LifecycleActionTrigger `protobuf:"bytes,5,opt,name=lifecycle_action_trigger,json=lifecycleActionTrigger,proto3,oneof"`
}
func (*ActionInvocationInstance_LifecycleActionTrigger) isActionInvocationInstance_ActionTrigger() {}
// LifecycleActionTrigger contains details on the conditions that led to the
// triggering of an action.
type LifecycleActionTrigger struct {
state protoimpl.MessageState `protogen:"open.v1"`
TriggeringResourceAddr string `protobuf:"bytes,1,opt,name=triggering_resource_addr,json=triggeringResourceAddr,proto3" json:"triggering_resource_addr,omitempty"`
TriggerEvent ActionTriggerEvent `protobuf:"varint,2,opt,name=trigger_event,json=triggerEvent,proto3,enum=tfplan.ActionTriggerEvent" json:"trigger_event,omitempty"`
ActionTriggerBlockIndex int64 `protobuf:"varint,3,opt,name=action_trigger_block_index,json=actionTriggerBlockIndex,proto3" json:"action_trigger_block_index,omitempty"`
ActionsListIndex int64 `protobuf:"varint,4,opt,name=actions_list_index,json=actionsListIndex,proto3" json:"actions_list_index,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *LifecycleActionTrigger) Reset() {
*x = LifecycleActionTrigger{}
mi := &file_planfile_proto_msgTypes[15]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *LifecycleActionTrigger) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*LifecycleActionTrigger) ProtoMessage() {}
func (x *LifecycleActionTrigger) ProtoReflect() protoreflect.Message {
mi := &file_planfile_proto_msgTypes[15]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use LifecycleActionTrigger.ProtoReflect.Descriptor instead.
func (*LifecycleActionTrigger) Descriptor() ([]byte, []int) {
return file_planfile_proto_rawDescGZIP(), []int{15}
}
func (x *LifecycleActionTrigger) GetTriggeringResourceAddr() string {
if x != nil {
return x.TriggeringResourceAddr
}
return ""
}
func (x *ActionInvocationInstance) GetTriggerEvent() ActionTriggerEvent {
func (x *LifecycleActionTrigger) GetTriggerEvent() ActionTriggerEvent {
if x != nil {
return x.TriggerEvent
}
return ActionTriggerEvent_INVALID_EVENT
}
func (x *ActionInvocationInstance) GetActionTriggerBlockIndex() int64 {
func (x *LifecycleActionTrigger) GetActionTriggerBlockIndex() int64 {
if x != nil {
return x.ActionTriggerBlockIndex
}
return 0
}
func (x *ActionInvocationInstance) GetActionsListIndex() int64 {
func (x *LifecycleActionTrigger) GetActionsListIndex() int64 {
if x != nil {
return x.ActionsListIndex
}
@ -1767,7 +1835,7 @@ type ResourceInstanceActionChange struct {
func (x *ResourceInstanceActionChange) Reset() {
*x = ResourceInstanceActionChange{}
mi := &file_planfile_proto_msgTypes[15]
mi := &file_planfile_proto_msgTypes[16]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@ -1779,7 +1847,7 @@ func (x *ResourceInstanceActionChange) String() string {
func (*ResourceInstanceActionChange) ProtoMessage() {}
func (x *ResourceInstanceActionChange) ProtoReflect() protoreflect.Message {
mi := &file_planfile_proto_msgTypes[15]
mi := &file_planfile_proto_msgTypes[16]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@ -1792,7 +1860,7 @@ func (x *ResourceInstanceActionChange) ProtoReflect() protoreflect.Message {
// Deprecated: Use ResourceInstanceActionChange.ProtoReflect.Descriptor instead.
func (*ResourceInstanceActionChange) Descriptor() ([]byte, []int) {
return file_planfile_proto_rawDescGZIP(), []int{15}
return file_planfile_proto_rawDescGZIP(), []int{16}
}
func (x *ResourceInstanceActionChange) GetAddr() string {
@ -1826,7 +1894,7 @@ type PlanResourceAttr struct {
func (x *PlanResourceAttr) Reset() {
*x = PlanResourceAttr{}
mi := &file_planfile_proto_msgTypes[17]
mi := &file_planfile_proto_msgTypes[18]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@ -1838,7 +1906,7 @@ func (x *PlanResourceAttr) String() string {
func (*PlanResourceAttr) ProtoMessage() {}
func (x *PlanResourceAttr) ProtoReflect() protoreflect.Message {
mi := &file_planfile_proto_msgTypes[17]
mi := &file_planfile_proto_msgTypes[18]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@ -1879,7 +1947,7 @@ type CheckResults_ObjectResult struct {
func (x *CheckResults_ObjectResult) Reset() {
*x = CheckResults_ObjectResult{}
mi := &file_planfile_proto_msgTypes[18]
mi := &file_planfile_proto_msgTypes[19]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@ -1891,7 +1959,7 @@ func (x *CheckResults_ObjectResult) String() string {
func (*CheckResults_ObjectResult) ProtoMessage() {}
func (x *CheckResults_ObjectResult) ProtoReflect() protoreflect.Message {
mi := &file_planfile_proto_msgTypes[18]
mi := &file_planfile_proto_msgTypes[19]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@ -1941,7 +2009,7 @@ type Path_Step struct {
func (x *Path_Step) Reset() {
*x = Path_Step{}
mi := &file_planfile_proto_msgTypes[19]
mi := &file_planfile_proto_msgTypes[20]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@ -1953,7 +2021,7 @@ func (x *Path_Step) String() string {
func (*Path_Step) ProtoMessage() {}
func (x *Path_Step) ProtoReflect() protoreflect.Message {
mi := &file_planfile_proto_msgTypes[19]
mi := &file_planfile_proto_msgTypes[20]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@ -2129,16 +2197,19 @@ const file_planfile_proto_rawDesc = "" +
"\aunknown\x18\x02 \x01(\bR\aunknown\x120\n" +
"\bidentity\x18\x03 \x01(\v2\x14.tfplan.DynamicValueR\bidentity\":\n" +
"\bDeferred\x12.\n" +
"\x06reason\x18\x01 \x01(\x0e2\x16.tfplan.DeferredReasonR\x06reason\"\xba\x03\n" +
"\x06reason\x18\x01 \x01(\x0e2\x16.tfplan.DeferredReasonR\x06reason\"\xc2\x02\n" +
"\x18ActionInvocationInstance\x12\x12\n" +
"\x04addr\x18\x01 \x01(\tR\x04addr\x12\x1a\n" +
"\bprovider\x18\x02 \x01(\tR\bprovider\x12O\n" +
"\x10linked_resources\x18\x03 \x03(\v2$.tfplan.ResourceInstanceActionChangeR\x0flinkedResources\x127\n" +
"\fconfig_value\x18\x04 \x01(\v2\x14.tfplan.DynamicValueR\vconfigValue\x128\n" +
"\x18triggering_resource_addr\x18\x05 \x01(\tR\x16triggeringResourceAddr\x12?\n" +
"\rtrigger_event\x18\x06 \x01(\x0e2\x1a.tfplan.ActionTriggerEventR\ftriggerEvent\x12;\n" +
"\x1aaction_trigger_block_index\x18\a \x01(\x03R\x17actionTriggerBlockIndex\x12,\n" +
"\x12actions_list_index\x18\b \x01(\x03R\x10actionsListIndex\"{\n" +
"\fconfig_value\x18\x04 \x01(\v2\x14.tfplan.DynamicValueR\vconfigValue\x12Z\n" +
"\x18lifecycle_action_trigger\x18\x05 \x01(\v2\x1e.tfplan.LifecycleActionTriggerH\x00R\x16lifecycleActionTriggerB\x10\n" +
"\x0eaction_trigger\"\xfe\x01\n" +
"\x16LifecycleActionTrigger\x128\n" +
"\x18triggering_resource_addr\x18\x01 \x01(\tR\x16triggeringResourceAddr\x12?\n" +
"\rtrigger_event\x18\x02 \x01(\x0e2\x1a.tfplan.ActionTriggerEventR\ftriggerEvent\x12;\n" +
"\x1aaction_trigger_block_index\x18\x03 \x01(\x03R\x17actionTriggerBlockIndex\x12,\n" +
"\x12actions_list_index\x18\x04 \x01(\x03R\x10actionsListIndex\"{\n" +
"\x1cResourceInstanceActionChange\x12\x12\n" +
"\x04addr\x18\x01 \x01(\tR\x04addr\x12\x1f\n" +
"\vdeposed_key\x18\x02 \x01(\tR\n" +
@ -2209,7 +2280,7 @@ func file_planfile_proto_rawDescGZIP() []byte {
}
var file_planfile_proto_enumTypes = make([]protoimpl.EnumInfo, 7)
var file_planfile_proto_msgTypes = make([]protoimpl.MessageInfo, 20)
var file_planfile_proto_msgTypes = make([]protoimpl.MessageInfo, 21)
var file_planfile_proto_goTypes = []any{
(Mode)(0), // 0: tfplan.Mode
(Action)(0), // 1: tfplan.Action
@ -2233,15 +2304,16 @@ var file_planfile_proto_goTypes = []any{
(*Importing)(nil), // 19: tfplan.Importing
(*Deferred)(nil), // 20: tfplan.Deferred
(*ActionInvocationInstance)(nil), // 21: tfplan.ActionInvocationInstance
(*ResourceInstanceActionChange)(nil), // 22: tfplan.ResourceInstanceActionChange
nil, // 23: tfplan.Plan.VariablesEntry
(*PlanResourceAttr)(nil), // 24: tfplan.Plan.resource_attr
(*CheckResults_ObjectResult)(nil), // 25: tfplan.CheckResults.ObjectResult
(*Path_Step)(nil), // 26: tfplan.Path.Step
(*LifecycleActionTrigger)(nil), // 22: tfplan.LifecycleActionTrigger
(*ResourceInstanceActionChange)(nil), // 23: tfplan.ResourceInstanceActionChange
nil, // 24: tfplan.Plan.VariablesEntry
(*PlanResourceAttr)(nil), // 25: tfplan.Plan.resource_attr
(*CheckResults_ObjectResult)(nil), // 26: tfplan.CheckResults.ObjectResult
(*Path_Step)(nil), // 27: tfplan.Path.Step
}
var file_planfile_proto_depIdxs = []int32{
0, // 0: tfplan.Plan.ui_mode:type_name -> tfplan.Mode
23, // 1: tfplan.Plan.variables:type_name -> tfplan.Plan.VariablesEntry
24, // 1: tfplan.Plan.variables:type_name -> tfplan.Plan.VariablesEntry
12, // 2: tfplan.Plan.resource_changes:type_name -> tfplan.ResourceInstanceChange
12, // 3: tfplan.Plan.resource_drift:type_name -> tfplan.ResourceInstanceChange
13, // 4: tfplan.Plan.deferred_changes:type_name -> tfplan.DeferredResourceInstanceChange
@ -2250,7 +2322,7 @@ var file_planfile_proto_depIdxs = []int32{
21, // 7: tfplan.Plan.action_invocations:type_name -> tfplan.ActionInvocationInstance
8, // 8: tfplan.Plan.backend:type_name -> tfplan.Backend
9, // 9: tfplan.Plan.state_store:type_name -> tfplan.StateStore
24, // 10: tfplan.Plan.relevant_attributes:type_name -> tfplan.Plan.resource_attr
25, // 10: tfplan.Plan.relevant_attributes:type_name -> tfplan.Plan.resource_attr
16, // 11: tfplan.Plan.function_results:type_name -> tfplan.FunctionCallHash
17, // 12: tfplan.Backend.config:type_name -> tfplan.DynamicValue
17, // 13: tfplan.StateStore.config:type_name -> tfplan.DynamicValue
@ -2270,23 +2342,24 @@ var file_planfile_proto_depIdxs = []int32{
11, // 27: tfplan.OutputChange.change:type_name -> tfplan.Change
6, // 28: tfplan.CheckResults.kind:type_name -> tfplan.CheckResults.ObjectKind
5, // 29: tfplan.CheckResults.status:type_name -> tfplan.CheckResults.Status
25, // 30: tfplan.CheckResults.objects:type_name -> tfplan.CheckResults.ObjectResult
26, // 31: tfplan.Path.steps:type_name -> tfplan.Path.Step
26, // 30: tfplan.CheckResults.objects:type_name -> tfplan.CheckResults.ObjectResult
27, // 31: tfplan.Path.steps:type_name -> tfplan.Path.Step
17, // 32: tfplan.Importing.identity:type_name -> tfplan.DynamicValue
3, // 33: tfplan.Deferred.reason:type_name -> tfplan.DeferredReason
22, // 34: tfplan.ActionInvocationInstance.linked_resources:type_name -> tfplan.ResourceInstanceActionChange
23, // 34: tfplan.ActionInvocationInstance.linked_resources:type_name -> tfplan.ResourceInstanceActionChange
17, // 35: tfplan.ActionInvocationInstance.config_value:type_name -> tfplan.DynamicValue
4, // 36: tfplan.ActionInvocationInstance.trigger_event:type_name -> tfplan.ActionTriggerEvent
11, // 37: tfplan.ResourceInstanceActionChange.change:type_name -> tfplan.Change
17, // 38: tfplan.Plan.VariablesEntry.value:type_name -> tfplan.DynamicValue
18, // 39: tfplan.Plan.resource_attr.attr:type_name -> tfplan.Path
5, // 40: tfplan.CheckResults.ObjectResult.status:type_name -> tfplan.CheckResults.Status
17, // 41: tfplan.Path.Step.element_key:type_name -> tfplan.DynamicValue
42, // [42:42] is the sub-list for method output_type
42, // [42:42] is the sub-list for method input_type
42, // [42:42] is the sub-list for extension type_name
42, // [42:42] is the sub-list for extension extendee
0, // [0:42] is the sub-list for field type_name
22, // 36: tfplan.ActionInvocationInstance.lifecycle_action_trigger:type_name -> tfplan.LifecycleActionTrigger
4, // 37: tfplan.LifecycleActionTrigger.trigger_event:type_name -> tfplan.ActionTriggerEvent
11, // 38: tfplan.ResourceInstanceActionChange.change:type_name -> tfplan.Change
17, // 39: tfplan.Plan.VariablesEntry.value:type_name -> tfplan.DynamicValue
18, // 40: tfplan.Plan.resource_attr.attr:type_name -> tfplan.Path
5, // 41: tfplan.CheckResults.ObjectResult.status:type_name -> tfplan.CheckResults.Status
17, // 42: tfplan.Path.Step.element_key:type_name -> tfplan.DynamicValue
43, // [43:43] is the sub-list for method output_type
43, // [43:43] is the sub-list for method input_type
43, // [43:43] is the sub-list for extension type_name
43, // [43:43] is the sub-list for extension extendee
0, // [0:43] is the sub-list for field type_name
}
func init() { file_planfile_proto_init() }
@ -2294,7 +2367,10 @@ func file_planfile_proto_init() {
if File_planfile_proto != nil {
return
}
file_planfile_proto_msgTypes[19].OneofWrappers = []any{
file_planfile_proto_msgTypes[14].OneofWrappers = []any{
(*ActionInvocationInstance_LifecycleActionTrigger)(nil),
}
file_planfile_proto_msgTypes[20].OneofWrappers = []any{
(*Path_Step_AttributeName)(nil),
(*Path_Step_ElementKey)(nil),
}
@ -2304,7 +2380,7 @@ func file_planfile_proto_init() {
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: unsafe.Slice(unsafe.StringData(file_planfile_proto_rawDesc), len(file_planfile_proto_rawDesc)),
NumEnums: 7,
NumMessages: 20,
NumMessages: 21,
NumExtensions: 0,
NumServices: 0,
},

@ -451,10 +451,18 @@ message ActionInvocationInstance {
repeated ResourceInstanceActionChange linked_resources = 3;
DynamicValue config_value = 4;
string triggering_resource_addr = 5;
ActionTriggerEvent trigger_event = 6;
int64 action_trigger_block_index = 7;
int64 actions_list_index = 8;
oneof action_trigger {
LifecycleActionTrigger lifecycle_action_trigger = 5;
}
}
// LifecycleActionTrigger contains details on the conditions that led to the
// triggering of an action.
message LifecycleActionTrigger {
string triggering_resource_addr = 1;
ActionTriggerEvent trigger_event = 2;
int64 action_trigger_block_index = 3;
int64 actions_list_index = 4;
}
message ResourceInstanceActionChange {

@ -107,18 +107,23 @@ resource "test_object" "a" {
t.Fatalf("expected action address to be 'action.test_unlinked.hello', got '%s'", action.Addr)
}
if !action.TriggeringResourceAddr.Equal(mustResourceInstanceAddr("test_object.a")) {
t.Fatalf("expected action to have a triggering resource address 'test_object.a', got '%s'", action.TriggeringResourceAddr)
at, ok := action.ActionTrigger.(plans.LifecycleActionTrigger)
if !ok {
t.Fatalf("expected action trigger to be a LifecycleActionTrigger, got %T", action.ActionTrigger)
}
if action.ActionTriggerBlockIndex != 0 {
t.Fatalf("expected action to have a triggering block index of 0, got %d", action.ActionTriggerBlockIndex)
if !at.TriggeringResourceAddr.Equal(mustResourceInstanceAddr("test_object.a")) {
t.Fatalf("expected action to have a triggering resource address 'test_object.a', got '%s'", at.TriggeringResourceAddr)
}
if action.TriggerEvent != configs.BeforeCreate {
t.Fatalf("expected action to have a triggering event of 'before_create', got '%s'", action.TriggerEvent)
if at.ActionTriggerBlockIndex != 0 {
t.Fatalf("expected action to have a triggering block index of 0, got %d", at.ActionTriggerBlockIndex)
}
if at.TriggerEvent() != configs.BeforeCreate {
t.Fatalf("expected action to have a triggering event of 'before_create', got '%s'", at.TriggerEvent())
}
if action.ActionsListIndex != 0 {
t.Fatalf("expected action to have a actions list index of 0, got %d", action.ActionsListIndex)
if at.ActionsListIndex != 0 {
t.Fatalf("expected action to have a actions list index of 0, got %d", at.ActionsListIndex)
}
if action.ProviderAddr.Provider != addrs.NewDefaultProvider("test") {
@ -902,18 +907,23 @@ resource "other_object" "a" {
t.Fatalf("expected action address to be 'module.mod.action.test_unlinked.hello', got '%s'", action.Addr)
}
if !action.TriggeringResourceAddr.Equal(mustResourceInstanceAddr("module.mod.other_object.a")) {
t.Fatalf("expected action to have triggering resource address 'module.mod.other_object.a', but it is %s", action.TriggeringResourceAddr)
at, ok := action.ActionTrigger.(plans.LifecycleActionTrigger)
if !ok {
t.Fatalf("expected action trigger to be a LifecycleActionTrigger, got %T", action.ActionTrigger)
}
if !at.TriggeringResourceAddr.Equal(mustResourceInstanceAddr("module.mod.other_object.a")) {
t.Fatalf("expected action to have triggering resource address 'module.mod.other_object.a', but it is %s", at.TriggeringResourceAddr)
}
if action.ActionTriggerBlockIndex != 0 {
t.Fatalf("expected action to have a triggering block index of 0, got %d", action.ActionTriggerBlockIndex)
if at.ActionTriggerBlockIndex != 0 {
t.Fatalf("expected action to have a triggering block index of 0, got %d", at.ActionTriggerBlockIndex)
}
if action.TriggerEvent != configs.BeforeCreate {
t.Fatalf("expected action to have a triggering event of 'before_create', got '%s'", action.TriggerEvent)
if at.TriggerEvent() != configs.BeforeCreate {
t.Fatalf("expected action to have a triggering event of 'before_create', got '%s'", at.TriggerEvent())
}
if action.ActionsListIndex != 0 {
t.Fatalf("expected action to have a actions list index of 0, got %d", action.ActionsListIndex)
if at.ActionsListIndex != 0 {
t.Fatalf("expected action to have a actions list index of 0, got %d", at.ActionsListIndex)
}
if action.ProviderAddr.Provider != addrs.NewDefaultProvider("test") {
@ -951,7 +961,15 @@ resource "other_object" "a" {
// We know we are run within two child modules, so we can just sort by the triggering resource address
slices.SortFunc(p.Changes.ActionInvocations, func(a, b *plans.ActionInvocationInstanceSrc) int {
if a.TriggeringResourceAddr.String() < b.TriggeringResourceAddr.String() {
at, ok := a.ActionTrigger.(plans.LifecycleActionTrigger)
if !ok {
t.Fatalf("expected action trigger to be a LifecycleActionTrigger, got %T", a.ActionTrigger)
}
bt, ok := b.ActionTrigger.(plans.LifecycleActionTrigger)
if !ok {
t.Fatalf("expected action trigger to be a LifecycleActionTrigger, got %T", b.ActionTrigger)
}
if at.TriggeringResourceAddr.String() < bt.TriggeringResourceAddr.String() {
return -1
} else {
return 1
@ -963,18 +981,20 @@ resource "other_object" "a" {
t.Fatalf("expected action address to be 'module.mod[0].action.test_unlinked.hello', got '%s'", action.Addr)
}
if !action.TriggeringResourceAddr.Equal(mustResourceInstanceAddr("module.mod[0].other_object.a")) {
t.Fatalf("expected action to have triggering resource address 'module.mod[0].other_object.a', but it is %s", action.TriggeringResourceAddr)
at := action.ActionTrigger.(plans.LifecycleActionTrigger)
if !at.TriggeringResourceAddr.Equal(mustResourceInstanceAddr("module.mod[0].other_object.a")) {
t.Fatalf("expected action to have triggering resource address 'module.mod[0].other_object.a', but it is %s", at.TriggeringResourceAddr)
}
if action.ActionTriggerBlockIndex != 0 {
t.Fatalf("expected action to have a triggering block index of 0, got %d", action.ActionTriggerBlockIndex)
if at.ActionTriggerBlockIndex != 0 {
t.Fatalf("expected action to have a triggering block index of 0, got %d", at.ActionTriggerBlockIndex)
}
if action.TriggerEvent != configs.BeforeCreate {
t.Fatalf("expected action to have a triggering event of 'before_create', got '%s'", action.TriggerEvent)
if at.TriggerEvent() != configs.BeforeCreate {
t.Fatalf("expected action to have a triggering event of 'before_create', got '%s'", at.TriggerEvent())
}
if action.ActionsListIndex != 0 {
t.Fatalf("expected action to have a actions list index of 0, got %d", action.ActionsListIndex)
if at.ActionsListIndex != 0 {
t.Fatalf("expected action to have a actions list index of 0, got %d", at.ActionsListIndex)
}
if action.ProviderAddr.Provider != addrs.NewDefaultProvider("test") {
@ -986,8 +1006,10 @@ resource "other_object" "a" {
t.Fatalf("expected action address to be 'module.mod[1].action.test_unlinked.hello', got '%s'", action2.Addr)
}
if !action2.TriggeringResourceAddr.Equal(mustResourceInstanceAddr("module.mod[1].other_object.a")) {
t.Fatalf("expected action to have triggering resource address 'module.mod[1].other_object.a', but it is %s", action2.TriggeringResourceAddr)
a2t := action2.ActionTrigger.(plans.LifecycleActionTrigger)
if !a2t.TriggeringResourceAddr.Equal(mustResourceInstanceAddr("module.mod[1].other_object.a")) {
t.Fatalf("expected action to have triggering resource address 'module.mod[1].other_object.a', but it is %s", a2t.TriggeringResourceAddr)
}
},
},
@ -1028,8 +1050,13 @@ resource "other_object" "a" {
t.Fatalf("expected action address to be 'module.mod.action.test_unlinked.hello', got '%s'", action.Addr)
}
if !action.TriggeringResourceAddr.Equal(mustResourceInstanceAddr("module.mod.other_object.a")) {
t.Fatalf("expected action to have triggering resource address 'module.mod.other_object.a', but it is %s", action.TriggeringResourceAddr)
at, ok := action.ActionTrigger.(plans.LifecycleActionTrigger)
if !ok {
t.Fatalf("expected action trigger to be a lifecycle action trigger, got %T", action.ActionTrigger)
}
if !at.TriggeringResourceAddr.Equal(mustResourceInstanceAddr("module.mod.other_object.a")) {
t.Fatalf("expected action to have triggering resource address 'module.mod.other_object.a', but it is %s", at.TriggeringResourceAddr)
}
if action.ProviderAddr.Module.String() != "module.mod" {
@ -1072,9 +1099,13 @@ resource "other_object" "a" {
if action.Addr.String() != "action.ecosystem_unlinked.hello" {
t.Fatalf("expected action address to be 'action.ecosystem_unlinked.hello', got '%s'", action.Addr)
}
at, ok := action.ActionTrigger.(plans.LifecycleActionTrigger)
if !ok {
t.Fatalf("expected action trigger to be a LifecycleActionTrigger, got %T", action.ActionTrigger)
}
if !action.TriggeringResourceAddr.Equal(mustResourceInstanceAddr("other_object.a")) {
t.Fatalf("expected action to have triggering resource address 'other_object.a', but it is %s", action.TriggeringResourceAddr)
if !at.TriggeringResourceAddr.Equal(mustResourceInstanceAddr("other_object.a")) {
t.Fatalf("expected action to have triggering resource address 'other_object.a', but it is %s", at.TriggeringResourceAddr)
}
if action.ProviderAddr.Provider.Namespace != "danielmschmidt" {
@ -1114,8 +1145,13 @@ resource "other_object" "a" {
t.Fatalf("expected action address to be 'action.test_unlinked.hello', got '%s'", action.Addr)
}
if !action.TriggeringResourceAddr.Equal(mustResourceInstanceAddr("other_object.a")) {
t.Fatalf("expected action to have triggering resource address 'other_object.a', but it is %s", action.TriggeringResourceAddr)
at, ok := action.ActionTrigger.(plans.LifecycleActionTrigger)
if !ok {
t.Fatalf("expected action trigger to be a LifecycleActionTrigger, got %T", action.ActionTrigger)
}
if !at.TriggeringResourceAddr.Equal(mustResourceInstanceAddr("other_object.a")) {
t.Fatalf("expected action to have triggering resource address 'other_object.a', but it is %s", at.TriggeringResourceAddr)
}
if action.ProviderAddr.Alias != "aliased" {

@ -38,15 +38,11 @@ type HookResourceIdentity struct {
type HookActionIdentity struct {
Addr addrs.AbsActionInstance
// If run as part of a plan / apply we also have the values below
// (if CLI triggered they are not applicable)
TriggeringResourceAddr addrs.AbsResourceInstance
ActionTriggerBlockIndex int
ActionsListIndex int
ActionTrigger plans.ActionTrigger
}
func (i *HookActionIdentity) String() string {
return i.Addr.String() + " (triggered by " + i.TriggeringResourceAddr.String() + ")"
return i.Addr.String() + " (triggered by " + i.ActionTrigger.String() + ")"
}
// Hook is the interface that must be implemented to hook into various

@ -57,13 +57,14 @@ func invokeActions(ctx EvalContext, actionInvocations []*plans.ActionInvocationI
// This way we have the correct order of execution.
orderedActionInvocations := make([]*plans.ActionInvocationInstance, 0, len(actionInvocations))
for _, invocation := range actionInvocations {
ai := ctx.Changes().GetActionInvocation(invocation.Addr, invocation.TriggeringResourceAddr, invocation.ActionTriggerBlockIndex, invocation.ActionsListIndex)
at := invocation.ActionTrigger.(plans.LifecycleActionTrigger) // We only support lifecycle actions for now.
ai := ctx.Changes().GetActionInvocation(invocation.Addr, at)
if ai == nil {
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
fmt.Sprintf("Failed to find action invocation instance %s in changes.", ai.Addr),
fmt.Sprintf("The action invocation instance %s was not found in the changes for %s.", ai.Addr, ai.TriggeringResourceAddr.String()),
fmt.Sprintf("The action invocation instance %s was not found in the changes for %s.", ai.Addr, at.TriggeringResourceAddr.String()),
))
return finishedActionInvocations, diags
}
@ -71,10 +72,12 @@ func invokeActions(ctx EvalContext, actionInvocations []*plans.ActionInvocationI
orderedActionInvocations = append(orderedActionInvocations, ai)
}
sort.Slice(orderedActionInvocations, func(i, j int) bool {
if orderedActionInvocations[i].ActionTriggerBlockIndex == orderedActionInvocations[j].ActionTriggerBlockIndex {
return orderedActionInvocations[i].ActionsListIndex < orderedActionInvocations[j].ActionsListIndex
ati := orderedActionInvocations[i].ActionTrigger.(plans.LifecycleActionTrigger)
atj := orderedActionInvocations[i].ActionTrigger.(plans.LifecycleActionTrigger)
if ati.ActionTriggerBlockIndex == atj.ActionTriggerBlockIndex {
return ati.ActionsListIndex < atj.ActionsListIndex
}
return orderedActionInvocations[i].ActionTriggerBlockIndex < orderedActionInvocations[j].ActionTriggerBlockIndex
return ati.ActionTriggerBlockIndex < atj.ActionTriggerBlockIndex
})
// Now we ensure we have an expanded action instance for each action invocations.
@ -139,10 +142,8 @@ func invokeActions(ctx EvalContext, actionInvocations []*plans.ActionInvocationI
}
hookIdentity := HookActionIdentity{
Addr: ai.Addr,
TriggeringResourceAddr: ai.TriggeringResourceAddr,
ActionTriggerBlockIndex: ai.ActionTriggerBlockIndex,
ActionsListIndex: ai.ActionsListIndex,
Addr: ai.Addr,
ActionTrigger: ai.ActionTrigger,
}
ctx.Hook(func(h Hook) (HookAction, error) {
@ -288,10 +289,10 @@ func areBeforeActionInvocations(actionInvocations []*plans.ActionInvocationInsta
if len(actionInvocations) == 0 {
panic("areBeforeActionInvocations called with empty actionInvocations")
}
firstEvent := actionInvocations[0].TriggerEvent
firstEvent := actionInvocations[0].ActionTrigger.TriggerEvent()
for _, ai := range actionInvocations {
if ai.TriggerEvent != firstEvent {
panic(fmt.Sprintf("areBeforeActionInvocations called with action invocations with different trigger events: %s != %s", firstEvent, ai.TriggerEvent))
if ai.ActionTrigger.TriggerEvent() != firstEvent {
panic(fmt.Sprintf("areBeforeActionInvocations called with action invocations with different trigger events: %s != %s", firstEvent, ai.ActionTrigger.TriggerEvent()))
}
}

@ -647,13 +647,15 @@ func (n *NodePlannableResourceInstance) planActionTriggers(ctx EvalContext, chan
}
ctx.Changes().AppendActionInvocation(&plans.ActionInvocationInstance{
Addr: absActionAddr,
ProviderAddr: actionInstance.ProviderAddr,
TriggeringResourceAddr: n.Addr,
TriggerEvent: *triggeringEvent,
ActionTriggerBlockIndex: i,
ActionsListIndex: j,
ConfigValue: actionInstance.ConfigValue,
Addr: absActionAddr,
ProviderAddr: actionInstance.ProviderAddr,
ConfigValue: actionInstance.ConfigValue,
ActionTrigger: plans.LifecycleActionTrigger{
TriggeringResourceAddr: n.Addr,
ActionTriggerEvent: *triggeringEvent,
ActionTriggerBlockIndex: i,
ActionsListIndex: j,
},
})
}
}

@ -89,9 +89,13 @@ func (t *DiffTransformer) Transform(g *Graph) error {
runBeforeNode := addrs.MakeMap[addrs.AbsResourceInstance, []*plans.ActionInvocationInstanceSrc]()
runAfterNode := addrs.MakeMap[addrs.AbsResourceInstance, []*plans.ActionInvocationInstanceSrc]()
for _, ai := range changes.ActionInvocations {
if _, ok := ai.ActionTrigger.(plans.LifecycleActionTrigger); !ok {
continue
}
ait := ai.ActionTrigger.(plans.LifecycleActionTrigger)
var targetMap addrs.Map[addrs.AbsResourceInstance, []*plans.ActionInvocationInstanceSrc]
switch ai.TriggerEvent {
switch ait.ActionTriggerEvent {
case configs.BeforeCreate, configs.BeforeUpdate, configs.BeforeDestroy:
targetMap = runBeforeNode
case configs.AfterCreate, configs.AfterUpdate, configs.AfterDestroy:
@ -101,11 +105,11 @@ func (t *DiffTransformer) Transform(g *Graph) error {
}
basis := []*plans.ActionInvocationInstanceSrc{}
if targetMap.Has(ai.TriggeringResourceAddr) {
basis = targetMap.Get(ai.TriggeringResourceAddr)
if targetMap.Has(ait.TriggeringResourceAddr) {
basis = targetMap.Get(ait.TriggeringResourceAddr)
}
targetMap.Put(ai.TriggeringResourceAddr, append(basis, ai))
targetMap.Put(ait.TriggeringResourceAddr, append(basis, ai))
}
for _, rc := range changes.Resources {

Loading…
Cancel
Save