Restore apply behavior

pull/38301/head
Roniece 3 weeks ago committed by Roniece Ricardo
parent a42d5b9f62
commit dc4e328e62

@ -52,6 +52,14 @@ type Component struct {
// that have changes that are deferred to a later plan and apply cycle.
DeferredResourceInstanceChanges addrs.Map[addrs.AbsResourceInstanceObject, *plans.DeferredResourceInstanceChangeSrc]
// ActionInvocations describes planned action invocations that should be
// preserved into the modules runtime apply plan.
ActionInvocations []*plans.ActionInvocationInstanceSrc
// DeferredActionInvocations describes action invocations that were deferred
// to a later plan and apply cycle.
DeferredActionInvocations []*plans.DeferredActionInvocationSrc
// PlanTimestamp is the time Terraform Core recorded as the single "plan
// timestamp", which is used only for the result of the "plantimestamp"
// function during apply and must not be used for any other purpose.
@ -114,6 +122,18 @@ func (c *Component) ForModulesRuntime() (*plans.Plan, error) {
}
}
for _, action := range c.ActionInvocations {
if action != nil {
changes.ActionInvocations = append(changes.ActionInvocations, action)
}
}
for _, deferredAction := range c.DeferredActionInvocations {
if deferredAction != nil {
plan.DeferredActionInvocations = append(plan.DeferredActionInvocations, deferredAction)
}
}
priorState := states.NewState()
ss := priorState.SyncWrapper()
for _, elem := range c.ResourceInstancePriorState.Elems {
@ -163,5 +183,23 @@ func (c *Component) RequiredProviderInstances() addrs.Set[addrs.RootProviderConf
Alias: elem.Value.Alias,
})
}
for _, action := range c.ActionInvocations {
if action == nil {
continue
}
providerInstances.Add(addrs.RootProviderConfig{
Provider: action.ProviderAddr.Provider,
Alias: action.ProviderAddr.Alias,
})
}
for _, deferredAction := range c.DeferredActionInvocations {
if deferredAction == nil || deferredAction.ActionInvocationInstanceSrc == nil {
continue
}
providerInstances.Add(addrs.RootProviderConfig{
Provider: deferredAction.ActionInvocationInstanceSrc.ProviderAddr.Provider,
Alias: deferredAction.ActionInvocationInstanceSrc.ProviderAddr.Alias,
})
}
return providerInstances
}

