Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
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
11 changes: 6 additions & 5 deletions internal/command/views/json/message_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,12 @@ const (
MessageDiagnostic MessageType = "diagnostic"

// Operation results
MessageResourceDrift MessageType = "resource_drift"
MessagePlannedChange MessageType = "planned_change"
MessagePlannedActionInvocation MessageType = "planned_action_invocation"
MessageChangeSummary MessageType = "change_summary"
MessageOutputs MessageType = "outputs"
MessageResourceDrift MessageType = "resource_drift"
MessagePlannedChange MessageType = "planned_change"
MessagePlannedActionInvocation MessageType = "planned_action_invocation"
MessageAppliedActionInvocation MessageType = "applied_action_invocation"
MessageChangeSummary MessageType = "change_summary"
MessageOutputs MessageType = "outputs"

// Hook-driven messages
MessageApplyStart MessageType = "apply_start"
Expand Down
8 changes: 8 additions & 0 deletions internal/command/views/json_view.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,14 @@ func (v *JSONView) PlannedActionInvocation(action *json.ActionInvocation) {
)
}

func (v *JSONView) AppliedActionInvocation(action *json.ActionInvocation) {
v.log.Info(
fmt.Sprintf("applied action invocation: %s", action.Action.Action),
"type", json.MessageAppliedActionInvocation,
"invocation", action,
)
}

