Skip to content
Draft
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
755e612
Add Action Invocation information to Stacks Protobuf
mutahhir Oct 10, 2025
f241e72
Generate code from protobuf definition
mutahhir Oct 10, 2025
b53aee4
Fix typo in ActionTriggerEvent
mutahhir Oct 10, 2025
80b270d
Allow requesting action schema from plan producer
mutahhir Oct 10, 2025
c907523
First pass at including action invocations within stacks proto
mutahhir Oct 10, 2025
df9454b
Populate ActionTrigger
mutahhir Oct 10, 2025
87911e2
Fix bug with getting schema
mutahhir Oct 13, 2025
11805bb
Start sending change event for action invocation planning
mutahhir Oct 13, 2025
23f96bd
Include action schema in dependencies provider
mutahhir Oct 17, 2025
382e8c6
Add action type to action instance for schema lookup
mutahhir Oct 17, 2025
56690c5
Send over action type in planned change
mutahhir Oct 17, 2025
236f734
update field name to ActionTypes after rename
mutahhir Oct 17, 2025
3afe760
Fix action type to send the type and not the name
mutahhir Oct 17, 2025
8a77b19
Add deferred actions support in stacks proto
mutahhir Oct 22, 2025
200e97c
Add Deferred Action Invocation to tfstackdata
mutahhir Oct 23, 2025
f375ae0
Populate Deferred Action Invocations
mutahhir Oct 23, 2025
a3d693e
Change comment
mutahhir Oct 30, 2025
a594c77
Just trying to allow this for now
RonRicardo Nov 5, 2025
d2a6676
WIP
RonRicardo Nov 12, 2025
b17c456
Add test for raw action invocation with malformed action address
RonRicardo Nov 18, 2025
f9d5ffc
Forward the existing StartAction, ProgressAction, CompleteAction even…
RonRicardo Nov 20, 2025
5c2888e
proto: Add ActionInvocationStatus message and enum to StackChangeProg…
RonRicardo Nov 21, 2025
d13bb34
Update stacks.go to add ReportActionInvocationStatus hook
RonRicardo Nov 21, 2025
93f472a
Add ParseAbsActionInvocationInstance helpers
RonRicardo Nov 21, 2025
73e78ca
Add ActionInvocationStatusHookData struct
RonRicardo Nov 21, 2025
f64c999
Update hooks.go to use ActionInvocationStatusHookData for tthe hook, …
RonRicardo Nov 21, 2025
f3475e8
Test and capture ActionInvocationStatus hooks
RonRicardo Nov 21, 2025
269f6aa
Use NewActionInvocationInStackAddr
RonRicardo Nov 25, 2025
e2a9b83
WIP: lots of debug logging, trying to follow the flow of action invoc…
RonRicardo Nov 26, 2025
7e35bab
Transfer action invocations to plan and populate ActionTargetAddrs:
RonRicardo Dec 1, 2025
ccdb2bc
Add more logging to action invocatinon hooks
RonRicardo Dec 1, 2025
9b67ab1
Test hooks + add generated code
RonRicardo Dec 1, 2025
15ccb9b
Restore transform_action_diff.go
RonRicardo Dec 2, 2025
15c3f0a
Don't populate plan.ActionTargetAddrs
RonRicardo Dec 2, 2025
5ca0f7c
Add applied action invocation message type
RonRicardo Dec 2, 2025
a3c278c
Add ActionInvocationProgress message
RonRicardo Dec 3, 2025
52517a5
Generated from the proto
RonRicardo Dec 3, 2025
362e61b
Add ActionInvocationProgressHookData type
RonRicardo Dec 3, 2025
7d59cb5
Implement ProgressAction reporting
RonRicardo Dec 3, 2025
1d0ae23
Fire pending status during preApply
RonRicardo Dec 3, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
First pass at including action invocations within stacks proto
  • Loading branch information
mutahhir committed Oct 31, 2025
commit c907523ea1a4866f8c0f1a0c3ae681cfa7425abc
11 changes: 8 additions & 3 deletions internal/plans/planfile/tfplan.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,10 @@ import (
"github.com/hashicorp/terraform/version"
)

const tfplanFormatVersion = 3
const tfplanFilename = "tfplan"
const (
tfplanFormatVersion = 3
tfplanFilename = "tfplan"
)

