blob: 88d678394e7c77646d06c4f3aa4320fd11acb933 [file] [log] [blame]
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package terraform
import (
"fmt"
"slices"
"github.com/hashicorp/hcl/v2"
"github.com/hashicorp/terraform/internal/addrs"
"github.com/hashicorp/terraform/internal/configs"
"github.com/hashicorp/terraform/internal/lang/langrefs"
"github.com/hashicorp/terraform/internal/tfdiags"
)
// nodeVariableValidation checks the author-specified validation rules against
// the final value of all expanded instances of a given input variable.
//
// A node of this type should always depend on another node that's responsible
// for deciding the final values for the nominated variable and registering
// them in the current "named values" state. [variableValidationTransformer]
// is the one responsible for inserting nodes of this type and ensuring that
// they each depend on the node that will register the final variable value.
type nodeVariableValidation struct {
configAddr addrs.ConfigInputVariable
rules []*configs.CheckRule
// defnRange is whatever source range we consider to best represent
// the definition of the variable, which should ideally cover the
// source code of the expression that was assigned to the variable.
// When that's not possible -- for example, if the variable was
// set from a non-configuration location like an environment variable --
// it's acceptable to use the declaration location instead.
defnRange hcl.Range
// validateWalk is set to true during a validation walk, where any input
// variables are set to unknown values. Since we may have unknown values
// which will be known during plan, we need to be more lenient about what
// can be unknown in variable validation expressions.
validateWalk bool
}
var _ GraphNodeModulePath = (*nodeVariableValidation)(nil)
var _ GraphNodeReferenceable = (*nodeVariableValidation)(nil)
var _ GraphNodeReferencer = (*nodeVariableValidation)(nil)
var _ GraphNodeExecutable = (*nodeVariableValidation)(nil)
var _ graphNodeTemporaryValue = (*nodeVariableValidation)(nil)
func (n *nodeVariableValidation) Name() string {
return fmt.Sprintf("%s (validation)", n.configAddr.String())
}
// ModulePath implements [GraphNodeModulePath].
func (n *nodeVariableValidation) ModulePath() addrs.Module {
return n.configAddr.Module
}
// ReferenceableAddrs implements [GraphNodeReferenceable], announcing that
// this node contributes to the value for the input variable that it's
// validating, and must therefore run before any nodes that refer to it.
func (n *nodeVariableValidation) ReferenceableAddrs() []addrs.Referenceable {
return []addrs.Referenceable{n.configAddr.Variable}
}
// nodeVariableValidation must act as if it's part of the associated variable
// node, and that means mirroring all that node's graph behavior. Root module
// variable are not temporary however, but because during a destroy we can't
// ensure that all references can be evaluated, we must skip validation unless
// absolutely necessary to avoid blocking the destroy from proceeding.
func (n *nodeVariableValidation) temporaryValue() bool {
return true
}
// References implements [GraphNodeReferencer], announcing anything that
// the check rules refer to, other than the variable that's being validated
// (which gets its dependency connected by [variableValidationTransformer]
// instead).
func (n *nodeVariableValidation) References() []*addrs.Reference {
var ret []*addrs.Reference
for _, rule := range n.rules {
// We ignore all diagnostics here because if an expression contains
// invalid references then we'll catch them once we visit the
// node (method Execute).
condRefs, _ := langrefs.ReferencesInExpr(addrs.ParseRef, rule.Condition)
msgRefs, _ := langrefs.ReferencesInExpr(addrs.ParseRef, rule.ErrorMessage)
ret = n.appendRefsFilterSelf(ret, condRefs...)
ret = n.appendRefsFilterSelf(ret, msgRefs...)
}
return ret
}
// appendRefsFilterSelf is a specialized version of builtin [append] that
// ignores any new references to the input variable represented by the
// reciever.
func (n *nodeVariableValidation) appendRefsFilterSelf(to []*addrs.Reference, new ...*addrs.Reference) []*addrs.Reference {
// We need to filter out any self-references, because those would
// make the resulting graph invalid and we don't need them because
// variableValidationTransformer should've arranged for us to
// already depend on whatever node provides the final value for
// this variable.
ret := slices.Grow(to, len(new))
ourAddr := n.configAddr.Variable
for _, ref := range new {
if refAddr, ok := ref.Subject.(addrs.InputVariable); ok {
if refAddr == ourAddr {
continue
}
}
ret = append(ret, ref)
}
return ret
}
func (n *nodeVariableValidation) Execute(globalCtx EvalContext, op walkOperation) tfdiags.Diagnostics {
var diags tfdiags.Diagnostics
// We need to perform validation work separately for each instance of
// the variable across expanded modules, because each one could potentially
// have a different value assigned to it and other different data in scope.
expander := globalCtx.InstanceExpander()
for _, modInst := range expander.ExpandModule(n.configAddr.Module, false) {
addr := n.configAddr.Variable.Absolute(modInst)
moduleCtx := globalCtx.withScope(evalContextModuleInstance{Addr: addr.Module})
diags = diags.Append(evalVariableValidations(
addr,
moduleCtx,
n.rules,
n.defnRange,
n.validateWalk,
))
}
return diags
}