core: Don't panic if EvalMaybeResourceDeposedObject has no DeposedKey
This is a "should never happen" case, but we have reports of it actually happening. In order to try to collect a bit more data about what's going on here, we're changing what was previously a hard panic into a normal error message that can include the address of the instance we were working on and the action we were trying to do to it at the time. The hope is to narrow down what situations can trigger this in order to find a reliable reproduction case in order to debug further. This also means that for those who _do_ encounter this problem in the meantime Terraform will have a chance to shut down cleanly and therefore be more likely to be able to recover on a subsequent plan/apply cycle. Further investigation of this will follow once we see a report or two of this updated error message.
This commit is contained in:
parent
413e423bba
commit
7f8e087ce3
|
@ -5010,7 +5010,7 @@ func TestContext2Apply_error_createBeforeDestroy(t *testing.T) {
|
||||||
State: state,
|
State: state,
|
||||||
})
|
})
|
||||||
p.ApplyFn = func(info *InstanceInfo, is *InstanceState, id *InstanceDiff) (*InstanceState, error) {
|
p.ApplyFn = func(info *InstanceInfo, is *InstanceState, id *InstanceDiff) (*InstanceState, error) {
|
||||||
return nil, fmt.Errorf("error")
|
return nil, fmt.Errorf("placeholder error from ApplyFn")
|
||||||
}
|
}
|
||||||
p.DiffFn = testDiffFn
|
p.DiffFn = testDiffFn
|
||||||
|
|
||||||
|
@ -5022,6 +5022,11 @@ func TestContext2Apply_error_createBeforeDestroy(t *testing.T) {
|
||||||
if diags == nil {
|
if diags == nil {
|
||||||
t.Fatal("should have error")
|
t.Fatal("should have error")
|
||||||
}
|
}
|
||||||
|
if got, want := diags.Err().Error(), "placeholder error from ApplyFn"; got != want {
|
||||||
|
// We're looking for our artificial error from ApplyFn above, whose
|
||||||
|
// message is literally "placeholder error from ApplyFn".
|
||||||
|
t.Fatalf("wrong error\ngot: %s\nwant: %s", got, want)
|
||||||
|
}
|
||||||
|
|
||||||
actual := strings.TrimSpace(state.String())
|
actual := strings.TrimSpace(state.String())
|
||||||
expected := strings.TrimSpace(testTerraformApplyErrorCreateBeforeDestroyStr)
|
expected := strings.TrimSpace(testTerraformApplyErrorCreateBeforeDestroyStr)
|
||||||
|
@ -11118,6 +11123,10 @@ func TestContext2Apply_taintedDestroyFailure(t *testing.T) {
|
||||||
Name: "c",
|
Name: "c",
|
||||||
}.Instance(addrs.NoKey))
|
}.Instance(addrs.NoKey))
|
||||||
|
|
||||||
|
if c.Current == nil {
|
||||||
|
t.Fatal("test_instance.c has no current instance, but it should")
|
||||||
|
}
|
||||||
|
|
||||||
if c.Current.Status != states.ObjectTainted {
|
if c.Current.Status != states.ObjectTainted {
|
||||||
t.Fatal("test_instance.c should be tainted")
|
t.Fatal("test_instance.c should be tainted")
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@ import (
|
||||||
|
|
||||||
"github.com/hashicorp/terraform/addrs"
|
"github.com/hashicorp/terraform/addrs"
|
||||||
"github.com/hashicorp/terraform/configs"
|
"github.com/hashicorp/terraform/configs"
|
||||||
|
"github.com/hashicorp/terraform/plans"
|
||||||
"github.com/hashicorp/terraform/providers"
|
"github.com/hashicorp/terraform/providers"
|
||||||
"github.com/hashicorp/terraform/states"
|
"github.com/hashicorp/terraform/states"
|
||||||
"github.com/hashicorp/terraform/tfdiags"
|
"github.com/hashicorp/terraform/tfdiags"
|
||||||
|
@ -388,6 +389,12 @@ func (n *EvalDeposeState) Eval(ctx EvalContext) (interface{}, error) {
|
||||||
type EvalMaybeRestoreDeposedObject struct {
|
type EvalMaybeRestoreDeposedObject struct {
|
||||||
Addr addrs.ResourceInstance
|
Addr addrs.ResourceInstance
|
||||||
|
|
||||||
|
// PlannedChange might be the action we're performing that includes
|
||||||
|
// the possiblity of restoring a deposed object. However, it might also
|
||||||
|
// be nil. It's here only for use in error messages and must not be
|
||||||
|
// used for business logic.
|
||||||
|
PlannedChange **plans.ResourceInstanceChange
|
||||||
|
|
||||||
// Key is a pointer to the deposed object key that should be forgotten
|
// Key is a pointer to the deposed object key that should be forgotten
|
||||||
// from the state, which must be non-nil.
|
// from the state, which must be non-nil.
|
||||||
Key *states.DeposedKey
|
Key *states.DeposedKey
|
||||||
|
@ -399,6 +406,33 @@ func (n *EvalMaybeRestoreDeposedObject) Eval(ctx EvalContext) (interface{}, erro
|
||||||
dk := *n.Key
|
dk := *n.Key
|
||||||
state := ctx.State()
|
state := ctx.State()
|
||||||
|
|
||||||
|
if dk == states.NotDeposed {
|
||||||
|
// This should never happen, and so it always indicates a bug.
|
||||||
|
// We should evaluate this node only if we've previously deposed
|
||||||
|
// an object as part of the same operation.
|
||||||
|
var diags tfdiags.Diagnostics
|
||||||
|
if n.PlannedChange != nil && *n.PlannedChange != nil {
|
||||||
|
diags = diags.Append(tfdiags.Sourceless(
|
||||||
|
tfdiags.Error,
|
||||||
|
"Attempt to restore non-existent deposed object",
|
||||||
|
fmt.Sprintf(
|
||||||
|
"Terraform has encountered a bug where it would need to restore a deposed object for %s without knowing a deposed object key for that object. This occurred during a %s action. This is a bug in Terraform; please report it!",
|
||||||
|
absAddr, (*n.PlannedChange).Action,
|
||||||
|
),
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
diags = diags.Append(tfdiags.Sourceless(
|
||||||
|
tfdiags.Error,
|
||||||
|
"Attempt to restore non-existent deposed object",
|
||||||
|
fmt.Sprintf(
|
||||||
|
"Terraform has encountered a bug where it would need to restore a deposed object for %s without knowing a deposed object key for that object. This is a bug in Terraform; please report it!",
|
||||||
|
absAddr,
|
||||||
|
),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
return nil, diags.Err()
|
||||||
|
}
|
||||||
|
|
||||||
restored := state.MaybeRestoreResourceInstanceDeposed(absAddr, dk)
|
restored := state.MaybeRestoreResourceInstanceDeposed(absAddr, dk)
|
||||||
if restored {
|
if restored {
|
||||||
log.Printf("[TRACE] EvalMaybeRestoreDeposedObject: %s deposed object %s was restored as the current object", absAddr, dk)
|
log.Printf("[TRACE] EvalMaybeRestoreDeposedObject: %s deposed object %s was restored as the current object", absAddr, dk)
|
||||||
|
|
|
@ -394,8 +394,9 @@ func (n *NodeApplyableResourceInstance) evalTreeManagedResource(addr addrs.AbsRe
|
||||||
return createBeforeDestroyEnabled && err != nil, nil
|
return createBeforeDestroyEnabled && err != nil, nil
|
||||||
},
|
},
|
||||||
Then: &EvalMaybeRestoreDeposedObject{
|
Then: &EvalMaybeRestoreDeposedObject{
|
||||||
Addr: addr.Resource,
|
Addr: addr.Resource,
|
||||||
Key: &deposedKey,
|
PlannedChange: &diffApply,
|
||||||
|
Key: &deposedKey,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue