From ddb2bbf4e9952fceae0a0bdbc1e268f988302730 Mon Sep 17 00:00:00 2001 From: James Bardin Date: Thu, 22 Oct 2020 09:13:02 -0400 Subject: [PATCH] Read orphaned resources during plan This forces orphaned resources to be re-read during planning, removing them from the state if they no longer exist. This needs to be done for a bare `refresh` execution, since Terraform should remove instances that don't exist and are not in the configuration from the state. They should also be removed from state so there is no Delete change planned, as not all providers will gracefully handle a delete operation on a resource that does not exist. --- terraform/graph_builder_plan.go | 1 + terraform/node_resource_plan_orphan.go | 38 +++++++++++++++++++++++++- 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/terraform/graph_builder_plan.go b/terraform/graph_builder_plan.go index 503ce1b61b..49184c2e2b 100644 --- a/terraform/graph_builder_plan.go +++ b/terraform/graph_builder_plan.go @@ -191,6 +191,7 @@ func (b *PlanGraphBuilder) init() { b.ConcreteResourceOrphan = func(a *NodeAbstractResourceInstance) dag.Vertex { return &NodePlannableResourceInstanceOrphan{ NodeAbstractResourceInstance: a, + skipRefresh: b.skipRefresh, } } } diff --git a/terraform/node_resource_plan_orphan.go b/terraform/node_resource_plan_orphan.go index ca02000545..7469473e30 100644 --- a/terraform/node_resource_plan_orphan.go +++ b/terraform/node_resource_plan_orphan.go @@ -9,6 +9,8 @@ import ( // it is ready to be applied and is represented by a diff. type NodePlannableResourceInstanceOrphan struct { *NodeAbstractResourceInstance + + skipRefresh bool } var ( @@ -35,7 +37,7 @@ func (n *NodePlannableResourceInstanceOrphan) Execute(ctx EvalContext, op walkOp var change *plans.ResourceInstanceChange var state *states.ResourceInstanceObject - _, providerSchema, err := GetProvider(ctx, n.ResolvedProvider) + provider, providerSchema, err := GetProvider(ctx, n.ResolvedProvider) if err != nil { return err } @@ -45,6 +47,40 @@ func (n *NodePlannableResourceInstanceOrphan) Execute(ctx EvalContext, op walkOp return err } + if !n.skipRefresh { + // Refresh this instance even though it is going to be destroyed, in + // order to catch missing resources. If this is a normal plan, + // providers expect a Read request to remove missing resources from the + // plan before apply, and may not handle a missing resource during + // Delete correctly. If this is a simple refresh, Terraform is + // expected to remove the missing resource from the state entirely + refresh := &EvalRefresh{ + Addr: addr.Resource, + ProviderAddr: n.ResolvedProvider, + Provider: &provider, + ProviderMetas: n.ProviderMetas, + ProviderSchema: &providerSchema, + State: &state, + Output: &state, + } + _, err = refresh.Eval(ctx) + if err != nil { + return err + } + + writeRefreshState := &EvalWriteState{ + Addr: addr.Resource, + ProviderAddr: n.ResolvedProvider, + ProviderSchema: &providerSchema, + State: &state, + targetState: refreshState, + } + _, err = writeRefreshState.Eval(ctx) + if err != nil { + return err + } + } + diffDestroy := &EvalDiffDestroy{ Addr: addr.Resource, State: &state,