Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions backend/local/backend_plan.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}
}

Expand Down
115 changes: 115 additions & 0 deletions backend/local/backend_plan_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down