Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changes/v1.14/ENHANCEMENTS-20250723-141420.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
kind: ENHANCEMENTS
body: 'terraform test: ignore prevent_destroy attribute during when cleaning up tests"'
time: 2025-07-23T14:14:20.602923+02:00
custom:
Issue: "37364"
4 changes: 4 additions & 0 deletions internal/command/test_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -357,6 +357,10 @@ func TestTest_Runs(t *testing.T) {
expectedErr: []string{"Invalid condition run"},
code: 1,
},
"prevent-destroy": {
expectedOut: []string{"1 passed, 0 failed."},
code: 0,
},
}
for name, tc := range tcs {
t.Run(name, func(t *testing.T) {
Expand Down
7 changes: 7 additions & 0 deletions internal/command/testdata/test/prevent-destroy/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@

resource "test_resource" "resource" {
lifecycle {
// we should still be able to destroy this during tests.
prevent_destroy = true
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@

run "test" {}
10 changes: 6 additions & 4 deletions internal/moduletest/graph/node_state_cleanup.go
Original file line number Diff line number Diff line change
Expand Up @@ -131,10 +131,12 @@ func (n *NodeStateCleanup) destroy(ctx *EvalContext, runNode *NodeTestRun, waite
setVariables, _, _ := runNode.FilterVariablesToModule(variables)

planOpts := &terraform.PlanOpts{
Mode: plans.DestroyMode,
SetVariables: setVariables,
Overrides: mocking.PackageOverrides(run.Config, file.Config, mocks),
ExternalProviders: providers,
Mode: plans.DestroyMode,
SetVariables: setVariables,
Overrides: mocking.PackageOverrides(run.Config, file.Config, mocks),
ExternalProviders: providers,
SkipRefresh: true,
OverridePreventDestroy: true,
}

tfCtx, _ := terraform.NewContext(n.opts.ContextOpts)
Expand Down
12 changes: 12 additions & 0 deletions internal/terraform/context_plan.go
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,12 @@ type PlanOpts struct {
// Query is a boolean that indicates whether the plan is being
// generated for a query operation.
Query bool

// OverridePreventDestroy will override any prevent_destroy attributes
// allowing Terraform to destroy resources even if the prevent_destroy
// attribute is set. This can only be set during a destroy plan, and should
// only be set during the test command.
OverridePreventDestroy bool
}

// Plan generates an execution plan by comparing the given configuration
Expand Down Expand Up @@ -494,6 +500,7 @@ func (c *Context) destroyPlan(config *configs.Config, prevRunState *states.State
refreshOpts := *opts
refreshOpts.Mode = plans.NormalMode
refreshOpts.PreDestroyRefresh = true
refreshOpts.OverridePreventDestroy = false

// FIXME: A normal plan is required here to refresh the state, because
// the state and configuration may not match during a destroy, and a
Expand Down Expand Up @@ -893,6 +900,10 @@ func (c *Context) planGraph(config *configs.Config, prevRunState *states.State,
externalProviderConfigs = opts.ExternalProviders
}

if opts != nil && opts.OverridePreventDestroy && opts.Mode != plans.DestroyMode {
panic("you can only set OverridePreventDestroy during destroy operations.")
}

switch mode := opts.Mode; mode {
case plans.NormalMode:
// In Normal mode we need to pay attention to import and removed blocks
Expand Down Expand Up @@ -950,6 +961,7 @@ func (c *Context) planGraph(config *configs.Config, prevRunState *states.State,
Operation: walkPlanDestroy,
Overrides: opts.Overrides,
SkipGraphValidation: c.graphOpts.SkipGraphValidation,
overridePreventDestroy: opts.OverridePreventDestroy,
}).Build(addrs.RootModuleInstance)
return graph, walkPlanDestroy, diags
default:
Expand Down
10 changes: 10 additions & 0 deletions internal/terraform/graph_builder_plan.go
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,11 @@ type PlanGraphBuilder struct {
// If true, the graph builder will generate a query plan instead of a
// normal plan. This is used for the "terraform query" command.
queryPlan bool

// overridePreventDestroy is only applicable during destroy operations, and
// allows Terraform to ignore the configuration attribute prevent_destroy
// to destroy resources regardless.
overridePreventDestroy bool
}

// See GraphBuilder
Expand Down Expand Up @@ -141,6 +146,10 @@ func (b *PlanGraphBuilder) Steps() []GraphTransformer {
panic("invalid plan operation: " + b.Operation.String())
}

if b.overridePreventDestroy && b.Operation != walkPlanDestroy {
panic("overridePreventDestroy can only be set during walkPlanDestroy operations")
}

steps := []GraphTransformer{
// Creates all the resources represented in the config
&ConfigTransformer{
Expand Down Expand Up @@ -336,6 +345,7 @@ func (b *PlanGraphBuilder) initDestroy() {
b.initPlan()

b.ConcreteResourceInstance = func(a *NodeAbstractResourceInstance) dag.Vertex {
a.overridePreventDestroy = b.overridePreventDestroy
return &NodePlanDestroyableResourceInstance{
NodeAbstractResourceInstance: a,
skipRefresh: b.skipRefresh,
Expand Down
7 changes: 6 additions & 1 deletion internal/terraform/node_resource_abstract_instance.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,11 @@ type NodeAbstractResourceInstance struct {

preDestroyRefresh bool

// overridePreventDestroy is set during test cleanup operations to allow
// tests to clean up any created infrastructure regardless of this setting
// in the configuration.
overridePreventDestroy bool

// During import (or query) we may generate configuration for a resource, which needs
// to be stored in the final change.
generatedConfigHCL string
Expand Down Expand Up @@ -185,7 +190,7 @@ func (n *NodeAbstractResourceInstance) checkPreventDestroy(change *plans.Resourc
return nil
}

preventDestroy := n.Config.Managed.PreventDestroy
preventDestroy := n.Config.Managed.PreventDestroy && !n.overridePreventDestroy

if (change.Action == plans.Delete || change.Action.IsReplace()) && preventDestroy {
var diags tfdiags.Diagnostics
Expand Down