// Copyright IBM Corp. 2014, 2026 // SPDX-License-Identifier: BUSL-1.1 package plans import ( "fmt" "github.com/zclconf/go-cty/cty" "github.com/hashicorp/terraform/internal/addrs" "github.com/hashicorp/terraform/internal/configs" "github.com/hashicorp/terraform/internal/lang/marks" "github.com/hashicorp/terraform/internal/providers" "github.com/hashicorp/terraform/internal/tfdiags" ) type ActionInvocationInstance struct { Addr addrs.AbsActionInstance 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 // used to apply it. ProviderAddr addrs.AbsProviderConfig ConfigValue cty.Value } func (ai *ActionInvocationInstance) Equals(other *ActionInvocationInstance) bool { // Since the trigger can be the same if it's a CLI invocation we also compare the action addr return ai.Addr.Equal(other.Addr) && ai.ActionTrigger.Equals(other.ActionTrigger) } type ActionTrigger interface { actionTriggerSigil() TriggerEvent() configs.ActionTriggerEvent String() string Equals(to ActionTrigger) bool Less(other ActionTrigger) bool } var ( _ ActionTrigger = (*ResourceActionTrigger)(nil) _ ActionTrigger = (*InvokeActionTrigger)(nil) ) // ResourceActionTrigger contains the action trigger configuration from the resource. type ResourceActionTrigger 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 *ResourceActionTrigger) TriggerEvent() configs.ActionTriggerEvent { return t.ActionTriggerEvent } func (t *ResourceActionTrigger) actionTriggerSigil() {} func (t *ResourceActionTrigger) String() string { return t.TriggeringResourceAddr.String() } func (t *ResourceActionTrigger) Equals(other ActionTrigger) bool { o, ok := other.(*ResourceActionTrigger) if !ok { return false } return t.TriggeringResourceAddr.Equal(o.TriggeringResourceAddr) && t.ActionTriggerBlockIndex == o.ActionTriggerBlockIndex && t.ActionsListIndex == o.ActionsListIndex && t.ActionTriggerEvent == o.ActionTriggerEvent } func (t *ResourceActionTrigger) Less(other ActionTrigger) bool { o, ok := other.(*ResourceActionTrigger) if !ok { return false // We always want to show non-lifecycle actions first } return t.TriggeringResourceAddr.Less(o.TriggeringResourceAddr) || (t.TriggeringResourceAddr.Equal(o.TriggeringResourceAddr) && t.ActionTriggerBlockIndex < o.ActionTriggerBlockIndex) || (t.TriggeringResourceAddr.Equal(o.TriggeringResourceAddr) && t.ActionTriggerBlockIndex == o.ActionTriggerBlockIndex && t.ActionsListIndex < o.ActionsListIndex && t.ActionTriggerEvent < o.ActionTriggerEvent) } // InvokeActionTrigger contains the configuration for an action triggered // (invoked) directly via CLI command. type InvokeActionTrigger struct{} func (t *InvokeActionTrigger) actionTriggerSigil() {} func (t *InvokeActionTrigger) String() string { return "CLI" } func (t *InvokeActionTrigger) TriggerEvent() configs.ActionTriggerEvent { return configs.Invoke } func (t *InvokeActionTrigger) Equals(other ActionTrigger) bool { _, ok := other.(*InvokeActionTrigger) if !ok { return false } return true // InvokeActionTriggers are always considered equal } func (t *InvokeActionTrigger) Less(other ActionTrigger) bool { // always return true, actions that are equal are already ordered by // address externally. these actions should go first anyway. return true } // 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, ActionTrigger: ai.ActionTrigger, ProviderAddr: ai.ProviderAddr, } if ai.ConfigValue != cty.NilVal { ty := cty.DynamicPseudoType if schema != nil { ty = schema.ConfigSchema.ImpliedType() } unmarkedConfigValue, pvms := ai.ConfigValue.UnmarkDeepWithPaths() sensitivePaths, otherMarks := marks.PathsWithMark(pvms, marks.Sensitive) if len(otherMarks) > 0 { return nil, fmt.Errorf("%s: error serializing action invocation with unexpected marks on config value: %#v. This is a bug in Terraform.", tfdiags.FormatCtyPath(otherMarks[0].Path), otherMarks[0].Marks) } var err error ret.ConfigValue, err = NewDynamicValue(unmarkedConfigValue, ty) ret.SensitiveConfigPaths = sensitivePaths if err != nil { return nil, err } } return ret, nil } type ActionInvocationInstances []*ActionInvocationInstance func (ais ActionInvocationInstances) DeepCopy() ActionInvocationInstances { if ais == nil { return ais } ret := make(ActionInvocationInstances, len(ais)) for i, ai := range ais { ret[i] = ai.DeepCopy() } return ret } func (ai *ActionInvocationInstance) DeepCopy() *ActionInvocationInstance { if ai == nil { return ai } ret := *ai return &ret }