From 3779dbc2af19c3b6d365ee054ce630782cb02628 Mon Sep 17 00:00:00 2001 From: James Bardin Date: Tue, 11 Oct 2022 14:30:29 -0400 Subject: [PATCH] noop orphan change has nothing to apply An orphaned resource which plans as a NoOp change will have no config. This is not an error, but there is nothing to do since there are also no checks to validate. We still leave the change in the plan to keep the plan as complete as possible, noting all possible changes. Preventing the node from being added to the graph is awkward, because the config is attached separately from the diff transformer. This should not pose any problems however, because there is no longer any state or config linking the instance to any dependencies in the graph. --- internal/terraform/context_apply2_test.go | 43 +++++++++++++++++++ .../terraform/node_resource_apply_instance.go | 15 ++++--- 2 files changed, 53 insertions(+), 5 deletions(-) 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",