backend/local: don't panic when an instance has only a deposed object
This unusual situation isn't supposed to arise in normal use, but it can come up in practice in some edge-case scenarios where Terraform fails in a severe way during a create_before_destroy. Some earlier versions of Terraform also had bugs in their handling of deposed objects, so this may also arise if upgrading from one of those older versions with some leftover deposed objects in the state.
This commit is contained in:
parent
2d194828a4
commit
b1213f7f6c
|
@ -260,9 +260,10 @@ func (b *Local) renderPlan(plan *plans.Plan, state *states.State, schemas *terra
|
|||
// check if the change is due to a tainted resource
|
||||
tainted := false
|
||||
if !state.Empty() {
|
||||
rs := state.ResourceInstance(rcs.Addr)
|
||||
if rs != nil {
|
||||
tainted = rs.Current.Status == states.ObjectTainted
|
||||
if is := state.ResourceInstance(rcs.Addr); is != nil {
|
||||
if obj := is.GetGeneration(rcs.DeposedKey.Generation()); obj != nil {
|
||||
tainted = obj.Status == states.ObjectTainted
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -193,6 +193,121 @@ Plan: 1 to add, 0 to change, 1 to destroy.`
|
|||
}
|
||||
}
|
||||
|
||||
func TestLocal_planDeposedOnly(t *testing.T) {
|
||||
b, cleanup := TestLocal(t)
|
||||
defer cleanup()
|
||||
p := TestLocalProvider(t, b, "test", planFixtureSchema())
|
||||
testStateFile(t, b.StatePath, states.BuildState(func(ss *states.SyncState) {
|
||||
ss.SetResourceInstanceDeposed(
|
||||
addrs.Resource{
|
||||
Mode: addrs.ManagedResourceMode,
|
||||
Type: "test_instance",
|
||||
Name: "foo",
|
||||
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
|
||||
states.DeposedKey("00000000"),
|
||||
&states.ResourceInstanceObjectSrc{
|
||||
Status: states.ObjectReady,
|
||||
AttrsJSON: []byte(`{
|
||||
"ami": "bar",
|
||||
"network_interface": [{
|
||||
"device_index": 0,
|
||||
"description": "Main network interface"
|
||||
}]
|
||||
}`),
|
||||
},
|
||||
addrs.ProviderConfig{
|
||||
Type: "test",
|
||||
}.Absolute(addrs.RootModuleInstance),
|
||||
)
|
||||
}))
|
||||
b.CLI = cli.NewMockUi()
|
||||
outDir := testTempDir(t)
|
||||
defer os.RemoveAll(outDir)
|
||||
planPath := filepath.Join(outDir, "plan.tfplan")
|
||||
op, configCleanup := testOperationPlan(t, "./test-fixtures/plan")
|
||||
defer configCleanup()
|
||||
op.PlanRefresh = true
|
||||
op.PlanOutPath = planPath
|
||||
cfg := cty.ObjectVal(map[string]cty.Value{
|
||||
"path": cty.StringVal(b.StatePath),
|
||||
})
|
||||
cfgRaw, err := plans.NewDynamicValue(cfg, cfg.Type())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
op.PlanOutBackend = &plans.Backend{
|
||||
// Just a placeholder so that we can generate a valid plan file.
|
||||
Type: "local",
|
||||
Config: cfgRaw,
|
||||
}
|
||||
run, err := b.Operation(context.Background(), op)
|
||||
if err != nil {
|
||||
t.Fatalf("bad: %s", err)
|
||||
}
|
||||
<-run.Done()
|
||||
if run.Result != backend.OperationSuccess {
|
||||
t.Fatalf("plan operation failed")
|
||||
}
|
||||
if !p.ReadResourceCalled {
|
||||
t.Fatal("ReadResource should be called")
|
||||
}
|
||||
if run.PlanEmpty {
|
||||
t.Fatal("plan should not be empty")
|
||||
}
|
||||
|
||||
// The deposed object and the current object are distinct, so our
|
||||
// plan includes separate actions for each of them. This strange situation
|
||||
// is not common: it should arise only if Terraform fails during
|
||||
// a create-before-destroy when the create hasn't completed yet but
|
||||
// in a severe way that prevents the previous object from being restored
|
||||
// as "current".
|
||||
//
|
||||
// However, that situation was more common in some earlier Terraform
|
||||
// versions where deposed objects were not managed properly, so this
|
||||
// can arise when upgrading from an older version with deposed objects
|
||||
// already in the state.
|
||||
//
|
||||
// This is one of the few cases where we expose the idea of "deposed" in
|
||||
// the UI, including the user-unfriendly "deposed key" (00000000 in this
|
||||
// case) just so that users can correlate this with what they might
|
||||
// see in `terraform show` and in the subsequent apply output, because
|
||||
// it's also possible for there to be _multiple_ deposed objects, in the
|
||||
// unlikely event that create_before_destroy _keeps_ crashing across
|
||||
// subsequent runs.
|
||||
expectedOutput := `An execution plan has been generated and is shown below.
|
||||
Resource actions are indicated with the following symbols:
|
||||
+ create
|
||||
- destroy
|
||||
|
||||
Terraform will perform the following actions:
|
||||
|
||||
# test_instance.foo will be created
|
||||
+ resource "test_instance" "foo" {
|
||||
+ ami = "bar"
|
||||
|
||||
+ network_interface {
|
||||
+ description = "Main network interface"
|
||||
+ device_index = 0
|
||||
}
|
||||
}
|
||||
|
||||
# test_instance.foo (deposed object 00000000) will be destroyed
|
||||
- resource "test_instance" "foo" {
|
||||
- ami = "bar" -> null
|
||||
|
||||
- network_interface {
|
||||
- description = "Main network interface" -> null
|
||||
- device_index = 0 -> null
|
||||
}
|
||||
}
|
||||
|
||||
Plan: 1 to add, 0 to change, 1 to destroy.`
|
||||
output := b.CLI.(*cli.MockUi).OutputWriter.String()
|
||||
if !strings.Contains(output, expectedOutput) {
|
||||
t.Fatalf("Unexpected output:\n%s\n\nwant output containing:\n%s", output, expectedOutput)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLocal_planTainted_createBeforeDestroy(t *testing.T) {
|
||||
b, cleanup := TestLocal(t)
|
||||
defer cleanup()
|
||||
|
|
Loading…
Reference in New Issue