diff --git a/internal/terraform/context_apply2_test.go b/internal/terraform/context_apply2_test.go index 55eca3b9fa..11db793140 100644 --- a/internal/terraform/context_apply2_test.go +++ b/internal/terraform/context_apply2_test.go @@ -2163,6 +2163,86 @@ import { } } +func TestContext2Apply_destroySkipsVariableValidations(t *testing.T) { + m := testModuleInline(t, map[string]string{ + "main.tf": ` +variable "input" { + type = string + + validation { + condition = var.input == "foo" + error_message = "bad input" + } +} + +resource "test_object" "a" { + test_string = var.input +} +`, + }) + + p := simpleMockProvider() + ctx := testContext2(t, &ContextOpts{ + Providers: map[addrs.Provider]providers.Factory{ + addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), + }, + }) + + plan, diags := ctx.Plan(m, states.BuildState(func(state *states.SyncState) { + state.SetResourceInstanceCurrent( + mustResourceInstanceAddr("test_object.a"), + &states.ResourceInstanceObjectSrc{ + Status: states.ObjectReady, + AttrsJSON: []byte(`{"test_string":"foo"}`), + }, + mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`), + ) + }), &PlanOpts{ + Mode: plans.DestroyMode, + SetVariables: InputValues{ + "input": { + Value: cty.StringVal("foo"), + SourceType: ValueFromCLIArg, + SourceRange: tfdiags.SourceRange{}, + }, + }, + }) + if diags.HasErrors() { + t.Errorf("expected no errors, but got %s", diags) + } + + planResult := plan.Checks.GetObjectResult(addrs.AbsInputVariableInstance{ + Variable: addrs.InputVariable{ + Name: "input", + }, + Module: addrs.RootModuleInstance, + }) + + if planResult.Status != checks.StatusPass { + // Should have passed during the planning stage indicating that it did + // actually execute. + t.Errorf("expected checks to be pass but was %s", planResult.Status) + } + + state, diags := ctx.Apply(plan, m) + if diags.HasErrors() { + t.Errorf("expected no errors, but got %s", diags) + } + + applyResult := state.CheckResults.GetObjectResult(addrs.AbsInputVariableInstance{ + Variable: addrs.InputVariable{ + Name: "input", + }, + Module: addrs.RootModuleInstance, + }) + + if applyResult.Status != checks.StatusUnknown { + // Shouldn't have made any validations here, so result should have + // stayed as unknown. + t.Errorf("expected checks to be unknown but was %s", applyResult.Status) + } +} + func TestContext2Apply_noExternalReferences(t *testing.T) { m := testModuleInline(t, map[string]string{ "main.tf": ` diff --git a/internal/terraform/graph_builder_apply.go b/internal/terraform/graph_builder_apply.go index 26bf452b31..a331bf2be7 100644 --- a/internal/terraform/graph_builder_apply.go +++ b/internal/terraform/graph_builder_apply.go @@ -100,8 +100,15 @@ func (b *ApplyGraphBuilder) Steps() []GraphTransformer { }, // Add dynamic values - &RootVariableTransformer{Config: b.Config, RawValues: b.RootVariableValues}, - &ModuleVariableTransformer{Config: b.Config}, + &RootVariableTransformer{ + Config: b.Config, + RawValues: b.RootVariableValues, + DestroyApply: b.Operation == walkDestroy, + }, + &ModuleVariableTransformer{ + Config: b.Config, + DestroyApply: b.Operation == walkDestroy, + }, &LocalTransformer{Config: b.Config}, &OutputTransformer{ Config: b.Config, diff --git a/internal/terraform/graph_builder_plan.go b/internal/terraform/graph_builder_plan.go index 042d3c525e..927be26117 100644 --- a/internal/terraform/graph_builder_plan.go +++ b/internal/terraform/graph_builder_plan.go @@ -128,8 +128,17 @@ func (b *PlanGraphBuilder) Steps() []GraphTransformer { }, // Add dynamic values - &RootVariableTransformer{Config: b.Config, RawValues: b.RootVariableValues, Planning: true}, - &ModuleVariableTransformer{Config: b.Config, Planning: true}, + &RootVariableTransformer{ + Config: b.Config, + RawValues: b.RootVariableValues, + Planning: true, + DestroyApply: false, // always false for planning + }, + &ModuleVariableTransformer{ + Config: b.Config, + Planning: true, + DestroyApply: false, // always false for planning + }, &LocalTransformer{Config: b.Config}, &OutputTransformer{ Config: b.Config, diff --git a/internal/terraform/node_module_variable.go b/internal/terraform/node_module_variable.go index d0c27cc6bc..7237fb84b3 100644 --- a/internal/terraform/node_module_variable.go +++ b/internal/terraform/node_module_variable.go @@ -29,6 +29,10 @@ type nodeExpandModuleVariable struct { // Planning must be set to true when building a planning graph, and must be // false when building an apply graph. Planning bool + + // DestroyApply must be set to true when planning or applying a destroy + // operation, and false otherwise. + DestroyApply bool } var ( @@ -72,6 +76,7 @@ func (n *nodeExpandModuleVariable) DynamicExpand(ctx EvalContext) (*Graph, tfdia Config: n.Config, Expr: n.Expr, ModuleInstance: module, + DestroyApply: n.DestroyApply, } g.Add(o) } @@ -140,6 +145,10 @@ type nodeModuleVariable struct { // ModuleInstance in order to create the appropriate context for evaluating // ModuleCallArguments, ex. so count.index and each.key can resolve ModuleInstance addrs.ModuleInstance + + // DestroyApply must be set to true when applying a destroy operation and + // false otherwise. + DestroyApply bool } // Ensure that we are implementing all of the interfaces we think we are @@ -195,7 +204,14 @@ func (n *nodeModuleVariable) Execute(ctx EvalContext, op walkOperation) (diags t _, call := n.Addr.Module.CallInstance() ctx.SetModuleCallArgument(call, n.Addr.Variable, val) - return evalVariableValidations(n.Addr, n.Config, n.Expr, ctx) + // Skip evalVariableValidations during destroy operations. We still want + // to evaluate the variable in case it is used to initialise providers + // or something downstream but we don't need to report on the success + // or failure of any validations for destroy operations. + if !n.DestroyApply { + diags = diags.Append(evalVariableValidations(n.Addr, n.Config, n.Expr, ctx)) + } + return diags } // dag.GraphNodeDotter impl. diff --git a/internal/terraform/node_root_variable.go b/internal/terraform/node_root_variable.go index 55498f468f..3152d8427d 100644 --- a/internal/terraform/node_root_variable.go +++ b/internal/terraform/node_root_variable.go @@ -29,6 +29,10 @@ type NodeRootVariable struct { // Planning must be set to true when building a planning graph, and must be // false when building an apply graph. Planning bool + + // DestroyApply must be set to true when applying a destroy operation and + // false otherwise. + DestroyApply bool } var ( @@ -109,13 +113,15 @@ func (n *NodeRootVariable) Execute(ctx EvalContext, op walkOperation) tfdiags.Di ctx.SetRootModuleArgument(addr.Variable, finalVal) - moreDiags = evalVariableValidations( - addrs.RootModuleInstance.InputVariable(n.Addr.Name), - n.Config, - nil, // not set for root module variables - ctx, - ) - diags = diags.Append(moreDiags) + if !n.DestroyApply { + diags = diags.Append(evalVariableValidations( + addrs.RootModuleInstance.InputVariable(n.Addr.Name), + n.Config, + nil, // not set for root module variables + ctx, + )) + } + return diags } diff --git a/internal/terraform/transform_module_variable.go b/internal/terraform/transform_module_variable.go index ac3df7852a..6da5c4bc6e 100644 --- a/internal/terraform/transform_module_variable.go +++ b/internal/terraform/transform_module_variable.go @@ -32,6 +32,10 @@ type ModuleVariableTransformer struct { // Planning must be set to true when building a planning graph, and must be // false when building an apply graph. Planning bool + + // DestroyApply must be set to true when applying a destroy operation and + // false otherwise. + DestroyApply bool } func (t *ModuleVariableTransformer) Transform(g *Graph) error { @@ -110,10 +114,11 @@ func (t *ModuleVariableTransformer) transformSingle(g *Graph, parent, c *configs Addr: addrs.InputVariable{ Name: v.Name, }, - Module: c.Path, - Config: v, - Expr: expr, - Planning: t.Planning, + Module: c.Path, + Config: v, + Expr: expr, + Planning: t.Planning, + DestroyApply: t.DestroyApply, } g.Add(node) } diff --git a/internal/terraform/transform_variable.go b/internal/terraform/transform_variable.go index 15d183354f..2d9365d5fe 100644 --- a/internal/terraform/transform_variable.go +++ b/internal/terraform/transform_variable.go @@ -22,6 +22,10 @@ type RootVariableTransformer struct { // Planning must be set to true when building a planning graph, and must be // false when building an apply graph. Planning bool + + // DestroyApply must be set to true when applying a destroy operation and + // false otherwise. + DestroyApply bool } func (t *RootVariableTransformer) Transform(g *Graph) error { @@ -40,9 +44,10 @@ func (t *RootVariableTransformer) Transform(g *Graph) error { Addr: addrs.InputVariable{ Name: v.Name, }, - Config: v, - RawValue: t.RawValues[v.Name], - Planning: t.Planning, + Config: v, + RawValue: t.RawValues[v.Name], + Planning: t.Planning, + DestroyApply: t.DestroyApply, } g.Add(node) }