github.com/graywolf-at-work-2/terraform-vendor@v1.4.5/internal/configs/checks.go (about)

     1  package configs
     2  
     3  import (
     4  	"fmt"
     5  
     6  	"github.com/hashicorp/hcl/v2"
     7  	"github.com/hashicorp/terraform/internal/addrs"
     8  	"github.com/hashicorp/terraform/internal/lang"
     9  )
    10  
    11  // CheckRule represents a configuration-defined validation rule, precondition,
    12  // or postcondition. Blocks of this sort can appear in a few different places
    13  // in configuration, including "validation" blocks for variables,
    14  // and "precondition" and "postcondition" blocks for resources.
    15  type CheckRule struct {
    16  	// Condition is an expression that must evaluate to true if the condition
    17  	// holds or false if it does not. If the expression produces an error then
    18  	// that's considered to be a bug in the module defining the check.
    19  	//
    20  	// The available variables in a condition expression vary depending on what
    21  	// a check is attached to. For example, validation rules attached to
    22  	// input variables can only refer to the variable that is being validated.
    23  	Condition hcl.Expression
    24  
    25  	// ErrorMessage should be one or more full sentences, which should be in
    26  	// English for consistency with the rest of the error message output but
    27  	// can in practice be in any language. The message should describe what is
    28  	// required for the condition to return true in a way that would make sense
    29  	// to a caller of the module.
    30  	//
    31  	// The error message expression has the same variables available for
    32  	// interpolation as the corresponding condition.
    33  	ErrorMessage hcl.Expression
    34  
    35  	DeclRange hcl.Range
    36  }
    37  
    38  // validateSelfReferences looks for references in the check rule matching the
    39  // specified resource address, returning error diagnostics if such a reference
    40  // is found.
    41  func (cr *CheckRule) validateSelfReferences(checkType string, addr addrs.Resource) hcl.Diagnostics {
    42  	var diags hcl.Diagnostics
    43  	exprs := []hcl.Expression{
    44  		cr.Condition,
    45  		cr.ErrorMessage,
    46  	}
    47  	for _, expr := range exprs {
    48  		if expr == nil {
    49  			continue
    50  		}
    51  		refs, _ := lang.References(expr.Variables())
    52  		for _, ref := range refs {
    53  			var refAddr addrs.Resource
    54  
    55  			switch rs := ref.Subject.(type) {
    56  			case addrs.Resource:
    57  				refAddr = rs
    58  			case addrs.ResourceInstance:
    59  				refAddr = rs.Resource
    60  			default:
    61  				continue
    62  			}
    63  
    64  			if refAddr.Equal(addr) {
    65  				diags = diags.Append(&hcl.Diagnostic{
    66  					Severity: hcl.DiagError,
    67  					Summary:  fmt.Sprintf("Invalid reference in %s", checkType),
    68  					Detail:   fmt.Sprintf("Configuration for %s may not refer to itself.", addr.String()),
    69  					Subject:  expr.Range().Ptr(),
    70  				})
    71  				break
    72  			}
    73  		}
    74  	}
    75  	return diags
    76  }
    77  
    78  // decodeCheckRuleBlock decodes the contents of the given block as a check rule.
    79  //
    80  // Unlike most of our "decode..." functions, this one can be applied to blocks
    81  // of various types as long as their body structures are "check-shaped". The
    82  // function takes the containing block only because some error messages will
    83  // refer to its location, and the returned object's DeclRange will be the
    84  // block's header.
    85  func decodeCheckRuleBlock(block *hcl.Block, override bool) (*CheckRule, hcl.Diagnostics) {
    86  	var diags hcl.Diagnostics
    87  	cr := &CheckRule{
    88  		DeclRange: block.DefRange,
    89  	}
    90  
    91  	if override {
    92  		// For now we'll just forbid overriding check blocks, to simplify
    93  		// the initial design. If we can find a clear use-case for overriding
    94  		// checks in override files and there's a way to define it that
    95  		// isn't confusing then we could relax this.
    96  		diags = diags.Append(&hcl.Diagnostic{
    97  			Severity: hcl.DiagError,
    98  			Summary:  fmt.Sprintf("Can't override %s blocks", block.Type),
    99  			Detail:   fmt.Sprintf("Override files cannot override %q blocks.", block.Type),
   100  			Subject:  cr.DeclRange.Ptr(),
   101  		})
   102  		return cr, diags
   103  	}
   104  
   105  	content, moreDiags := block.Body.Content(checkRuleBlockSchema)
   106  	diags = append(diags, moreDiags...)
   107  
   108  	if attr, exists := content.Attributes["condition"]; exists {
   109  		cr.Condition = attr.Expr
   110  
   111  		if len(cr.Condition.Variables()) == 0 {
   112  			// A condition expression that doesn't refer to any variable is
   113  			// pointless, because its result would always be a constant.
   114  			diags = diags.Append(&hcl.Diagnostic{
   115  				Severity: hcl.DiagError,
   116  				Summary:  fmt.Sprintf("Invalid %s expression", block.Type),
   117  				Detail:   "The condition expression must refer to at least one object from elsewhere in the configuration, or else its result would not be checking anything.",
   118  				Subject:  cr.Condition.Range().Ptr(),
   119  			})
   120  		}
   121  	}
   122  
   123  	if attr, exists := content.Attributes["error_message"]; exists {
   124  		cr.ErrorMessage = attr.Expr
   125  	}
   126  
   127  	return cr, diags
   128  }
   129  
   130  var checkRuleBlockSchema = &hcl.BodySchema{
   131  	Attributes: []hcl.AttributeSchema{
   132  		{
   133  			Name:     "condition",
   134  			Required: true,
   135  		},
   136  		{
   137  			Name:     "error_message",
   138  			Required: true,
   139  		},
   140  	},
   141  }