From c4a00664d7ddb45a9d45db40355126cf4b06fa82 Mon Sep 17 00:00:00 2001 From: kmoe <5575356+kmoe@users.noreply.github.com> Date: Wed, 3 Aug 2022 17:45:13 +0100 Subject: [PATCH] do not create delete change for nonexistent output (#31471) If there are outputs in configuration, a destroy plan will always contain a "delete" change for each of these outputs. This leads to meaningless delete changes being present for outputs which were not present in state and therefore cannot be deleted. Since there is a change in the plan, this plan will then be considered applyable, and the user will be presented with text instructing them to apply a plan in which there are no actual changes. This commit stops the above from happening in the case of root module outputs. --- internal/terraform/node_output.go | 14 ++++++++------ internal/terraform/node_output_test.go | 22 +++++++++++++++++++++- 2 files changed, 29 insertions(+), 7 deletions(-) diff --git a/internal/terraform/node_output.go b/internal/terraform/node_output.go index 2f6febd685..55b0717524 100644 --- a/internal/terraform/node_output.go +++ b/internal/terraform/node_output.go @@ -420,12 +420,14 @@ func (n *NodeDestroyableOutput) Execute(ctx EvalContext, op walkOperation) tfdia before := cty.NullVal(cty.DynamicPseudoType) mod := state.Module(n.Addr.Module) if n.Addr.Module.IsRoot() && mod != nil { - for name, o := range mod.OutputValues { - if name == n.Addr.OutputValue.Name { - sensitiveBefore = o.Sensitive - before = o.Value - break - } + if o, ok := mod.OutputValues[n.Addr.OutputValue.Name]; ok { + sensitiveBefore = o.Sensitive + before = o.Value + } else { + // If the output was not in state, a delete change would + // be meaningless, so exit early. + return nil + } } diff --git a/internal/terraform/node_output_test.go b/internal/terraform/node_output_test.go index e2bbdec745..892427c77c 100644 --- a/internal/terraform/node_output_test.go +++ b/internal/terraform/node_output_test.go @@ -5,11 +5,12 @@ import ( "testing" "github.com/hashicorp/hcl/v2" + "github.com/zclconf/go-cty/cty" + "github.com/hashicorp/terraform/internal/addrs" "github.com/hashicorp/terraform/internal/configs" "github.com/hashicorp/terraform/internal/lang/marks" "github.com/hashicorp/terraform/internal/states" - "github.com/zclconf/go-cty/cty" ) func TestNodeApplyableOutputExecute_knownValue(t *testing.T) { @@ -160,3 +161,22 @@ func TestNodeDestroyableOutputExecute(t *testing.T) { t.Fatal("Unexpected outputs in state after removal") } } + +func TestNodeDestroyableOutputExecute_notInState(t *testing.T) { + outputAddr := addrs.OutputValue{Name: "foo"}.Absolute(addrs.RootModuleInstance) + + state := states.NewState() + + ctx := &MockEvalContext{ + StateState: state.SyncWrapper(), + } + node := NodeDestroyableOutput{Addr: outputAddr} + + diags := node.Execute(ctx, walkApply) + if diags.HasErrors() { + t.Fatalf("Unexpected error: %s", diags.Err()) + } + if state.OutputValue(outputAddr) != nil { + t.Fatal("Unexpected outputs in state after removal") + } +}