Skip to content

Commit

Permalink
stacks: handle deferred actions for refresh
Browse files Browse the repository at this point in the history
when we refresh as part of another operation we need to provide a clean state in terms of deferrals for the main operation
  • Loading branch information
DanielMSchmidt committed Apr 3, 2024
1 parent c0a2d76 commit 17920b2
Show file tree
Hide file tree
Showing 5 changed files with 55 additions and 25 deletions.
12 changes: 12 additions & 0 deletions internal/plans/deferring/deferred.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,18 @@ func NewDeferred(resourceGraph addrs.DirectedGraph[addrs.ConfigResource]) *Defer
}
}

// Clear clears all deferred changes, allowing the deferred state to be
// reset to its initial state.
func (d *Deferred) Clear() {
d.mu.Lock()
defer d.mu.Unlock()

d.externalDependencyDeferred = false
d.resourceInstancesDeferred = addrs.MakeMap[addrs.ConfigResource, addrs.Map[addrs.AbsResourceInstance, deferredResourceInstance]]()
d.partialExpandedResourcesDeferred = addrs.MakeMap[addrs.ConfigResource, addrs.Map[addrs.PartialExpandedResource, deferredPartialExpandedResource]]()
d.partialExpandedModulesDeferred = addrs.MakeSet[addrs.PartialExpandedModule]()
}

// SetExternalDependencyDeferred modifies a freshly-constructed [Deferred]
// so that it will consider all resource instances as needing their actions
// deferred, even if there's no other reason to do that.
Expand Down
46 changes: 30 additions & 16 deletions internal/terraform/context_apply_deferred_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -424,23 +424,37 @@ output "a" {
"upstream_names": cty.NullVal(cty.Set(cty.String)),
}),
},
complete: false,
complete: true,
},
{
inputs: map[string]cty.Value{},
wantPlanned: map[string]cty.Value{
"a": cty.ObjectVal(map[string]cty.Value{
"name": cty.StringVal("a"),
"upstream_names": cty.NullVal(cty.Set(cty.String)),
"defer_read": cty.True,
}),
},

wantActions: map[string]plans.Action{
`test.a`: plans.Update,
},
wantApplied: map[string]cty.Value{
"a": cty.ObjectVal(map[string]cty.Value{
"name": cty.StringVal("a"),
"upstream_names": cty.NullVal(cty.Set(cty.String)),
"defer_read": cty.True,
}),
},
wantOutputs: map[string]cty.Value{
"a": cty.ObjectVal(map[string]cty.Value{
"name": cty.StringVal("a"),
"upstream_names": cty.NullVal(cty.Set(cty.String)),
"defer_read": cty.True,
}),
},
complete: true,
},
// TODO: This test currently panics with
// ProposedNew only supports object-typed values
// during the plan stage. This will be fixed in TF-13953.
//
// {
// inputs: map[string]cty.Value{},
// wantPlanned: map[string]cty.Value{
// "a": cty.UnknownAsNull(cty.DynamicVal),
// },

// wantActions: map[string]plans.Action{},
// wantApplied: map[string]cty.Value{},
// wantOutputs: map[string]cty.Value{},
// complete: false,
// },
},
}
)
Expand Down
8 changes: 1 addition & 7 deletions internal/terraform/node_resource_abstract_instance.go
Original file line number Diff line number Diff line change
Expand Up @@ -653,13 +653,7 @@ func (n *NodeAbstractResourceInstance) refresh(ctx EvalContext, deposedKey state

if resp.Deferred != nil {
deferrals := ctx.Deferrals()
expectedValue := cty.UnknownVal(cty.DynamicPseudoType)
deferrals.ReportResourceInstanceDeferred(absAddr, plans.Read, expectedValue)

// TODO: Add this to the plan (TF-13953)
return &states.ResourceInstanceObject{
Value: expectedValue,
}, diags
deferrals.ReportResourceInstanceDeferred(absAddr, plans.Read, resp.NewState)
}
}
if n.Config != nil {
Expand Down
9 changes: 8 additions & 1 deletion internal/terraform/node_resource_plan_instance.go
Original file line number Diff line number Diff line change
Expand Up @@ -211,7 +211,14 @@ func (n *NodePlannableResourceInstance) managedResourceExecute(ctx EvalContext)
return diags
}

instanceRefreshState = s
if !ctx.Deferrals().HaveAnyDeferrals() {
// If we don't have any deferrals, we can just use the state we got
instanceRefreshState = s
} else {
// If we have deferrals we will continue the operation without the
// refresh. We also need to clear the deferrals to not confuse upcoming operations.
ctx.Deferrals().Clear()
}

if instanceRefreshState != nil {
// When refreshing we start by merging the stored dependencies and
Expand Down
5 changes: 4 additions & 1 deletion internal/terraform/node_resource_plan_orphan.go
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,10 @@ func (n *NodePlannableResourceInstanceOrphan) managedResourceExecute(ctx EvalCon

// If we refreshed then our subsequent planning should be in terms of
// the new object, not the original object.
oldState = refreshedState
// If the refresh has deferrals, we need to keep the old state
if ctx.Deferrals().HaveAnyDeferrals() {
oldState = refreshedState
}
}

// If we're skipping planning, all we need to do is write the state. If the
Expand Down

0 comments on commit 17920b2

Please sign in to comment.