opentofu/internal/tofu/node_variable_reference.go
Christian Mesh f6a1d8e2dd
Support References in Variable Validation (#2216)
Signed-off-by: Christian Mesh <christianmesh1@gmail.com>
2024-12-04 14:24:44 -05:00

174 lines
5.1 KiB
Go

// Copyright (c) The OpenTofu Authors
// SPDX-License-Identifier: MPL-2.0
// Copyright (c) 2023 HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package tofu
import (
"fmt"
"log"
"github.com/hashicorp/hcl/v2"
"github.com/opentofu/opentofu/internal/addrs"
"github.com/opentofu/opentofu/internal/configs"
"github.com/opentofu/opentofu/internal/dag"
"github.com/opentofu/opentofu/internal/lang"
"github.com/opentofu/opentofu/internal/tfdiags"
)
// nodeVariableReference is the placeholder for an variable reference that has not yet had
// its module path expanded. It is a dependency on the evaluation (in a different scope) of
// the nodes which provide the actual variable value to the evaluation context. This split
// allows the evaluation and validation of the variable in the two different scopes required.
type nodeVariableReference struct {
Addr addrs.InputVariable
Module addrs.Module
Config *configs.Variable
Expr hcl.Expression // Used for diagnostics only
}
var (
_ GraphNodeDynamicExpandable = (*nodeVariableReference)(nil)
_ GraphNodeReferenceable = (*nodeVariableReference)(nil)
_ GraphNodeReferencer = (*nodeVariableReference)(nil)
_ graphNodeExpandsInstances = (*nodeVariableReference)(nil)
_ graphNodeTemporaryValue = (*nodeVariableReference)(nil)
)
// graphNodeExpandsInstances
func (n *nodeVariableReference) expandsInstances() {}
// Abuse graphNodeTemporaryValue to keep the validation rule around
func (n *nodeVariableReference) temporaryValue() bool {
return len(n.Config.Validations) == 0
}
// GraphNodeDynamicExpandable
func (n *nodeVariableReference) DynamicExpand(ctx EvalContext) (*Graph, error) {
var g Graph
// If this variable has preconditions, we need to report these checks now.
//
// We should only do this during planning as the apply phase starts with
// all the same checkable objects that were registered during the plan.
var checkableAddrs addrs.Set[addrs.Checkable]
if checkState := ctx.Checks(); checkState.ConfigHasChecks(n.Addr.InModule(n.Module)) {
checkableAddrs = addrs.MakeSet[addrs.Checkable]()
}
expander := ctx.InstanceExpander()
for _, module := range expander.ExpandModule(n.Module) {
addr := n.Addr.Absolute(module)
if checkableAddrs != nil {
checkableAddrs.Add(addr)
}
o := &nodeVariableReferenceInstance{
Addr: addr,
Config: n.Config,
Expr: n.Expr,
}
g.Add(o)
}
addRootNodeToGraph(&g)
if checkableAddrs != nil {
ctx.Checks().ReportCheckableObjects(n.Addr.InModule(n.Module), checkableAddrs)
}
return &g, nil
}
func (n *nodeVariableReference) Name() string {
addrStr := n.Addr.String()
if len(n.Module) != 0 {
addrStr = n.Module.String() + "." + addrStr
}
return fmt.Sprintf("%s (expand, reference)", addrStr)
}
// GraphNodeModulePath
func (n *nodeVariableReference) ModulePath() addrs.Module {
return n.Module
}
// GraphNodeReferencer
func (n *nodeVariableReference) References() []*addrs.Reference {
var refs []*addrs.Reference
if n.Config != nil {
for _, validation := range n.Config.Validations {
condFuncs, _ := lang.ReferencesInExpr(addrs.ParseRef, validation.Condition)
refs = append(refs, condFuncs...)
errFuncs, _ := lang.ReferencesInExpr(addrs.ParseRef, validation.ErrorMessage)
refs = append(refs, errFuncs...)
}
}
return refs
}
// GraphNodeReferenceable
func (n *nodeVariableReference) ReferenceableAddrs() []addrs.Referenceable {
return []addrs.Referenceable{n.Addr}
}
// nodeVariableReferenceInstance represents a module variable reference during
// the apply step.
type nodeVariableReferenceInstance struct {
Addr addrs.AbsInputVariableInstance
Config *configs.Variable // Config is the var in the config
Expr hcl.Expression // Used for diagnostics only
}
// Ensure that we are implementing all of the interfaces we think we are
// implementing.
var (
_ GraphNodeModuleInstance = (*nodeVariableReferenceInstance)(nil)
_ GraphNodeExecutable = (*nodeVariableReferenceInstance)(nil)
_ dag.GraphNodeDotter = (*nodeVariableReferenceInstance)(nil)
)
func (n *nodeVariableReferenceInstance) Name() string {
return n.Addr.String() + " (reference)"
}
// GraphNodeModuleInstance
func (n *nodeVariableReferenceInstance) Path() addrs.ModuleInstance {
return n.Addr.Module
}
// GraphNodeModulePath
func (n *nodeVariableReferenceInstance) ModulePath() addrs.Module {
return n.Addr.Module.Module()
}
// GraphNodeExecutable
func (n *nodeVariableReferenceInstance) Execute(ctx EvalContext, op walkOperation) tfdiags.Diagnostics {
log.Printf("[TRACE] nodeVariableReferenceInstance: evaluating %s", n.Addr)
diags := evalVariableValidations(n.Addr, n.Config, n.Expr, ctx)
if op == walkValidate {
var filtered tfdiags.Diagnostics
// Validate may contain unknown values, we can ignore that until plan/apply
for _, diag := range diags {
if !tfdiags.DiagnosticCausedByUnknown(diag) {
filtered = append(filtered, diag)
}
}
return filtered
}
return diags
}
// dag.GraphNodeDotter impl.
func (n *nodeVariableReferenceInstance) DotNode(name string, _ *dag.DotOpts) *dag.DotNode {
return &dag.DotNode{
Name: name,
Attrs: map[string]string{
"label": n.Name(),
"shape": "note",
},
}
}