mirror of
https://github.com/opentffoundation/opentf.git
synced 2025-12-19 17:59:05 -05:00
Validations should only be run during non-destroy operations (#3131)
Signed-off-by: Christian Mesh <christianmesh1@gmail.com>
This commit is contained in:
@@ -29,6 +29,7 @@ BUG FIXES:
|
|||||||
* The `issensitive` function now returns an unknown result when its argument is unknown, since a sensitive unknown value can potentially become non-sensitive once more information is available. ([#3008](https://github.com/opentofu/opentofu/pull/3008))
|
* The `issensitive` function now returns an unknown result when its argument is unknown, since a sensitive unknown value can potentially become non-sensitive once more information is available. ([#3008](https://github.com/opentofu/opentofu/pull/3008))
|
||||||
* Provider references like "null.some_alias[each.key]" in .tf.json files are now correctly parsed ([#2915](https://github.com/opentofu/opentofu/issues/2915))
|
* Provider references like "null.some_alias[each.key]" in .tf.json files are now correctly parsed ([#2915](https://github.com/opentofu/opentofu/issues/2915))
|
||||||
* Fixed crash when processing multiple deprecated marks on a complex object ([#3105](https://github.com/opentofu/opentofu/pull/3105))
|
* Fixed crash when processing multiple deprecated marks on a complex object ([#3105](https://github.com/opentofu/opentofu/pull/3105))
|
||||||
|
* Variables with validation no longer interfere with the destroy process ([#3131](https://github.com/opentofu/opentofu/pull/3131))
|
||||||
|
|
||||||
## Previous Releases
|
## Previous Releases
|
||||||
|
|
||||||
|
|||||||
@@ -4820,14 +4820,19 @@ variable "root_var" {
|
|||||||
error_message = "${local.err} root"
|
error_message = "${local.err} root"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
data "test_data_source" "res_parent" {
|
||||||
|
}
|
||||||
|
data "test_data_source" "res" {
|
||||||
|
count = length(data.test_data_source.res_parent.id)
|
||||||
|
}
|
||||||
module "mod" {
|
module "mod" {
|
||||||
source = "./mod"
|
source = "./mod"
|
||||||
mod_var = local.value
|
mod_var = local.value + length(data.test_data_source.res[0].id)
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
"mod/mod.tofu": `
|
"mod/mod.tofu": `
|
||||||
locals {
|
locals {
|
||||||
expected = 10
|
expected = 21
|
||||||
err = "error"
|
err = "error"
|
||||||
}
|
}
|
||||||
variable "mod_var" {
|
variable "mod_var" {
|
||||||
@@ -4883,7 +4888,24 @@ variable "other_var" {
|
|||||||
t.Run("valid", func(t *testing.T) {
|
t.Run("valid", func(t *testing.T) {
|
||||||
input := InputValuesFromCaller(map[string]cty.Value{"root_var": cty.NumberIntVal(10)})
|
input := InputValuesFromCaller(map[string]cty.Value{"root_var": cty.NumberIntVal(10)})
|
||||||
|
|
||||||
ctx := testContext2(t, &ContextOpts{})
|
provider := testProvider("test")
|
||||||
|
|
||||||
|
provider.ReadDataSourceFn = func(req providers.ReadDataSourceRequest) providers.ReadDataSourceResponse {
|
||||||
|
return providers.ReadDataSourceResponse{
|
||||||
|
State: cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"id": cty.StringVal("data_source"),
|
||||||
|
"foo": cty.StringVal("ok"),
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ps := map[addrs.Provider]providers.Factory{
|
||||||
|
addrs.NewDefaultProvider("test"): testProviderFuncFixed(provider),
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := testContext2(t, &ContextOpts{
|
||||||
|
Providers: ps,
|
||||||
|
})
|
||||||
|
|
||||||
plan, diags := ctx.Plan(context.Background(), valid, nil, &PlanOpts{
|
plan, diags := ctx.Plan(context.Background(), valid, nil, &PlanOpts{
|
||||||
SetVariables: input,
|
SetVariables: input,
|
||||||
|
|||||||
@@ -194,7 +194,9 @@ func (b *ApplyGraphBuilder) Steps() []GraphTransformer {
|
|||||||
// We need to remove configuration nodes that are not used at all, as
|
// We need to remove configuration nodes that are not used at all, as
|
||||||
// they may not be able to evaluate, especially during destroy.
|
// they may not be able to evaluate, especially during destroy.
|
||||||
// These include variables, locals, and instance expanders.
|
// These include variables, locals, and instance expanders.
|
||||||
&pruneUnusedNodesTransformer{},
|
&pruneUnusedNodesTransformer{
|
||||||
|
Op: b.Operation,
|
||||||
|
},
|
||||||
|
|
||||||
// Target
|
// Target
|
||||||
&TargetingTransformer{Targets: b.Targets, Excludes: b.Excludes},
|
&TargetingTransformer{Targets: b.Targets, Excludes: b.Excludes},
|
||||||
|
|||||||
@@ -248,7 +248,7 @@ func (b *PlanGraphBuilder) Steps() []GraphTransformer {
|
|||||||
},
|
},
|
||||||
|
|
||||||
&pruneUnusedNodesTransformer{
|
&pruneUnusedNodesTransformer{
|
||||||
skip: b.Operation != walkPlanDestroy,
|
Op: b.Operation,
|
||||||
},
|
},
|
||||||
|
|
||||||
// Target
|
// Target
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ var (
|
|||||||
func (n *nodeExpandLocal) expandsInstances() {}
|
func (n *nodeExpandLocal) expandsInstances() {}
|
||||||
|
|
||||||
// graphNodeTemporaryValue
|
// graphNodeTemporaryValue
|
||||||
func (n *nodeExpandLocal) temporaryValue() bool {
|
func (n *nodeExpandLocal) temporaryValue(_ walkOperation) bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -103,7 +103,7 @@ var (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// graphNodeTemporaryValue
|
// graphNodeTemporaryValue
|
||||||
func (n *NodeLocal) temporaryValue() bool {
|
func (n *NodeLocal) temporaryValue(_ walkOperation) bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ var (
|
|||||||
|
|
||||||
func (n *nodeExpandModuleVariable) expandsInstances() {}
|
func (n *nodeExpandModuleVariable) expandsInstances() {}
|
||||||
|
|
||||||
func (n *nodeExpandModuleVariable) temporaryValue() bool {
|
func (n *nodeExpandModuleVariable) temporaryValue(_ walkOperation) bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -130,7 +130,7 @@ var (
|
|||||||
_ dag.GraphNodeDotter = (*nodeModuleVariable)(nil)
|
_ dag.GraphNodeDotter = (*nodeModuleVariable)(nil)
|
||||||
)
|
)
|
||||||
|
|
||||||
func (n *nodeModuleVariable) temporaryValue() bool {
|
func (n *nodeModuleVariable) temporaryValue(_ walkOperation) bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ var (
|
|||||||
|
|
||||||
func (n *nodeExpandOutput) expandsInstances() {}
|
func (n *nodeExpandOutput) expandsInstances() {}
|
||||||
|
|
||||||
func (n *nodeExpandOutput) temporaryValue() bool {
|
func (n *nodeExpandOutput) temporaryValue(_ walkOperation) bool {
|
||||||
// non root outputs are temporary
|
// non root outputs are temporary
|
||||||
return !n.Module.IsRoot()
|
return !n.Module.IsRoot()
|
||||||
}
|
}
|
||||||
@@ -220,7 +220,7 @@ var (
|
|||||||
_ dag.GraphNodeDotter = (*NodeApplyableOutput)(nil)
|
_ dag.GraphNodeDotter = (*NodeApplyableOutput)(nil)
|
||||||
)
|
)
|
||||||
|
|
||||||
func (n *NodeApplyableOutput) temporaryValue() bool {
|
func (n *NodeApplyableOutput) temporaryValue(_ walkOperation) bool {
|
||||||
// this must always be evaluated if it is a root module output
|
// this must always be evaluated if it is a root module output
|
||||||
return !n.Addr.Module.IsRoot()
|
return !n.Addr.Module.IsRoot()
|
||||||
}
|
}
|
||||||
@@ -499,7 +499,7 @@ func (n *NodeDestroyableOutput) ModulePath() addrs.Module {
|
|||||||
return n.Addr.Module.Module()
|
return n.Addr.Module.Module()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *NodeDestroyableOutput) temporaryValue() bool {
|
func (n *NodeDestroyableOutput) temporaryValue(_ walkOperation) bool {
|
||||||
// this must always be evaluated if it is a root module output
|
// this must always be evaluated if it is a root module output
|
||||||
return !n.Addr.Module.IsRoot()
|
return !n.Addr.Module.IsRoot()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,5 +11,5 @@ package tofu
|
|||||||
// A boolean return value allows a node which may need to be saved to
|
// A boolean return value allows a node which may need to be saved to
|
||||||
// conditionally do so.
|
// conditionally do so.
|
||||||
type graphNodeTemporaryValue interface {
|
type graphNodeTemporaryValue interface {
|
||||||
temporaryValue() bool
|
temporaryValue(op walkOperation) bool
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,8 +45,8 @@ var (
|
|||||||
func (n *nodeVariableReference) expandsInstances() {}
|
func (n *nodeVariableReference) expandsInstances() {}
|
||||||
|
|
||||||
// Abuse graphNodeTemporaryValue to keep the validation rule around
|
// Abuse graphNodeTemporaryValue to keep the validation rule around
|
||||||
func (n *nodeVariableReference) temporaryValue() bool {
|
func (n *nodeVariableReference) temporaryValue(op walkOperation) bool {
|
||||||
return len(n.Config.Validations) == 0
|
return len(n.Config.Validations) == 0 || op == walkDestroy || op == walkPlanDestroy
|
||||||
}
|
}
|
||||||
|
|
||||||
// GraphNodeDynamicExpandable
|
// GraphNodeDynamicExpandable
|
||||||
|
|||||||
@@ -287,11 +287,11 @@ type pruneUnusedNodesTransformer struct {
|
|||||||
// destroy. Planing normally involves all nodes, but during a destroy plan
|
// destroy. Planing normally involves all nodes, but during a destroy plan
|
||||||
// we may need to prune things which are in the configuration but do not
|
// we may need to prune things which are in the configuration but do not
|
||||||
// exist in state to evaluate.
|
// exist in state to evaluate.
|
||||||
skip bool
|
Op walkOperation
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *pruneUnusedNodesTransformer) Transform(_ context.Context, g *Graph) error {
|
func (t *pruneUnusedNodesTransformer) Transform(_ context.Context, g *Graph) error {
|
||||||
if t.skip {
|
if t.Op != walkPlanDestroy && t.Op != walkDestroy && t.Op != walkApply {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -314,7 +314,7 @@ func (t *pruneUnusedNodesTransformer) Transform(_ context.Context, g *Graph) err
|
|||||||
case graphNodeTemporaryValue:
|
case graphNodeTemporaryValue:
|
||||||
// root module outputs indicate they are not temporary by
|
// root module outputs indicate they are not temporary by
|
||||||
// returning false here.
|
// returning false here.
|
||||||
if !n.temporaryValue() {
|
if !n.temporaryValue(t.Op) {
|
||||||
log.Printf("[TRACE] pruneUnusedNodes: temporary value vertex %q kept because it's not a temporary value vertex", dag.VertexName(n))
|
log.Printf("[TRACE] pruneUnusedNodes: temporary value vertex %q kept because it's not a temporary value vertex", dag.VertexName(n))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -342,7 +342,7 @@ func (t *pruneUnusedNodesTransformer) Transform(_ context.Context, g *Graph) err
|
|||||||
// root module, and so it's not actually important
|
// root module, and so it's not actually important
|
||||||
// to expand it and so this lets us do a bit more
|
// to expand it and so this lets us do a bit more
|
||||||
// pruning than we'd be able to do otherwise.
|
// pruning than we'd be able to do otherwise.
|
||||||
if tmp, ok := v.(graphNodeTemporaryValue); ok && !tmp.temporaryValue() {
|
if tmp, ok := v.(graphNodeTemporaryValue); ok && !tmp.temporaryValue(t.Op) {
|
||||||
log.Printf("[TRACE] pruneUnusedNodes: expanding vertex %q kept because another expanding vertex %q with non-temporary value is one of its dependencies", dag.VertexName(n), dag.VertexName(v))
|
log.Printf("[TRACE] pruneUnusedNodes: expanding vertex %q kept because another expanding vertex %q with non-temporary value is one of its dependencies", dag.VertexName(n), dag.VertexName(v))
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -411,7 +411,7 @@ func TestPruneUnusedNodesTransformer_rootModuleOutputValues(t *testing.T) {
|
|||||||
},
|
},
|
||||||
&ReferenceTransformer{},
|
&ReferenceTransformer{},
|
||||||
&AttachDependenciesTransformer{},
|
&AttachDependenciesTransformer{},
|
||||||
&pruneUnusedNodesTransformer{},
|
&pruneUnusedNodesTransformer{Op: walkDestroy},
|
||||||
&CloseRootModuleTransformer{},
|
&CloseRootModuleTransformer{},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -204,7 +204,8 @@ func (t *TargetingTransformer) getTargetedOutputNodes(targetedNodes dag.Set, gra
|
|||||||
|
|
||||||
// root module outputs indicate that while they are an output type,
|
// root module outputs indicate that while they are an output type,
|
||||||
// they not temporary and will return false here.
|
// they not temporary and will return false here.
|
||||||
if tv.temporaryValue() {
|
// We use walkInvalid here as we only care about the op as a workaround for nodeVariableReference, which does not apply here
|
||||||
|
if tv.temporaryValue(walkInvalid) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user