Fix function refs in variable validation (#2052)

Signed-off-by: Christian Mesh <christianmesh1@gmail.com>
This commit is contained in:
Christian Mesh 2024-10-17 12:37:54 -04:00 committed by GitHub
parent d36220e44c
commit 7cacb9f066
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 102 additions and 20 deletions

View File

@ -41,6 +41,11 @@ issues:
linters:
- funlen
- dupl
- revive
- path: (.+)_test.go
text: "ST1003"
- path: (.+)_test.go
text: "var-naming: don't use underscores in Go names"
linters:
disable-all: true

View File

@ -2487,3 +2487,84 @@ locals {
t.Fatalf("expected deprecated warning, got: %q\n", warn)
}
}
func TextContext2Validate_providerFunctions(t *testing.T) {
p := testProvider("aws")
p.GetProviderSchemaResponse = &providers.GetProviderSchemaResponse{
Functions: map[string]providers.FunctionSpec{
"arn_parse": providers.FunctionSpec{
Parameters: []providers.FunctionParameterSpec{{
Name: "arn",
Type: cty.String,
}},
Return: cty.Bool,
},
},
}
p.CallFunctionResponse = &providers.CallFunctionResponse{
Result: cty.True,
}
m := testModuleInline(t, map[string]string{
"main.tf": `
terraform {
required_providers {
aws = ">=5.70.0"
}
}
provider "aws" {
region="us-east-1"
}
module "mod" {
source = "./mod"
providers = {
aws = aws
}
}
`,
"mod/mod.tf": `
terraform {
required_providers {
aws = ">=5.70.0"
}
}
variable "obfmod" {
type = object({
arns = optional(list(string))
})
description = "Configuration for xxx."
validation {
condition = alltrue([
for arn in var.obfmod.arns: can(provider::aws::arn_parse(arn))
])
error_message = "All arns MUST BE a valid AWS ARN format."
}
default = {
arns = [
"arn:partition:service:region:account-id:resource-id",
]
}
}
`,
})
ctx := testContext2(t, &ContextOpts{
Providers: map[addrs.Provider]providers.Factory{
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
},
})
diags := ctx.Validate(m)
warn := diags.ErrWithWarnings().Error()
if !strings.Contains(warn, `The attribute "foo" is deprecated`) {
t.Fatalf("expected deprecated warning, got: %q\n", warn)
}
if !p.CallFunctionCalled {
t.Fatalf("Expected function call")
}
}

View File

@ -91,16 +91,27 @@ func (n *nodeExpandModuleVariable) ModulePath() addrs.Module {
// GraphNodeReferencer
func (n *nodeExpandModuleVariable) References() []*addrs.Reference {
var refs []*addrs.Reference
if n.Config != nil {
// These references will ignore GraphNodeReferenceOutside and are used by the ProviderFunctionTransformer and lang.Scope.evalContext
// It's an odd pattern, but it works
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...)
}
}
// If we have no value expression, we cannot depend on anything.
if n.Expr == nil {
return nil
return refs
}
// Variables in the root don't depend on anything, because their values
// are gathered prior to the graph walk and recorded in the context.
if len(n.Module) == 0 {
return nil
return refs
}
// Otherwise, we depend on anything referenced by our value expression.
@ -113,7 +124,9 @@ func (n *nodeExpandModuleVariable) References() []*addrs.Reference {
// where our associated variable was declared, which is correct because
// our value expression is assigned within a "module" block in the parent
// module.
refs, _ := lang.ReferencesInExpr(addrs.ParseRef, n.Expr)
outerRefs, _ := lang.ReferencesInExpr(addrs.ParseRef, n.Expr)
refs = append(refs, outerRefs...)
return refs
}
@ -167,23 +180,6 @@ 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)