core: If we refresh during orphan processing, use the result for planning
If we don't do this then we can create a situation where refresh detects that an object already doesn't exist but we plan to destroy it anyway, rather than returning "no changes" as expected.
This commit is contained in:
parent
ecd030eb26
commit
8d4d333efe
|
@ -350,7 +350,22 @@ func (n *NodeAbstractResourceInstance) planDestroy(ctx EvalContext, currentState
|
|||
// If there is no state or our attributes object is null then we're already
|
||||
// destroyed.
|
||||
if currentState == nil || currentState.Value.IsNull() {
|
||||
return nil, nil
|
||||
// We still need to generate a NoOp change, because that allows
|
||||
// outside consumers of the plan to distinguish between us affirming
|
||||
// that we checked something and concluded no changes were needed
|
||||
// vs. that something being entirely excluded e.g. due to -target.
|
||||
noop := &plans.ResourceInstanceChange{
|
||||
Addr: absAddr,
|
||||
DeposedKey: deposedKey,
|
||||
Change: plans.Change{
|
||||
Action: plans.NoOp,
|
||||
Before: cty.NullVal(cty.DynamicPseudoType),
|
||||
After: cty.NullVal(cty.DynamicPseudoType),
|
||||
},
|
||||
Private: currentState.Private,
|
||||
ProviderAddr: n.ResolvedProvider,
|
||||
}
|
||||
return noop, nil
|
||||
}
|
||||
|
||||
// Call pre-diff hook
|
||||
|
|
|
@ -114,6 +114,10 @@ func (n *NodePlannableResourceInstanceOrphan) managedResourceExecute(ctx EvalCon
|
|||
if diags.HasErrors() {
|
||||
return diags
|
||||
}
|
||||
|
||||
// If we refreshed then our subsequent planning should be in terms of
|
||||
// the new object, not the original object.
|
||||
oldState = refreshedState
|
||||
}
|
||||
|
||||
if !n.skipPlanChanges {
|
||||
|
|
|
@ -7,7 +7,9 @@ import (
|
|||
"github.com/hashicorp/terraform/configs/configschema"
|
||||
"github.com/hashicorp/terraform/instances"
|
||||
"github.com/hashicorp/terraform/plans"
|
||||
"github.com/hashicorp/terraform/providers"
|
||||
"github.com/hashicorp/terraform/states"
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
)
|
||||
|
||||
func TestNodeResourcePlanOrphanExecute(t *testing.T) {
|
||||
|
@ -64,3 +66,81 @@ func TestNodeResourcePlanOrphanExecute(t *testing.T) {
|
|||
t.Fatalf("expected empty state, got %s", state.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestNodeResourcePlanOrphanExecute_alreadyDeleted(t *testing.T) {
|
||||
addr := addrs.Resource{
|
||||
Mode: addrs.ManagedResourceMode,
|
||||
Type: "test_object",
|
||||
Name: "foo",
|
||||
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance)
|
||||
|
||||
state := states.NewState()
|
||||
state.Module(addrs.RootModuleInstance).SetResourceInstanceCurrent(
|
||||
addr.Resource,
|
||||
&states.ResourceInstanceObjectSrc{
|
||||
AttrsFlat: map[string]string{
|
||||
"test_string": "foo",
|
||||
},
|
||||
Status: states.ObjectReady,
|
||||
},
|
||||
addrs.AbsProviderConfig{
|
||||
Provider: addrs.NewDefaultProvider("test"),
|
||||
Module: addrs.RootModule,
|
||||
},
|
||||
)
|
||||
refreshState := state.DeepCopy()
|
||||
prevRunState := state.DeepCopy()
|
||||
changes := plans.NewChanges()
|
||||
|
||||
p := simpleMockProvider()
|
||||
p.ReadResourceResponse = &providers.ReadResourceResponse{
|
||||
NewState: cty.NullVal(p.GetProviderSchemaResponse.ResourceTypes["test_string"].Block.ImpliedType()),
|
||||
}
|
||||
ctx := &MockEvalContext{
|
||||
StateState: state.SyncWrapper(),
|
||||
RefreshStateState: refreshState.SyncWrapper(),
|
||||
PrevRunStateState: prevRunState.SyncWrapper(),
|
||||
InstanceExpanderExpander: instances.NewExpander(),
|
||||
ProviderProvider: p,
|
||||
ProviderSchemaSchema: &ProviderSchema{
|
||||
ResourceTypes: map[string]*configschema.Block{
|
||||
"test_object": simpleTestSchema(),
|
||||
},
|
||||
},
|
||||
ChangesChanges: changes.SyncWrapper(),
|
||||
}
|
||||
|
||||
node := NodePlannableResourceInstanceOrphan{
|
||||
NodeAbstractResourceInstance: &NodeAbstractResourceInstance{
|
||||
NodeAbstractResource: NodeAbstractResource{
|
||||
ResolvedProvider: addrs.AbsProviderConfig{
|
||||
Provider: addrs.NewDefaultProvider("test"),
|
||||
Module: addrs.RootModule,
|
||||
},
|
||||
},
|
||||
Addr: mustResourceInstanceAddr("test_object.foo"),
|
||||
},
|
||||
}
|
||||
diags := node.Execute(ctx, walkPlan)
|
||||
if diags.HasErrors() {
|
||||
t.Fatalf("unexpected error: %s", diags.Err())
|
||||
}
|
||||
if !state.Empty() {
|
||||
t.Fatalf("expected empty state, got %s", state.String())
|
||||
}
|
||||
|
||||
if got := prevRunState.ResourceInstance(addr); got == nil {
|
||||
t.Errorf("no entry for %s in the prev run state; should still be present", addr)
|
||||
}
|
||||
if got := refreshState.ResourceInstance(addr); got != nil {
|
||||
t.Errorf("refresh state has entry for %s; should've been removed", addr)
|
||||
}
|
||||
if got := changes.ResourceInstance(addr); got == nil {
|
||||
t.Errorf("no entry for %s in the planned changes; should have a NoOp change", addr)
|
||||
} else {
|
||||
if got, want := got.Action, plans.NoOp; got != want {
|
||||
t.Errorf("planned change for %s has wrong action\ngot: %s\nwant: %s", addr, got, want)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue