diff --git a/terraform/context_apply2_test.go b/terraform/context_apply2_test.go new file mode 100644 index 000000000..cc3ee2f47 --- /dev/null +++ b/terraform/context_apply2_test.go @@ -0,0 +1 @@ +package terraform diff --git a/terraform/context_apply_test.go b/terraform/context_apply_test.go index 537bd289b..1f80ae7d3 100644 --- a/terraform/context_apply_test.go +++ b/terraform/context_apply_test.go @@ -12564,3 +12564,8 @@ resource "test_object" "a" { t.Fatalf("error missing expected info: %q", errString) } } + +//////////////////////////////////////////////////////////////////////////////// +// NOTE: Due to the size of this file, new tests should be added to +// context_apply2_test.go. +//////////////////////////////////////////////////////////////////////////////// diff --git a/terraform/context_plan2_test.go b/terraform/context_plan2_test.go new file mode 100644 index 000000000..ecf2a36f8 --- /dev/null +++ b/terraform/context_plan2_test.go @@ -0,0 +1,61 @@ +package terraform + +import ( + "testing" + + "github.com/hashicorp/terraform/addrs" + "github.com/hashicorp/terraform/plans" + "github.com/hashicorp/terraform/providers" + "github.com/hashicorp/terraform/states" + "github.com/zclconf/go-cty/cty" +) + +func TestContext2Plan_removedDuringRefresh(t *testing.T) { + // The resource was added to state but actually failed to create and was + // left tainted. This should be removed during plan and result in a Create + // action. + m := testModuleInline(t, map[string]string{ + "main.tf": ` +resource "test_object" "a" { +} +`, + }) + + p := simpleMockProvider() + p.ReadResourceFn = func(req providers.ReadResourceRequest) (resp providers.ReadResourceResponse) { + resp.NewState = cty.NullVal(req.PriorState.Type()) + return resp + } + + addr := mustResourceInstanceAddr("test_object.a") + state := states.BuildState(func(s *states.SyncState) { + s.SetResourceInstanceCurrent(addr, &states.ResourceInstanceObjectSrc{ + AttrsJSON: []byte(`{"test_string":"foo"}`), + Status: states.ObjectTainted, + }, mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`)) + }) + + ctx := testContext2(t, &ContextOpts{ + Config: m, + State: state, + Providers: map[addrs.Provider]providers.Factory{ + addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), + }, + }) + + plan, diags := ctx.Plan() + if diags.HasErrors() { + t.Fatal(diags.Err()) + } + + for _, c := range plan.Changes.Resources { + if c.Action != plans.Create { + t.Fatalf("expected Create action for missing %s, got %s", c.Addr, c.Action) + } + } + + _, diags = ctx.Apply() + if diags.HasErrors() { + t.Fatal(diags.Err()) + } +} diff --git a/terraform/context_plan_test.go b/terraform/context_plan_test.go index 099fff1c1..5507cc08d 100644 --- a/terraform/context_plan_test.go +++ b/terraform/context_plan_test.go @@ -6732,3 +6732,8 @@ output "planned" { } } } + +//////////////////////////////////////////////////////////////////////////////// +// NOTE: Due to the size of this file, new tests should be added to +// context_plan2_test.go. +//////////////////////////////////////////////////////////////////////////////// diff --git a/terraform/node_resource_abstract_instance.go b/terraform/node_resource_abstract_instance.go index 1393d1abe..ad5574c85 100644 --- a/terraform/node_resource_abstract_instance.go +++ b/terraform/node_resource_abstract_instance.go @@ -905,7 +905,7 @@ func (n *NodeAbstractResourceInstance) plan( // If our prior value was tainted then we actually want this to appear // as a replace change, even though so far we've been treating it as a // create. - if action == plans.Create && priorValTainted != cty.NilVal { + if action == plans.Create && !priorValTainted.IsNull() { if createBeforeDestroy { action = plans.CreateThenDelete } else { diff --git a/terraform/node_resource_plan_instance.go b/terraform/node_resource_plan_instance.go index e6cfc1dc7..1f57e85c8 100644 --- a/terraform/node_resource_plan_instance.go +++ b/terraform/node_resource_plan_instance.go @@ -128,11 +128,12 @@ func (n *NodePlannableResourceInstance) managedResourceExecute(ctx EvalContext) // Refresh, maybe if !n.skipRefresh { - instanceRefreshState, refreshDiags := n.refresh(ctx, instanceRefreshState) + s, refreshDiags := n.refresh(ctx, instanceRefreshState) diags = diags.Append(refreshDiags) if diags.HasErrors() { return diags } + instanceRefreshState = s diags = diags.Append(n.writeResourceInstanceState(ctx, instanceRefreshState, n.Dependencies, refreshState)) if diags.HasErrors() {