@ -237,6 +237,8 @@ func (l *Loader) AddRaw(rawMsg *anypb.Any) error {
ResourceInstancePriorState: addrs.MakeMap[addrs.AbsResourceInstanceObject, *states.ResourceInstanceObjectSrc](),
ResourceInstanceProviderConfig: addrs.MakeMap[addrs.AbsResourceInstanceObject, addrs.AbsProviderConfig](),
DeferredResourceInstanceChanges: addrs.MakeMap[addrs.AbsResourceInstanceObject, *plans.DeferredResourceInstanceChangeSrc](),
ActionInvocations: make([]*plans.ActionInvocationInstanceSrc, 0),
DeferredActionInvocations: make([]*plans.DeferredActionInvocationSrc, 0),
})
err = c.PlanTimestamp.UnmarshalText([]byte(msg.PlanTimestamp))
if err != nil {
@ -302,7 +304,42 @@ func (l *Loader) AddRaw(rawMsg *anypb.Any) error {
})
case *tfstackdata1.PlanActionInvocationPlanned:
// TODO: Implemented in a future apply-related PR.
c, fullAddr, err := LoadComponentForActionInvocation(l.ret, msg)
if err != nil {
return err
}
action, err := ValidateActionInvocation(msg, fullAddr)
if err != nil {
return err
}
if action != nil {
c.ActionInvocations = append(c.ActionInvocations, action)
}
case *tfstackdata1.PlanDeferredActionInvocation:
if msg.Deferred == nil {
return fmt.Errorf("missing deferred from PlanDeferredActionInvocation")
}
if msg.Invocation == nil {
return fmt.Errorf("missing invocation from PlanDeferredActionInvocation")
}
c, fullAddr, err := LoadComponentForActionInvocation(l.ret, msg.Invocation)
if err != nil {
return err
}
action, err := ValidateActionInvocation(msg.Invocation, fullAddr)
if err != nil {
return err
}
deferredReason, _ := planfile.DeferredReasonFromProto(msg.Deferred.Reason)
c.DeferredActionInvocations = append(c.DeferredActionInvocations, &plans.DeferredActionInvocationSrc{
DeferredReason: deferredReason,
ActionInvocationInstanceSrc: action,
})
default:
// Should not get here, because a stack plan can only be loaded by
@ -472,3 +509,42 @@ func LoadComponentForPartialResourceInstance(plan *Plan, change *tfstackdata1.Pl
return c, fullAddr, providerConfigAddr, nil
}
func ValidateActionInvocation(change *tfstackdata1.PlanActionInvocationPlanned, fullAddr stackaddrs.AbsActionInvocationInstance) (*plans.ActionInvocationInstanceSrc, error) {
if change.Invocation == nil {
return nil, nil
}
action, err := planfile.ActionInvocationFromProto(change.Invocation)
if err != nil {
return nil, fmt.Errorf("invalid action invocation: %w", err)
}
if !action.Addr.Equal(fullAddr.Item) {
return nil, fmt.Errorf("planned action invocation has inconsistent address to its containing object")
}
return action, nil
}
func LoadComponentForActionInvocation(plan *Plan, change *tfstackdata1.PlanActionInvocationPlanned) (*Component, stackaddrs.AbsActionInvocationInstance, error) {
cAddr, diags := stackaddrs.ParsePartialComponentInstanceStr(change.ComponentInstanceAddr)
if diags.HasErrors() {
return nil, stackaddrs.AbsActionInvocationInstance{}, fmt.Errorf("invalid component instance address syntax in %q", change.ComponentInstanceAddr)
}
actionAddr, diags := addrs.ParseAbsActionInstanceStr(change.ActionInvocationAddr)
if diags.HasErrors() {
return nil, stackaddrs.AbsActionInvocationInstance{}, fmt.Errorf("invalid action invocation address syntax in %q", change.ActionInvocationAddr)
}
fullAddr := stackaddrs.AbsActionInvocationInstance{
Component: cAddr,
Item: actionAddr,
}
c, ok := plan.Root.GetOk(cAddr)
if !ok {
return nil, stackaddrs.AbsActionInvocationInstance{}, fmt.Errorf("action invocation change for unannounced component instance %s", cAddr)
}
return c, fullAddr, nil
}

