diff --git a/backend/local/backend_plan_test.go b/backend/local/backend_plan_test.go index aba80d1b74..3b90724d88 100644 --- a/backend/local/backend_plan_test.go +++ b/backend/local/backend_plan_test.go @@ -427,6 +427,7 @@ Terraform will perform the following actions: } # test_instance.foo (deposed object 00000000) will be destroyed + # (left over from a partially-failed replacement of this instance) - resource "test_instance" "foo" { - ami = "bar" -> null diff --git a/command/format/diff.go b/command/format/diff.go index 92e271d0e9..13702415d0 100644 --- a/command/format/diff.go +++ b/command/format/diff.go @@ -67,6 +67,10 @@ func ResourceChange( } case plans.Delete: buf.WriteString(color.Color(fmt.Sprintf("[bold] # %s[reset] will be [bold][red]destroyed", dispAddr))) + if change.DeposedKey != states.NotDeposed { + // Some extra context about this unusual situation. + buf.WriteString(color.Color(fmt.Sprint("\n # (left over from a partially-failed replacement of this instance)"))) + } default: // should never happen, since the above is exhaustive buf.WriteString(fmt.Sprintf("%s has an action the plan renderer doesn't support (this is a bug)", dispAddr)) diff --git a/command/format/diff_test.go b/command/format/diff_test.go index 41c42553ed..2bd7dd8f5e 100644 --- a/command/format/diff_test.go +++ b/command/format/diff_test.go @@ -8,6 +8,7 @@ import ( "github.com/hashicorp/terraform/addrs" "github.com/hashicorp/terraform/configs/configschema" "github.com/hashicorp/terraform/plans" + "github.com/hashicorp/terraform/states" "github.com/mitchellh/colorstring" "github.com/zclconf/go-cty/cty" ) @@ -88,6 +89,27 @@ func TestResourceChange_primitiveTypes(t *testing.T) { - resource "test_instance" "example" { - id = "i-02ae66f368e8518a9" -> null } +`, + }, + "deletion of deposed object": { + Action: plans.Delete, + Mode: addrs.ManagedResourceMode, + DeposedKey: states.DeposedKey("byebye"), + Before: cty.ObjectVal(map[string]cty.Value{ + "id": cty.StringVal("i-02ae66f368e8518a9"), + }), + After: cty.NullVal(cty.EmptyObject), + Schema: &configschema.Block{ + Attributes: map[string]*configschema.Attribute{ + "id": {Type: cty.String, Computed: true}, + }, + }, + RequiredReplace: cty.NewPathSet(), + ExpectedOutput: ` # test_instance.example (deposed object byebye) will be destroyed + # (left over from a partially-failed replacement of this instance) + - resource "test_instance" "example" { + - id = "i-02ae66f368e8518a9" -> null + } `, }, "deletion (empty string)": { @@ -4081,6 +4103,7 @@ type testCase struct { Action plans.Action ActionReason plans.ResourceInstanceChangeActionReason Mode addrs.ResourceMode + DeposedKey states.DeposedKey Before cty.Value BeforeValMarks []cty.PathValueMarks AfterValMarks []cty.PathValueMarks @@ -4127,6 +4150,7 @@ func runTestCases(t *testing.T, testCases map[string]testCase) { Type: "test_instance", Name: "example", }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance), + DeposedKey: tc.DeposedKey, ProviderAddr: addrs.AbsProviderConfig{ Provider: addrs.NewDefaultProvider("test"), Module: addrs.RootModule, @@ -4143,9 +4167,8 @@ func runTestCases(t *testing.T, testCases map[string]testCase) { } output := ResourceChange(change, tc.Schema, color) - if output != tc.ExpectedOutput { - t.Errorf("Unexpected diff.\ngot:\n%s\nwant:\n%s\n", output, tc.ExpectedOutput) - t.Errorf("%s", cmp.Diff(output, tc.ExpectedOutput)) + if diff := cmp.Diff(output, tc.ExpectedOutput); diff != "" { + t.Errorf("wrong output\n%s", diff) } }) }