diff --git a/internal/terraform/context_apply2_test.go b/internal/terraform/context_apply2_test.go index 73ef7b3d0cf5..c0e734f68bc8 100644 --- a/internal/terraform/context_apply2_test.go +++ b/internal/terraform/context_apply2_test.go @@ -1442,3 +1442,46 @@ resource "test_object" "x" { t.Fatalf("apply: %s", diags.Err()) } } + +func TestContext2Apply_missingOrphanedResource(t *testing.T) { + m := testModuleInline(t, map[string]string{ + "main.tf": ` +# changed resource address to create a new object +resource "test_object" "y" { + test_string = "y" +} +`, + }) + + p := simpleMockProvider() + + // report the prior value is missing + p.ReadResourceFn = func(req providers.ReadResourceRequest) (resp providers.ReadResourceResponse) { + resp.NewState = cty.NullVal(req.PriorState.Type()) + return resp + } + + state := states.NewState() + root := state.EnsureModule(addrs.RootModuleInstance) + root.SetResourceInstanceCurrent( + mustResourceInstanceAddr("test_object.x").Resource, + &states.ResourceInstanceObjectSrc{ + Status: states.ObjectReady, + AttrsJSON: []byte(`{"test_string":"x"}`), + }, + mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`), + ) + + ctx := testContext2(t, &ContextOpts{ + Providers: map[addrs.Provider]providers.Factory{ + addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), + }, + }) + + opts := SimplePlanOpts(plans.NormalMode, nil) + plan, diags := ctx.Plan(m, state, opts) + assertNoErrors(t, diags) + + _, diags = ctx.Apply(plan, m) + assertNoErrors(t, diags) +} diff --git a/internal/terraform/node_resource_apply_instance.go b/internal/terraform/node_resource_apply_instance.go index 7fc6e6fc2e28..f257c3f7d0c4 100644 --- a/internal/terraform/node_resource_apply_instance.go +++ b/internal/terraform/node_resource_apply_instance.go @@ -113,11 +113,16 @@ func (n *NodeApplyableResourceInstance) Execute(ctx EvalContext, op walkOperatio addr := n.ResourceInstanceAddr() if n.Config == nil { - // This should not be possible, but we've got here in at least one - // case as discussed in the following issue: - // https://github.com/hashicorp/terraform/issues/21258 - // To avoid an outright crash here, we'll instead return an explicit - // error. + // If there is no config, and there is no change, then we have nothing + // to do and the change was left in the plan for informational + // purposes only. + changes := ctx.Changes() + csrc := changes.GetResourceInstanceChange(n.ResourceInstanceAddr(), states.CurrentGen) + if csrc == nil || csrc.Action == plans.NoOp { + log.Printf("[DEBUG] NodeApplyableResourceInstance: No config or planned change recorded for %s", n.Addr) + return nil + } + diags = diags.Append(tfdiags.Sourceless( tfdiags.Error, "Resource node has no configuration attached",