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))
|
* 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))
|
* 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 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
|
## 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" {
|
output "dummy" {
|
||||||
value = provider::example::echo("Hello Functions")
|
value = provider::example::echo("Hello Functions")
|
||||||
}
|
}
|
||||||
|
@ -15,6 +15,7 @@ import (
|
|||||||
"github.com/hashicorp/hcl/v2/hclsyntax"
|
"github.com/hashicorp/hcl/v2/hclsyntax"
|
||||||
"github.com/zclconf/go-cty/cty"
|
"github.com/zclconf/go-cty/cty"
|
||||||
"github.com/zclconf/go-cty/cty/convert"
|
"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/addrs"
|
||||||
"github.com/opentofu/opentofu/internal/configs/configschema"
|
"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{
|
ctx := &hcl.EvalContext{
|
||||||
Variables: vals,
|
Variables: vals,
|
||||||
|
// TODO consider if any provider functions make sense here
|
||||||
Functions: s.Functions(),
|
Functions: s.Functions(),
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -297,11 +299,14 @@ func (s *Scope) evalContext(refs []*addrs.Reference, selfAddr addrs.Referenceabl
|
|||||||
|
|
||||||
var diags tfdiags.Diagnostics
|
var diags tfdiags.Diagnostics
|
||||||
vals := make(map[string]cty.Value)
|
vals := make(map[string]cty.Value)
|
||||||
funcs := s.Functions()
|
funcs := make(map[string]function.Function)
|
||||||
ctx := &hcl.EvalContext{
|
ctx := &hcl.EvalContext{
|
||||||
Variables: vals,
|
Variables: vals,
|
||||||
Functions: funcs,
|
Functions: funcs,
|
||||||
}
|
}
|
||||||
|
for name, fn := range s.Functions() {
|
||||||
|
funcs[name] = fn
|
||||||
|
}
|
||||||
|
|
||||||
if len(refs) == 0 {
|
if len(refs) == 0 {
|
||||||
// Easy path for common case where there are no references at all.
|
// 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)
|
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 {
|
func filterProviderFunctions(funcs []hcl.Traversal) []hcl.Traversal {
|
||||||
pfuncs := make([]hcl.Traversal, 0, len(funcs))
|
pfuncs := make([]hcl.Traversal, 0, len(funcs))
|
||||||
for _, fn := range funcs {
|
for _, fn := range funcs {
|
||||||
|
@ -18,6 +18,7 @@ import (
|
|||||||
"github.com/opentofu/opentofu/internal/addrs"
|
"github.com/opentofu/opentofu/internal/addrs"
|
||||||
"github.com/opentofu/opentofu/internal/checks"
|
"github.com/opentofu/opentofu/internal/checks"
|
||||||
"github.com/opentofu/opentofu/internal/configs"
|
"github.com/opentofu/opentofu/internal/configs"
|
||||||
|
"github.com/opentofu/opentofu/internal/lang"
|
||||||
"github.com/opentofu/opentofu/internal/lang/marks"
|
"github.com/opentofu/opentofu/internal/lang/marks"
|
||||||
"github.com/opentofu/opentofu/internal/tfdiags"
|
"github.com/opentofu/opentofu/internal/tfdiags"
|
||||||
)
|
)
|
||||||
@ -234,16 +235,25 @@ func evalVariableValidations(addr addrs.AbsInputVariableInstance, config *config
|
|||||||
})
|
})
|
||||||
return diags
|
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 {
|
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)
|
result, ruleDiags := evalVariableValidation(validation, hclCtx, addr, config, expr, ix)
|
||||||
diags = diags.Append(ruleDiags)
|
diags = diags.Append(ruleDiags)
|
||||||
|
|
||||||
|
@ -167,6 +167,23 @@ func (n *nodeModuleVariable) ModulePath() addrs.Module {
|
|||||||
return n.Addr.Module.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
|
// GraphNodeExecutable
|
||||||
func (n *nodeModuleVariable) Execute(ctx EvalContext, op walkOperation) (diags tfdiags.Diagnostics) {
|
func (n *nodeModuleVariable) Execute(ctx EvalContext, op walkOperation) (diags tfdiags.Diagnostics) {
|
||||||
log.Printf("[TRACE] nodeModuleVariable: evaluating %s", n.Addr)
|
log.Printf("[TRACE] nodeModuleVariable: evaluating %s", n.Addr)
|
||||||
|
@ -13,6 +13,7 @@ import (
|
|||||||
"github.com/opentofu/opentofu/internal/addrs"
|
"github.com/opentofu/opentofu/internal/addrs"
|
||||||
"github.com/opentofu/opentofu/internal/configs"
|
"github.com/opentofu/opentofu/internal/configs"
|
||||||
"github.com/opentofu/opentofu/internal/dag"
|
"github.com/opentofu/opentofu/internal/dag"
|
||||||
|
"github.com/opentofu/opentofu/internal/lang"
|
||||||
"github.com/opentofu/opentofu/internal/tfdiags"
|
"github.com/opentofu/opentofu/internal/tfdiags"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -52,6 +53,23 @@ func (n *NodeRootVariable) ReferenceableAddrs() []addrs.Referenceable {
|
|||||||
return []addrs.Referenceable{n.Addr}
|
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
|
// GraphNodeExecutable
|
||||||
func (n *NodeRootVariable) Execute(ctx EvalContext, op walkOperation) tfdiags.Diagnostics {
|
func (n *NodeRootVariable) Execute(ctx EvalContext, op walkOperation) tfdiags.Diagnostics {
|
||||||
// Root module variables are special in that they are provided directly
|
// 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
|
// earlier, however there may be more to prune now based on
|
||||||
// targeting or a destroy with no related instances in the
|
// targeting or a destroy with no related instances in the
|
||||||
// state.
|
// 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)
|
des, _ := g.Descendents(n)
|
||||||
for _, v := range des {
|
for _, v := range des {
|
||||||
switch v.(type) {
|
switch v.(type) {
|
||||||
case GraphNodeProviderConsumer:
|
case GraphNodeProviderConsumer:
|
||||||
return
|
return
|
||||||
|
case GraphNodeReferencer:
|
||||||
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user