113 lines
4.2 KiB
Go
113 lines
4.2 KiB
Go
package terraform
|
|
|
|
import (
|
|
"fmt"
|
|
"log"
|
|
|
|
"github.com/hashicorp/hcl/v2"
|
|
"github.com/hashicorp/terraform/internal/addrs"
|
|
"github.com/hashicorp/terraform/internal/configs"
|
|
"github.com/hashicorp/terraform/internal/tfdiags"
|
|
"github.com/zclconf/go-cty/cty"
|
|
"github.com/zclconf/go-cty/cty/convert"
|
|
)
|
|
|
|
// evalVariableValidations ensures that all of the configured custom validations
|
|
// for a variable are passing.
|
|
//
|
|
// This must be used only after any side-effects that make the value of the
|
|
// variable available for use in expression evaluation, such as
|
|
// EvalModuleCallArgument for variables in descendent modules.
|
|
func evalVariableValidations(addr addrs.AbsInputVariableInstance, config *configs.Variable, expr hcl.Expression, ctx EvalContext) (diags tfdiags.Diagnostics) {
|
|
if config == nil || len(config.Validations) == 0 {
|
|
log.Printf("[TRACE] evalVariableValidations: not active for %s, so skipping", addr)
|
|
return nil
|
|
}
|
|
|
|
// Variable nodes evaluate in the parent module to where they were declared
|
|
// because the value expression (n.Expr, if set) comes from the calling
|
|
// "module" block in the parent module.
|
|
//
|
|
// Validation expressions are statically validated (during configuration
|
|
// loading) to refer only to the variable being validated, so we can
|
|
// bypass our usual evaluation machinery here and just produce a minimal
|
|
// evaluation context containing just the required value, and thus avoid
|
|
// the problem that ctx's evaluation functions refer to the wrong module.
|
|
val := ctx.GetVariableValue(addr)
|
|
hclCtx := &hcl.EvalContext{
|
|
Variables: map[string]cty.Value{
|
|
"var": cty.ObjectVal(map[string]cty.Value{
|
|
config.Name: val,
|
|
}),
|
|
},
|
|
Functions: ctx.EvaluationScope(nil, EvalDataForNoInstanceKey).Functions(),
|
|
}
|
|
|
|
for _, validation := range config.Validations {
|
|
const errInvalidCondition = "Invalid variable validation result"
|
|
const errInvalidValue = "Invalid value for variable"
|
|
|
|
result, moreDiags := validation.Condition.Value(hclCtx)
|
|
diags = diags.Append(moreDiags)
|
|
if moreDiags.HasErrors() {
|
|
log.Printf("[TRACE] evalVariableValidations: %s rule %s condition expression failed: %s", addr, validation.DeclRange, diags.Err().Error())
|
|
}
|
|
if !result.IsKnown() {
|
|
log.Printf("[TRACE] evalVariableValidations: %s rule %s condition value is unknown, so skipping validation for now", addr, validation.DeclRange)
|
|
continue // We'll wait until we've learned more, then.
|
|
}
|
|
if result.IsNull() {
|
|
diags = diags.Append(&hcl.Diagnostic{
|
|
Severity: hcl.DiagError,
|
|
Summary: errInvalidCondition,
|
|
Detail: "Validation condition expression must return either true or false, not null.",
|
|
Subject: validation.Condition.Range().Ptr(),
|
|
Expression: validation.Condition,
|
|
EvalContext: hclCtx,
|
|
})
|
|
continue
|
|
}
|
|
var err error
|
|
result, err = convert.Convert(result, cty.Bool)
|
|
if err != nil {
|
|
diags = diags.Append(&hcl.Diagnostic{
|
|
Severity: hcl.DiagError,
|
|
Summary: errInvalidCondition,
|
|
Detail: fmt.Sprintf("Invalid validation condition result value: %s.", tfdiags.FormatError(err)),
|
|
Subject: validation.Condition.Range().Ptr(),
|
|
Expression: validation.Condition,
|
|
EvalContext: hclCtx,
|
|
})
|
|
continue
|
|
}
|
|
|
|
// Validation condition may be marked if the input variable is bound to
|
|
// a sensitive value. This is irrelevant to the validation process, so
|
|
// we discard the marks now.
|
|
result, _ = result.Unmark()
|
|
|
|
if result.False() {
|
|
if expr != nil {
|
|
diags = diags.Append(&hcl.Diagnostic{
|
|
Severity: hcl.DiagError,
|
|
Summary: errInvalidValue,
|
|
Detail: fmt.Sprintf("%s\n\nThis was checked by the validation rule at %s.", validation.ErrorMessage, validation.DeclRange.String()),
|
|
Subject: expr.Range().Ptr(),
|
|
})
|
|
} else {
|
|
// Since we don't have a source expression for a root module
|
|
// variable, we'll just report the error from the perspective
|
|
// of the variable declaration itself.
|
|
diags = diags.Append(&hcl.Diagnostic{
|
|
Severity: hcl.DiagError,
|
|
Summary: errInvalidValue,
|
|
Detail: fmt.Sprintf("%s\n\nThis was checked by the validation rule at %s.", validation.ErrorMessage, validation.DeclRange.String()),
|
|
Subject: config.DeclRange.Ptr(),
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
return diags
|
|
}
|