// ---------------------------------------------------------------------------
// This file deals with the internal structure of the "tfplan" sub-file within
Expand Down Expand Up @@ -403,7 +405,6 @@ func ActionFromProto(rawAction planproto.Action) (plans.Action, error) {
default:
return plans.NoOp, fmt.Errorf("invalid change action %s", rawAction)
}

}

func changeFromTfplan(rawChange *planproto.Change) (*plans.ChangeSrc, error) {
Expand Down Expand Up @@ -1389,6 +1390,10 @@ func actionInvocationFromTfplan(rawAction *planproto.ActionInvocationInstance) (
return ret, nil
}

func ActionInvocationToProto(action *plans.ActionInvocationInstanceSrc) (*planproto.ActionInvocationInstance, error) {
return actionInvocationToTfPlan(action)
}

func actionInvocationToTfPlan(action *plans.ActionInvocationInstanceSrc) (*planproto.ActionInvocationInstance, error) {
if action == nil {
return nil, nil
Expand Down
7 changes: 7 additions & 0 deletions internal/rpcapi/terraform1/stacks/conversion.go
Original file line number Diff line number Diff line change
Expand Up @@ -177,3 +177,10 @@ func NewResourceInstanceObjectInStackAddr(addr stackaddrs.AbsResourceInstanceObj
DeposedKey: addr.Item.DeposedKey.String(),
}
}

func NewActionInvocationInStackAddr(addr stackaddrs.AbsActionInvocationInstance) *ActionInvocationInstanceInStackAddr {
return &ActionInvocationInstanceInStackAddr{
ComponentInstanceAddr: addr.Component.String(),
ActionInvocationInstanceAddr: addr.Item.String(),
}
}
26 changes: 13 additions & 13 deletions internal/rpcapi/terraform1/stacks/stacks.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion internal/rpcapi/terraform1/stacks/stacks.proto
Original file line number Diff line number Diff line change
Expand Up @@ -539,7 +539,7 @@ message PlannedChange {
bool plan_applyable = 4;
ResourceInstanceDeferred resource_instance_deferred = 5;
InputVariable input_variable_planned = 6;
ActionInvocationInstance action_invocation_instance = 7;
ActionInvocationInstance action_invocation_planned = 7;
}
}

Expand Down
4 changes: 4 additions & 0 deletions internal/stacks/stackaddrs/in_component.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,10 @@ type AbsResourceInstance = InAbsComponentInstance[addrs.AbsResourceInstance]
// of a resource from inside a particular component instance.
type AbsResourceInstanceObject = InAbsComponentInstance[addrs.AbsResourceInstanceObject]

// AbsActionInvocationInstance represents an instance of an action from inside a
// particular component instance.
type AbsActionInvocationInstance = InAbsComponentInstance[addrs.AbsActionInstance]

// AbsModuleInstance represents an instance of a module from inside a
// particular component instance.
//
Expand Down
30 changes: 30 additions & 0 deletions internal/stacks/stackplan/from_plan.go
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,36 @@ func FromPlan(ctx context.Context, config *configs.Config, plan *plans.Plan, ref
seenObjects.Add(objAddr)
}

// Keep track of Action Invocations
for _, actionChange := range plan.Changes.ActionInvocations {
schema, err := producer.ActionSchema(
ctx,
actionChange.ProviderAddr.Provider,
actionChange.Addr.String(),
)
if err != nil {
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
"Can't fetch provider schema to save plan",
fmt.Sprintf(
"Failed to retrieve the schema for %s from provider %s: %s. This is a bug in Terraform.",
actionChange.Addr, actionChange.ProviderAddr.Provider, err,
),
))
continue
}

changes = append(changes, &PlannedChangeActionInvocationInstancePlanned{
ActionInvocationAddr: stackaddrs.AbsActionInvocationInstance{
Component: producer.Addr(),
Item: actionChange.Addr,
},
Invocation: actionChange,
Schema: schema,
ProviderConfigAddr: actionChange.ProviderAddr,
})
}

// We also need to catch any objects that exist in the "prior state"
// but don't have any actions planned, since we still need to capture
// the prior state part in case it was updated by refreshing during
Expand Down
111 changes: 110 additions & 1 deletion internal/stacks/stackplan/planned_change.go
Original file line number Diff line number Diff line change
Expand Up @@ -493,7 +493,6 @@ func (pc *PlannedChangeResourceInstancePlanned) ChangeDescription() (*stacks.Pla
},
},
}, nil

}