@ -11,9 +11,14 @@ import (
"github.com/zclconf/go-cty/cty"
"google.golang.org/protobuf/types/known/anypb"
"github.com/hashicorp/terraform/internal/addrs"
"github.com/hashicorp/terraform/internal/collections"
"github.com/hashicorp/terraform/internal/configs"
"github.com/hashicorp/terraform/internal/lang/marks"
"github.com/hashicorp/terraform/internal/plans"
"github.com/hashicorp/terraform/internal/plans/planfile"
"github.com/hashicorp/terraform/internal/plans/planproto"
"github.com/hashicorp/terraform/internal/providers"
"github.com/hashicorp/terraform/internal/stacks/stackaddrs"
"github.com/hashicorp/terraform/internal/stacks/tfstackdata1"
)
@ -100,3 +105,109 @@ func TestAddRaw(t *testing.T) {
})
}
}
func TestAddRaw_ActionInvocations(t *testing.T) {
provider := addrs.MustParseProviderSourceString("example.com/test/actions")
providerConfig := addrs.AbsProviderConfig{
Module: addrs.RootModule,
Provider: provider,
}
action := &plans.ActionInvocationInstanceSrc{
Addr: addrs.RootModuleInstance.ActionInstance("webhook", "notify", addrs.NoKey),
ActionTrigger: &plans.ResourceActionTrigger{
TriggeringResourceAddr: addrs.RootModuleInstance.ResourceInstance(addrs.ManagedResourceMode, "example_resource", "main", addrs.NoKey),
ActionTriggerEvent: configs.AfterCreate,
ActionTriggerBlockIndex: 0,
ActionsListIndex: 0,
},
ProviderAddr: providerConfig,
}
rawAction, err := planfile.ActionInvocationToProto(action)
if err != nil {
t.Fatal(err)
}
loader := NewLoader()
err = loader.AddRaw(mustMarshalAnyPb(&tfstackdata1.PlanComponentInstance{
ComponentInstanceAddr: "component.web",
PlannedAction: planproto.Action_NOOP,
Mode: planproto.Mode_NORMAL,
PlanTimestamp: "2017-03-27T10:00:00-08:00",
}))
if err != nil {
t.Fatalf("adding component: %v", err)
}
err = loader.AddRaw(mustMarshalAnyPb(&tfstackdata1.PlanActionInvocationPlanned{
ComponentInstanceAddr: "component.web",
ActionInvocationAddr: action.Addr.String(),
ProviderConfigAddr: provider.String(),
Invocation: rawAction,
}))
if err != nil {
t.Fatalf("adding planned action invocation: %v", err)
}
err = loader.AddRaw(mustMarshalAnyPb(&tfstackdata1.PlanDeferredActionInvocation{
Deferred: &planproto.Deferred{
Reason: planproto.DeferredReason_DEFERRED_PREREQ,
},
Invocation: &tfstackdata1.PlanActionInvocationPlanned{
ComponentInstanceAddr: "component.web",
ActionInvocationAddr: action.Addr.String(),
ProviderConfigAddr: provider.String(),
Invocation: rawAction,
},
}))
if err != nil {
t.Fatalf("adding deferred action invocation: %v", err)
}
componentAddr, diags := stackaddrs.ParseAbsComponentInstanceStr("component.web")
if diags.HasErrors() {
t.Fatalf("parsing component address: %s", diags.Err())
}
component := loader.ret.GetComponent(componentAddr)
if component == nil {
t.Fatal("expected component to be loaded")
}
if len(component.ActionInvocations) != 1 {
t.Fatalf("expected 1 planned action invocation, got %d", len(component.ActionInvocations))
}
if diff := cmp.Diff(action, component.ActionInvocations[0], ctydebug.CmpOptions); diff != "" {
t.Fatalf("wrong planned action invocation (-want +got):\n%s", diff)
}
if len(component.DeferredActionInvocations) != 1 {
t.Fatalf("expected 1 deferred action invocation, got %d", len(component.DeferredActionInvocations))
}
if diff := cmp.Diff(&plans.DeferredActionInvocationSrc{
DeferredReason: providers.DeferredReasonDeferredPrereq,
ActionInvocationInstanceSrc: action,
}, component.DeferredActionInvocations[0], ctydebug.CmpOptions); diff != "" {
t.Fatalf("wrong deferred action invocation (-want +got):\n%s", diff)
}
modulesPlan, err := component.ForModulesRuntime()
if err != nil {
t.Fatalf("ForModulesRuntime: %v", err)
}
if len(modulesPlan.Changes.ActionInvocations) != 1 {
t.Fatalf("expected 1 planned action invocation in modules runtime plan, got %d", len(modulesPlan.Changes.ActionInvocations))
}
if diff := cmp.Diff(action, modulesPlan.Changes.ActionInvocations[0], ctydebug.CmpOptions); diff != "" {
t.Fatalf("wrong modules runtime action invocation (-want +got):\n%s", diff)
}
if len(modulesPlan.DeferredActionInvocations) != 1 {
t.Fatalf("expected 1 deferred action invocation in modules runtime plan, got %d", len(modulesPlan.DeferredActionInvocations))
}
if diff := cmp.Diff(&plans.DeferredActionInvocationSrc{
DeferredReason: providers.DeferredReasonDeferredPrereq,
ActionInvocationInstanceSrc: action,
}, modulesPlan.DeferredActionInvocations[0], ctydebug.CmpOptions); diff != "" {
t.Fatalf("wrong modules runtime deferred action invocation (-want +got):\n%s", diff)
}
requiredProviders := component.RequiredProviderInstances()
if !requiredProviders.Has(addrs.RootProviderConfig{Provider: provider}) {
t.Fatalf("expected action provider %s to be required", provider)
}
}

Loading…
Cancel
Save