diff --git a/internal/terraform/node_output.go b/internal/terraform/node_output.go index ebce8a872d60..f3b1f5c90a7e 100644 --- a/internal/terraform/node_output.go +++ b/internal/terraform/node_output.go @@ -5,6 +5,8 @@ import ( "log" "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/dag" @@ -13,7 +15,6 @@ import ( "github.com/hashicorp/terraform/internal/plans" "github.com/hashicorp/terraform/internal/states" "github.com/hashicorp/terraform/internal/tfdiags" - "github.com/zclconf/go-cty/cty" ) // nodeExpandOutput is the placeholder for a non-root module output that has @@ -413,12 +414,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 e2bbdec74535..892427c77ce0 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") + } +}