deferring a dependency resource should defer action invocation

pull/37547/head
Daniel Schmidt 8 months ago
parent 29b2ab15e2
commit b6a9990354

@ -768,6 +768,12 @@ func (d *Deferred) ShouldDeferActionInvocation(ai plans.ActionInvocationInstance
return false
}
// ShouldDeferAction returns true if the action should be deferred. This is the case if a
// dependency of the action is deferred.
func (d *Deferred) ShouldDeferAction(deps []addrs.ConfigResource) bool {
return d.DependenciesDeferred(deps)
}
// UnexpectedProviderDeferralDiagnostic is a diagnostic that indicates that a
// provider was deferred although deferrals were not allowed.
func UnexpectedProviderDeferralDiagnostic(addrs fmt.Stringer) tfdiags.Diagnostic {

@ -2781,7 +2781,6 @@ resource "test_object" "a" {
}
},
},
"splat is not supported": {
module: map[string]string{
"main.tf": `
@ -2792,7 +2791,7 @@ resource "test_object" "a" {
lifecycle {
action_trigger {
events = [before_create]
actions = [action.test_unlinked.hello[*]]
actions = [action.test_unlinked.hello[*]]
}
}
}
@ -2812,6 +2811,61 @@ resource "test_object" "a" {
})
},
},
"deferring resource dependencies should defer action": {
module: map[string]string{
"main.tf": `
resource "test_object" "origin" {
name = "origin"
}
action "test_unlinked" "hello" {
config {
attr = test_object.origin.name
}
}
resource "test_object" "a" {
name = "a"
lifecycle {
action_trigger {
events = [after_create]
actions = [action.test_unlinked.hello]
}
}
}
`,
},
expectPlanActionCalled: false,
planOpts: &PlanOpts{
Mode: plans.NormalMode,
DeferralAllowed: true,
},
planResourceFn: func(t *testing.T, req providers.PlanResourceChangeRequest) providers.PlanResourceChangeResponse {
if req.Config.GetAttr("name").AsString() == "origin" {
return providers.PlanResourceChangeResponse{
Deferred: &providers.Deferred{
Reason: providers.DeferredReasonAbsentPrereq,
},
}
}
return providers.PlanResourceChangeResponse{
PlannedState: req.ProposedNewState,
PlannedPrivate: req.PriorPrivate,
PlannedIdentity: req.PriorIdentity,
}
},
assertPlan: func(t *testing.T, p *plans.Plan) {
if len(p.DeferredActionInvocations) != 1 {
t.Errorf("Expected 1 deferred action invocation, got %d", len(p.DeferredActionInvocations))
}
if p.DeferredActionInvocations[0].ActionInvocationInstanceSrc.Addr.String() != "action.test_unlinked.hello" {
t.Errorf("Expected action.test_unlinked.hello, got %s", p.DeferredActionInvocations[0].ActionInvocationInstanceSrc.Addr.String())
}
if p.DeferredActionInvocations[0].DeferredReason != providers.DeferredReasonDeferredPrereq {
t.Errorf("Expected DeferredReasonDeferredPrereq, got %s", p.DeferredActionInvocations[0].DeferredReason)
}
},
},
} {
t.Run(name, func(t *testing.T) {
if tc.toBeImplemented {

@ -80,6 +80,7 @@ func (n *nodeExpandActionDeclaration) DynamicExpand(ctx EvalContext) (*Graph, tf
Config: &n.Config,
Schema: n.Schema,
ResolvedProvider: n.ResolvedProvider,
Dependencies: n.Dependencies,
}
g.Add(&node)
} else {
@ -90,6 +91,7 @@ func (n *nodeExpandActionDeclaration) DynamicExpand(ctx EvalContext) (*Graph, tf
Config: &n.Config,
Schema: n.Schema,
ResolvedProvider: n.ResolvedProvider,
Dependencies: n.Dependencies,
}
g.Add(&node)

@ -24,6 +24,7 @@ type NodeAbstractAction struct {
// The address of the provider this action will use
ResolvedProvider addrs.AbsProviderConfig
Schema *providers.ActionSchema
Dependencies []addrs.ConfigResource
}
var (
@ -32,6 +33,7 @@ var (
_ GraphNodeConfigAction = (*NodeAbstractAction)(nil)
_ GraphNodeAttachActionSchema = (*NodeAbstractAction)(nil)
_ GraphNodeProviderConsumer = (*NodeAbstractAction)(nil)
_ GraphNodeAttachDependencies = (*NodeAbstractAction)(nil)
)
func (n NodeAbstractAction) Name() string {
@ -110,3 +112,7 @@ func (n *NodeAbstractAction) Provider() addrs.Provider {
func (n *NodeAbstractAction) SetProvider(p addrs.AbsProviderConfig) {
n.ResolvedProvider = p
}
func (n *NodeAbstractAction) AttachDependencies(deps []addrs.ConfigResource) {
n.Dependencies = deps
}

@ -9,7 +9,6 @@ import (
"github.com/hashicorp/hcl/v2"
"github.com/hashicorp/terraform/internal/addrs"
"github.com/hashicorp/terraform/internal/configs"
"github.com/hashicorp/terraform/internal/dag"
"github.com/hashicorp/terraform/internal/lang/langrefs"
"github.com/hashicorp/terraform/internal/providers"
"github.com/hashicorp/terraform/internal/tfdiags"
@ -25,6 +24,7 @@ type NodeActionDeclarationInstance struct {
Config *configs.Action
Schema *providers.ActionSchema
ResolvedProvider addrs.AbsProviderConfig
Dependencies []addrs.ConfigResource
}
var (
@ -32,29 +32,23 @@ var (
_ GraphNodeExecutable = (*NodeActionDeclarationInstance)(nil)
_ GraphNodeReferencer = (*NodeActionDeclarationInstance)(nil)
_ GraphNodeReferenceable = (*NodeActionDeclarationInstance)(nil)
_ dag.GraphNodeDotter = (*NodeActionDeclarationInstance)(nil)
)
func (n *NodeActionDeclarationInstance) Name() string {
return n.Addr.String()
}
func (n *NodeActionDeclarationInstance) DotNode(string, *dag.DotOpts) *dag.DotNode {
return &dag.DotNode{
Name: n.Name(),
}
}
func (n *NodeActionDeclarationInstance) Path() addrs.ModuleInstance {
return n.Addr.Module
}
func (n *NodeActionDeclarationInstance) Execute(ctx EvalContext, _ walkOperation) tfdiags.Diagnostics {
var diags tfdiags.Diagnostics
deferrals := ctx.Deferrals()
if n.Addr.Action.Key == addrs.WildcardKey {
if ctx.Deferrals().DeferralAllowed() {
ctx.Deferrals().ReportActionDeferred(n.Addr, providers.DeferredReasonInstanceCountUnknown)
if deferrals.DeferralAllowed() {
deferrals.ReportActionDeferred(n.Addr, providers.DeferredReasonInstanceCountUnknown)
} else {
diags = diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagError,
@ -66,6 +60,11 @@ func (n *NodeActionDeclarationInstance) Execute(ctx EvalContext, _ walkOperation
return diags
}
if deferrals.DeferralAllowed() && deferrals.ShouldDeferAction(n.Dependencies) {
deferrals.ReportActionDeferred(n.Addr, providers.DeferredReasonDeferredPrereq)
return diags
}
// This should have been caught already
if n.Schema == nil {
panic("NodeActionDeclarationInstance.Execute called without a schema")

Loading…
Cancel
Save