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  }