From 2c09ae4f3d8f7e311dd2572ecf1f8bcbbcd5cd2f Mon Sep 17 00:00:00 2001 From: James Bardin Date: Mon, 17 Apr 2023 13:48:04 -0400 Subject: [PATCH] prune unused providers within modules The logic used to prune unused providers was only taking into account the common case of providers in the root module. The quick check of looking for up edges doesn't work within a module, because the module structures will create non-resource nodes connected to the providers. Use a deeper check of looking for any dependent resources which may require that provider to be configured. --- internal/terraform/context_apply2_test.go | 43 ++++++++++++++++++++ internal/terraform/transform_destroy_edge.go | 16 ++++++-- 2 files changed, 55 insertions(+), 4 deletions(-) diff --git a/internal/terraform/context_apply2_test.go b/internal/terraform/context_apply2_test.go index 18f46768b1..c9d4aad1ba 100644 --- a/internal/terraform/context_apply2_test.go +++ b/internal/terraform/context_apply2_test.go @@ -2042,3 +2042,46 @@ resource "test_resource" "b" { _, diags = ctx.Apply(plan, m) assertNoErrors(t, diags) } + +func TestContext2Apply_destroyUnusedModuleProvider(t *testing.T) { + // an unsued provider within a module should not be called during destroy + unusedProvider := testProvider("unused") + testProvider := testProvider("test") + ctx := testContext2(t, &ContextOpts{ + Providers: map[addrs.Provider]providers.Factory{ + addrs.NewDefaultProvider("test"): testProviderFuncFixed(testProvider), + addrs.NewDefaultProvider("unused"): testProviderFuncFixed(unusedProvider), + }, + }) + + unusedProvider.ConfigureProviderFn = func(req providers.ConfigureProviderRequest) (resp providers.ConfigureProviderResponse) { + resp.Diagnostics = resp.Diagnostics.Append(errors.New("configuration failed")) + return resp + } + + m := testModuleInline(t, map[string]string{ + "main.tf": ` +module "mod" { + source = "./mod" +} + +resource "test_resource" "test" { +} +`, + + "mod/main.tf": ` +provider "unused" { +} + +resource "unused_resource" "test" { +} +`, + }) + + plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{ + Mode: plans.DestroyMode, + }) + assertNoErrors(t, diags) + _, diags = ctx.Apply(plan, m) + assertNoErrors(t, diags) +} diff --git a/internal/terraform/transform_destroy_edge.go b/internal/terraform/transform_destroy_edge.go index 2d6fb7083f..357bfca8ab 100644 --- a/internal/terraform/transform_destroy_edge.go +++ b/internal/terraform/transform_destroy_edge.go @@ -348,10 +348,18 @@ func (t *pruneUnusedNodesTransformer) Transform(g *Graph) error { } case GraphNodeProvider: - // Providers that may have been required by expansion nodes - // that we no longer need can also be removed. - if g.UpEdges(n).Len() > 0 { - return + // Only keep providers for evaluation if they have + // resources to handle. + // The provider transformers removed most unused providers + // earlier, however there may be more to prune now based on + // targeting or a destroy with no related instances in the + // state. + des, _ := g.Descendents(n) + for _, v := range des { + switch v.(type) { + case GraphNodeProviderConsumer: + return + } } default: