mirror of
https://github.com/opentofu/opentofu.git
synced 2025-02-25 18:45:20 -06:00
Provider functions in variable validations (#1689)
Signed-off-by: Christian Mesh <christianmesh1@gmail.com>
This commit is contained in:
parent
cc8d6e07f4
commit
882b942575
@ -17,6 +17,7 @@ BUG FIXES:
|
||||
* Fixed support for provider functions in tests ([#1603](https://github.com/opentofu/opentofu/pull/1603))
|
||||
* Added a better error message on `for_each` block with sensitive value of unsuitable type. ([#1485](https://github.com/opentofu/opentofu/pull/1485))
|
||||
* Fix race condition on locking in gcs backend ([#1342](https://github.com/opentofu/opentofu/pull/1342))
|
||||
* Fix bug where provider functions were unusable in variables and outputs ([#1689](https://github.com/opentofu/opentofu/pull/1689))
|
||||
|
||||
## Previous Releases
|
||||
|
||||
|
@ -7,6 +7,15 @@ terraform {
|
||||
}
|
||||
}
|
||||
|
||||
variable "number" {
|
||||
type = number
|
||||
default = 1
|
||||
validation {
|
||||
condition = provider::example::echo(var.number) > 0
|
||||
error_message = "number must be > ${provider::example::echo(0)}"
|
||||
}
|
||||
}
|
||||
|
||||
output "dummy" {
|
||||
value = provider::example::echo("Hello Functions")
|
||||
}
|
||||
|
@ -15,6 +15,7 @@ import (
|
||||
"github.com/hashicorp/hcl/v2/hclsyntax"
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
"github.com/zclconf/go-cty/cty/convert"
|
||||
"github.com/zclconf/go-cty/cty/function"
|
||||
|
||||
"github.com/opentofu/opentofu/internal/addrs"
|
||||
"github.com/opentofu/opentofu/internal/configs/configschema"
|
||||
@ -147,6 +148,7 @@ func (s *Scope) EvalSelfBlock(body hcl.Body, self cty.Value, schema *configschem
|
||||
|
||||
ctx := &hcl.EvalContext{
|
||||
Variables: vals,
|
||||
// TODO consider if any provider functions make sense here
|
||||
Functions: s.Functions(),
|
||||
}
|
||||
|
||||
@ -297,11 +299,14 @@ func (s *Scope) evalContext(refs []*addrs.Reference, selfAddr addrs.Referenceabl
|
||||
|
||||
var diags tfdiags.Diagnostics
|
||||
vals := make(map[string]cty.Value)
|
||||
funcs := s.Functions()
|
||||
funcs := make(map[string]function.Function)
|
||||
ctx := &hcl.EvalContext{
|
||||
Variables: vals,
|
||||
Functions: funcs,
|
||||
}
|
||||
for name, fn := range s.Functions() {
|
||||
funcs[name] = fn
|
||||
}
|
||||
|
||||
if len(refs) == 0 {
|
||||
// Easy path for common case where there are no references at all.
|
||||
|
@ -92,6 +92,20 @@ func ReferencesInExpr(parseRef ParseRef, expr hcl.Expression) ([]*addrs.Referenc
|
||||
return References(parseRef, traversals)
|
||||
}
|
||||
|
||||
// ProviderFunctionsInExpr is a helper wrapper around References that searches for provider
|
||||
// function traversals in an ExpressionWithFunctions, then converts the traversals into
|
||||
// references
|
||||
func ProviderFunctionsInExpr(parseRef ParseRef, expr hcl.Expression) ([]*addrs.Reference, tfdiags.Diagnostics) {
|
||||
if expr == nil {
|
||||
return nil, nil
|
||||
}
|
||||
if fexpr, ok := expr.(hcl.ExpressionWithFunctions); ok {
|
||||
funcs := filterProviderFunctions(fexpr.Functions())
|
||||
return References(parseRef, funcs)
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func filterProviderFunctions(funcs []hcl.Traversal) []hcl.Traversal {
|
||||
pfuncs := make([]hcl.Traversal, 0, len(funcs))
|
||||
for _, fn := range funcs {
|
||||
|
@ -18,6 +18,7 @@ import (
|
||||
"github.com/opentofu/opentofu/internal/addrs"
|
||||
"github.com/opentofu/opentofu/internal/checks"
|
||||
"github.com/opentofu/opentofu/internal/configs"
|
||||
"github.com/opentofu/opentofu/internal/lang"
|
||||
"github.com/opentofu/opentofu/internal/lang/marks"
|
||||
"github.com/opentofu/opentofu/internal/tfdiags"
|
||||
)
|
||||
@ -234,16 +235,25 @@ func evalVariableValidations(addr addrs.AbsInputVariableInstance, config *config
|
||||
})
|
||||
return diags
|
||||
}
|
||||
hclCtx := &hcl.EvalContext{
|
||||
Variables: map[string]cty.Value{
|
||||
"var": cty.ObjectVal(map[string]cty.Value{
|
||||
config.Name: val,
|
||||
}),
|
||||
},
|
||||
Functions: ctx.EvaluationScope(nil, nil, EvalDataForNoInstanceKey).Functions(),
|
||||
}
|
||||
|
||||
for ix, validation := range config.Validations {
|
||||
condFuncs, condDiags := lang.ProviderFunctionsInExpr(addrs.ParseRef, validation.Condition)
|
||||
diags = diags.Append(condDiags)
|
||||
errFuncs, errDiags := lang.ProviderFunctionsInExpr(addrs.ParseRef, validation.ErrorMessage)
|
||||
diags = diags.Append(errDiags)
|
||||
|
||||
if diags.HasErrors() {
|
||||
continue
|
||||
}
|
||||
|
||||
hclCtx, ctxDiags := ctx.EvaluationScope(nil, nil, EvalDataForNoInstanceKey).EvalContext(append(condFuncs, errFuncs...))
|
||||
diags = diags.Append(ctxDiags)
|
||||
if diags.HasErrors() {
|
||||
continue
|
||||
}
|
||||
hclCtx.Variables["var"] = cty.ObjectVal(map[string]cty.Value{
|
||||
config.Name: val,
|
||||
})
|
||||
|
||||
result, ruleDiags := evalVariableValidation(validation, hclCtx, addr, config, expr, ix)
|
||||
diags = diags.Append(ruleDiags)
|
||||
|
||||
|
@ -167,6 +167,23 @@ func (n *nodeModuleVariable) ModulePath() addrs.Module {
|
||||
return n.Addr.Module.Module()
|
||||
}
|
||||
|
||||
// GraphNodeReferencer
|
||||
func (n *nodeModuleVariable) References() []*addrs.Reference {
|
||||
// This is identical to NodeRootVariable.References
|
||||
var refs []*addrs.Reference
|
||||
|
||||
if n.Config != nil {
|
||||
for _, validation := range n.Config.Validations {
|
||||
condFuncs, _ := lang.ProviderFunctionsInExpr(addrs.ParseRef, validation.Condition)
|
||||
refs = append(refs, condFuncs...)
|
||||
errFuncs, _ := lang.ProviderFunctionsInExpr(addrs.ParseRef, validation.ErrorMessage)
|
||||
refs = append(refs, errFuncs...)
|
||||
}
|
||||
}
|
||||
|
||||
return refs
|
||||
}
|
||||
|
||||
// GraphNodeExecutable
|
||||
func (n *nodeModuleVariable) Execute(ctx EvalContext, op walkOperation) (diags tfdiags.Diagnostics) {
|
||||
log.Printf("[TRACE] nodeModuleVariable: evaluating %s", n.Addr)
|
||||
|
@ -13,6 +13,7 @@ import (
|
||||
"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"
|
||||
)
|
||||
|
||||
@ -52,6 +53,23 @@ func (n *NodeRootVariable) ReferenceableAddrs() []addrs.Referenceable {
|
||||
return []addrs.Referenceable{n.Addr}
|
||||
}
|
||||
|
||||
// GraphNodeReferencer
|
||||
func (n *NodeRootVariable) References() []*addrs.Reference {
|
||||
// This is identical to nodeModuleVariable.References
|
||||
var refs []*addrs.Reference
|
||||
|
||||
if n.Config != nil {
|
||||
for _, validation := range n.Config.Validations {
|
||||
condFuncs, _ := lang.ProviderFunctionsInExpr(addrs.ParseRef, validation.Condition)
|
||||
refs = append(refs, condFuncs...)
|
||||
errFuncs, _ := lang.ProviderFunctionsInExpr(addrs.ParseRef, validation.ErrorMessage)
|
||||
refs = append(refs, errFuncs...)
|
||||
}
|
||||
}
|
||||
|
||||
return refs
|
||||
}
|
||||
|
||||
// GraphNodeExecutable
|
||||
func (n *NodeRootVariable) Execute(ctx EvalContext, op walkOperation) tfdiags.Diagnostics {
|
||||
// Root module variables are special in that they are provided directly
|
||||
|
@ -359,11 +359,15 @@ func (t *pruneUnusedNodesTransformer) Transform(g *Graph) error {
|
||||
// earlier, however there may be more to prune now based on
|
||||
// targeting or a destroy with no related instances in the
|
||||
// state.
|
||||
// TODO: consider replacing this with an actual "references" check instead of the simple type check below.
|
||||
// Due to provider functions, many provider references through GraphNodeReferencer still are required.
|
||||
des, _ := g.Descendents(n)
|
||||
for _, v := range des {
|
||||
switch v.(type) {
|
||||
case GraphNodeProviderConsumer:
|
||||
return
|
||||
case GraphNodeReferencer:
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user