github.com/muratcelep/terraform@v1.1.0-beta2-not-internal-4/not-internal/terraform/eval_variable.go (about) 1 package terraform 2 3 import ( 4 "fmt" 5 "log" 6 7 "github.com/hashicorp/hcl/v2" 8 "github.com/muratcelep/terraform/not-internal/addrs" 9 "github.com/muratcelep/terraform/not-internal/configs" 10 "github.com/muratcelep/terraform/not-internal/tfdiags" 11 "github.com/zclconf/go-cty/cty" 12 "github.com/zclconf/go-cty/cty/convert" 13 ) 14 15 // evalVariableValidations ensures that all of the configured custom validations 16 // for a variable are passing. 17 // 18 // This must be used only after any side-effects that make the value of the 19 // variable available for use in expression evaluation, such as 20 // EvalModuleCallArgument for variables in descendent modules. 21 func evalVariableValidations(addr addrs.AbsInputVariableInstance, config *configs.Variable, expr hcl.Expression, ctx EvalContext) (diags tfdiags.Diagnostics) { 22 if config == nil || len(config.Validations) == 0 { 23 log.Printf("[TRACE] evalVariableValidations: not active for %s, so skipping", addr) 24 return nil 25 } 26 27 // Variable nodes evaluate in the parent module to where they were declared 28 // because the value expression (n.Expr, if set) comes from the calling 29 // "module" block in the parent module. 30 // 31 // Validation expressions are statically validated (during configuration 32 // loading) to refer only to the variable being validated, so we can 33 // bypass our usual evaluation machinery here and just produce a minimal 34 // evaluation context containing just the required value, and thus avoid 35 // the problem that ctx's evaluation functions refer to the wrong module. 36 val := ctx.GetVariableValue(addr) 37 hclCtx := &hcl.EvalContext{ 38 Variables: map[string]cty.Value{ 39 "var": cty.ObjectVal(map[string]cty.Value{ 40 config.Name: val, 41 }), 42 }, 43 Functions: ctx.EvaluationScope(nil, EvalDataForNoInstanceKey).Functions(), 44 } 45 46 for _, validation := range config.Validations { 47 const errInvalidCondition = "Invalid variable validation result" 48 const errInvalidValue = "Invalid value for variable" 49 50 result, moreDiags := validation.Condition.Value(hclCtx) 51 diags = diags.Append(moreDiags) 52 if moreDiags.HasErrors() { 53 log.Printf("[TRACE] evalVariableValidations: %s rule %s condition expression failed: %s", addr, validation.DeclRange, diags.Err().Error()) 54 } 55 if !result.IsKnown() { 56 log.Printf("[TRACE] evalVariableValidations: %s rule %s condition value is unknown, so skipping validation for now", addr, validation.DeclRange) 57 continue // We'll wait until we've learned more, then. 58 } 59 if result.IsNull() { 60 diags = diags.Append(&hcl.Diagnostic{ 61 Severity: hcl.DiagError, 62 Summary: errInvalidCondition, 63 Detail: "Validation condition expression must return either true or false, not null.", 64 Subject: validation.Condition.Range().Ptr(), 65 Expression: validation.Condition, 66 EvalContext: hclCtx, 67 }) 68 continue 69 } 70 var err error 71 result, err = convert.Convert(result, cty.Bool) 72 if err != nil { 73 diags = diags.Append(&hcl.Diagnostic{ 74 Severity: hcl.DiagError, 75 Summary: errInvalidCondition, 76 Detail: fmt.Sprintf("Invalid validation condition result value: %s.", tfdiags.FormatError(err)), 77 Subject: validation.Condition.Range().Ptr(), 78 Expression: validation.Condition, 79 EvalContext: hclCtx, 80 }) 81 continue 82 } 83 84 // Validation condition may be marked if the input variable is bound to 85 // a sensitive value. This is irrelevant to the validation process, so 86 // we discard the marks now. 87 result, _ = result.Unmark() 88 89 if result.False() { 90 if expr != nil { 91 diags = diags.Append(&hcl.Diagnostic{ 92 Severity: hcl.DiagError, 93 Summary: errInvalidValue, 94 Detail: fmt.Sprintf("%s\n\nThis was checked by the validation rule at %s.", validation.ErrorMessage, validation.DeclRange.String()), 95 Subject: expr.Range().Ptr(), 96 }) 97 } else { 98 // Since we don't have a source expression for a root module 99 // variable, we'll just report the error from the perspective 100 // of the variable declaration itself. 101 diags = diags.Append(&hcl.Diagnostic{ 102 Severity: hcl.DiagError, 103 Summary: errInvalidValue, 104 Detail: fmt.Sprintf("%s\n\nThis was checked by the validation rule at %s.", validation.ErrorMessage, validation.DeclRange.String()), 105 Subject: config.DeclRange.Ptr(), 106 }) 107 } 108 } 109 } 110 111 return diags 112 }