func DynamicValueToTerraform1(val cty.Value, ty cty.Type) (*stacks.DynamicValue, error) {
Expand Down Expand Up @@ -853,3 +852,113 @@ func (pc *PlannedChangeProviderFunctionResults) PlannedChangeProto() (*stacks.Pl
Raw: []*anypb.Any{&raw},
}, nil
}

type PlannedChangeActionInvocationInstancePlanned struct {
ActionInvocationAddr stackaddrs.AbsActionInvocationInstance

// Invocation describes the planned invocation.
Invocation *plans.ActionInvocationInstanceSrc

// ProviderConfigAddr is the address of the provider configuration
// that planned this change, resolved in terms of the configuration for
// the component this resource instance object belongs to.
ProviderConfigAddr addrs.AbsProviderConfig

// Schema MUST be the same schema that was used to encode the dynamic
// values inside ChangeSrc
//
// Can be empty if and only if ChangeSrc is nil.
Schema providers.ActionSchema
}

var _ PlannedChange = (*PlannedChangeActionInvocationInstancePlanned)(nil)

func (pc *PlannedChangeActionInvocationInstancePlanned) PlanActionInvocationProto() (*tfstackdata1.PlanActionInvocationPlanned, error) {
addr := pc.ActionInvocationAddr

if pc.Invocation == nil {
// This is just a stubby placeholder to remind us to drop the
// apparently-deleted-outside-of-Terraform object from the state
// if this plan later gets applied.

return &tfstackdata1.PlanActionInvocationPlanned{
ComponentInstanceAddr: addr.Component.String(),
ActionInvocationAddr: addr.Item.String(),
ProviderConfigAddr: pc.ProviderConfigAddr.String(),
}, nil
}

invocationProto, err := planfile.ActionInvocationToProto(pc.Invocation)
if err != nil {
return nil, fmt.Errorf("converting action invocation to proto: %w", err)
}

return &tfstackdata1.PlanActionInvocationPlanned{
ComponentInstanceAddr: addr.Component.String(),
ActionInvocationAddr: addr.Item.String(),
ProviderConfigAddr: pc.ProviderConfigAddr.String(),
Invocation: invocationProto,
}, nil
}

func (pc *PlannedChangeActionInvocationInstancePlanned) ChangeDescription() (*stacks.PlannedChange_ChangeDescription, error) {
addr := pc.ActionInvocationAddr

// We only emit an external description if there's an invocation to describe.
if pc.Invocation == nil {
return nil, nil
}

return &stacks.PlannedChange_ChangeDescription{
Description: &stacks.PlannedChange_ChangeDescription_ActionInvocationPlanned{
ActionInvocationPlanned: &stacks.PlannedChange_ActionInvocationInstance{
Addr: stacks.NewActionInvocationInStackAddr(addr),
ProviderAddr: pc.Invocation.ProviderAddr.Provider.String(),

ConfigValue: stacks.NewDynamicValue(
pc.Invocation.ConfigValue,
pc.Invocation.SensitiveConfigPaths,
),

ActionTrigger: nil,
},
},
}, nil
}

// PlannedChangeProto implements PlannedChange.
func (pc *PlannedChangeActionInvocationInstancePlanned) PlannedChangeProto() (*stacks.PlannedChange, error) {
paip, err := pc.PlanActionInvocationProto()
if err != nil {
return nil, err
}
var raw anypb.Any
err = anypb.MarshalFrom(&raw, paip, proto.MarshalOptions{})
if err != nil {
return nil, err
}

if pc.Invocation == nil {
// We only emit a "raw" in this case, because this is a relatively
// uninteresting edge-case. The PlanActionInvocationProto
// function should have returned a placeholder value for this use case.

return &stacks.PlannedChange{
Raw: []*anypb.Any{&raw},
}, nil
}

var descs []*stacks.PlannedChange_ChangeDescription
desc, err := pc.ChangeDescription()
if err != nil {
return nil, err
}
if desc != nil {
descs = append(descs, desc)
}

return &stacks.PlannedChange{
Raw: []*anypb.Any{&raw},
Descriptions: descs,
}, nil
}
Loading