func (v *JSONView) ResourceDrift(c *json.ResourceInstanceChange) {
v.log.Info(
fmt.Sprintf("%s: Drift detected (%s)", c.Resource.Addr, c.Action),
Expand Down
17 changes: 11 additions & 6 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 @@ -198,7 +200,7 @@ func readTfplan(r io.Reader) (*plans.Plan, error) {
}

for _, rawAction := range rawPlan.ActionInvocations {
action, err := actionInvocationFromTfplan(rawAction)
action, err := ActionInvocationFromTfplan(rawAction)
if err != nil {
// errors from actionInvocationFromTfplan already include context
return nil, err
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 @@ -563,7 +564,7 @@ func deferredActionInvocationFromTfplan(dai *planproto.DeferredActionInvocation)
return nil, fmt.Errorf("deferred action invocation object is absent")
}

actionInvocation, err := actionInvocationFromTfplan(dai.ActionInvocation)
actionInvocation, err := ActionInvocationFromTfplan(dai.ActionInvocation)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -1314,7 +1315,7 @@ func CheckResultsToPlanProto(checkResults *states.CheckResults) ([]*planproto.Ch
}
}

func actionInvocationFromTfplan(rawAction *planproto.ActionInvocationInstance) (*plans.ActionInvocationInstanceSrc, error) {
func ActionInvocationFromTfplan(rawAction *planproto.ActionInvocationInstance) (*plans.ActionInvocationInstanceSrc, error) {
if rawAction == nil {
// Should never happen in practice, since protobuf can't represent
// a nil value in a list.
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
13 changes: 13 additions & 0 deletions internal/rpcapi/dependencies_provider_schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -141,18 +141,23 @@ func providerSchemaToProto(schemaResp providers.GetProviderSchemaResponse) *depe

mrtSchemas := make(map[string]*dependencies.Schema, len(schemaResp.ResourceTypes))
drtSchemas := make(map[string]*dependencies.Schema, len(schemaResp.DataSources))
actionSchemas := make(map[string]*dependencies.ActionSchema, len(schemaResp.Actions))

for name, elem := range schemaResp.ResourceTypes {
mrtSchemas[name] = schemaElementToProto(elem)
}
for name, elem := range schemaResp.DataSources {
drtSchemas[name] = schemaElementToProto(elem)
}
for name, elem := range schemaResp.Actions {
actionSchemas[name] = actionElementToProto(elem)
}

return &dependencies.ProviderSchema{
ProviderConfig: schemaElementToProto(schemaResp.Provider),
ManagedResourceTypes: mrtSchemas,
DataResourceTypes: drtSchemas,
ActionTypes: actionSchemas,
}
}

Expand All @@ -162,6 +167,14 @@ func schemaElementToProto(elem providers.Schema) *dependencies.Schema {
}
}

func actionElementToProto(elem providers.ActionSchema) *dependencies.ActionSchema {
return &dependencies.ActionSchema{
Schema: &dependencies.Schema{
Block: schemaBlockToProto(elem.ConfigSchema),
},
}
}

func schemaBlockToProto(block *configschema.Block) *dependencies.Schema_Block {
if block == nil {
return &dependencies.Schema_Block{}
Expand Down
109 changes: 108 additions & 1 deletion internal/rpcapi/stacks.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"context"
"fmt"
"io"
"log"
"time"

"github.com/hashicorp/go-slug/sourceaddrs"
Expand Down Expand Up @@ -917,7 +918,6 @@ func (s *stacksServer) CloseTerraformState(ctx context.Context, request *stacks.
}

func (s *stacksServer) MigrateTerraformState(request *stacks.MigrateTerraformState_Request, server stacks.Stacks_MigrateTerraformStateServer) error {

previousStateHandle := handle[*states.State](request.StateHandle)
previousState := s.handles.TerraformState(previousStateHandle)
if previousState == nil {
Expand Down Expand Up @@ -1196,6 +1196,81 @@ func stackChangeHooks(send func(*stacks.StackChangeProgress) error, mainStackSou
return span
},

ReportActionInvocationPlanned: func(ctx context.Context, span any, ai *hooks.ActionInvocation) any {
span.(trace.Span).AddEvent("planned action invocation", trace.WithAttributes(
attribute.String("component_instance", ai.Addr.Component.String()),
attribute.String("resource_instance", ai.Addr.Item.String()),
))

inv, err := actionInvocationPlanned(ai)
if err != nil {
return span
}

send(&stacks.StackChangeProgress{
Event: &stacks.StackChangeProgress_ActionInvocationPlanned_{
ActionInvocationPlanned: inv,
},
})

return span
},

ReportActionInvocationStatus: func(ctx context.Context, span any, status *hooks.ActionInvocationStatusHookData) any {
log.Printf("[DEBUG] ReportActionInvocationStatus called: Action=%s, Status=%s, Provider=%s",
status.Addr.Item.String(), status.Status.String(), status.ProviderAddr.String())

span.(trace.Span).AddEvent("action invocation status", trace.WithAttributes(
attribute.String("component_instance", status.Addr.Component.String()),
attribute.String("action_instance", status.Addr.Item.String()),
attribute.String("status", status.Status.String()),
))

protoStatus := status.Status.ForProtobuf()
log.Printf("[DEBUG] Sending ActionInvocationStatus to gRPC client: Addr=%s, Status=%d (proto)",
status.Addr.String(), protoStatus)

send(&stacks.StackChangeProgress{
Event: &stacks.StackChangeProgress_ActionInvocationStatus_{
ActionInvocationStatus: &stacks.StackChangeProgress_ActionInvocationStatus{
Addr: stacks.NewActionInvocationInStackAddr(status.Addr),
Status: protoStatus,
ProviderAddr: status.ProviderAddr.String(),
},
},
})

log.Printf("[DEBUG] ActionInvocationStatus event successfully sent to client")
return span
},

ReportActionInvocationProgress: func(ctx context.Context, span any, progress *hooks.ActionInvocationProgressHookData) any {
log.Printf("[DEBUG] ReportActionInvocationProgress called: Action=%s, Message=%s, Provider=%s",
progress.Addr.Item.String(), progress.Message, progress.ProviderAddr.String())

span.(trace.Span).AddEvent("action invocation progress", trace.WithAttributes(
attribute.String("component_instance", progress.Addr.Component.String()),
attribute.String("action_instance", progress.Addr.Item.String()),
attribute.String("message", progress.Message),
))

log.Printf("[DEBUG] Sending ActionInvocationProgress to gRPC client: Addr=%s, Message=%s",
progress.Addr.String(), progress.Message)

send(&stacks.StackChangeProgress{
Event: &stacks.StackChangeProgress_ActionInvocationProgress_{
ActionInvocationProgress: &stacks.StackChangeProgress_ActionInvocationProgress{
Addr: stacks.NewActionInvocationInStackAddr(progress.Addr),
Message: progress.Message,
ProviderAddr: progress.ProviderAddr.String(),
},
},
})

log.Printf("[DEBUG] ActionInvocationProgress event successfully sent to client")
return span
},

ReportResourceInstanceDeferred: func(ctx context.Context, span any, change *hooks.DeferredResourceInstanceChange) any {
span.(trace.Span).AddEvent("deferred resource instance", trace.WithAttributes(
attribute.String("component_instance", change.Change.Addr.Component.String()),
Expand Down Expand Up @@ -1306,6 +1381,38 @@ func resourceInstancePlanned(ric *hooks.ResourceInstanceChange) (*stacks.StackCh
}, nil
}

func actionInvocationPlanned(ai *hooks.ActionInvocation) (*stacks.StackChangeProgress_ActionInvocationPlanned, error) {
res := &stacks.StackChangeProgress_ActionInvocationPlanned{
Addr: stacks.NewActionInvocationInStackAddr(ai.Addr),
ProviderAddr: ai.ProviderAddr.String(),
}

switch trig := ai.Trigger.(type) {
case *plans.LifecycleActionTrigger:
res.ActionTrigger = &stacks.StackChangeProgress_ActionInvocationPlanned_LifecycleActionTrigger{
LifecycleActionTrigger: &stacks.StackChangeProgress_LifecycleActionTrigger{
TriggeringResourceAddress: stacks.NewResourceInstanceInStackAddr(
stackaddrs.AbsResourceInstance{
Component: ai.Addr.Component,
Item: trig.TriggeringResourceAddr,
},
),
TriggerEvent: stacks.StackChangeProgress_ActionTriggerEvent(trig.TriggerEvent()),
ActionTriggerBlockIndex: int64(trig.ActionTriggerBlockIndex),
ActionsListIndex: int64(trig.ActionsListIndex),
},
}
case *plans.InvokeActionTrigger:
res.ActionTrigger = &stacks.StackChangeProgress_ActionInvocationPlanned_InvokeActionTrigger{
InvokeActionTrigger: &stacks.StackChangeProgress_InvokeActionTrigger{},
}
default:
return nil, fmt.Errorf("unsupported action invocation trigger type")
}

return res, nil
}

func evtComponentInstanceStatus(ci stackaddrs.AbsComponentInstance, status hooks.ComponentInstanceStatus) *stacks.StackChangeProgress {
return &stacks.StackChangeProgress{
Event: &stacks.StackChangeProgress_ComponentInstanceStatus_{
Expand Down
Loading
Loading