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

     1  package configschema
     2  
     3  import (
     4  	"fmt"
     5  	"sort"
     6  
     7  	"github.com/hashicorp/hcl/v2"
     8  	"github.com/hashicorp/hcl/v2/hclsyntax"
     9  	"github.com/zclconf/go-cty/cty"
    10  
    11  	"github.com/hashicorp/terraform/internal/didyoumean"
    12  	"github.com/hashicorp/terraform/internal/tfdiags"
    13  )
    14  
    15  // StaticValidateTraversal checks whether the given traversal (which must be
    16  // relative) refers to a construct in the receiving schema, returning error
    17  // diagnostics if any problems are found.
    18  //
    19  // This method is "optimistic" in that it will not return errors for possible
    20  // problems that cannot be detected statically. It is possible that a
    21  // traversal which passed static validation will still fail when evaluated.
    22  func (b *Block) StaticValidateTraversal(traversal hcl.Traversal) tfdiags.Diagnostics {
    23  	if !traversal.IsRelative() {
    24  		panic("StaticValidateTraversal on absolute traversal")
    25  	}
    26  	if len(traversal) == 0 {
    27  		return nil
    28  	}
    29  
    30  	var diags tfdiags.Diagnostics
    31  
    32  	next := traversal[0]
    33  	after := traversal[1:]
    34  
    35  	var name string
    36  	switch step := next.(type) {
    37  	case hcl.TraverseAttr:
    38  		name = step.Name
    39  	case hcl.TraverseIndex:
    40  		// No other traversal step types are allowed directly at a block.
    41  		// If it looks like the user was trying to use index syntax to
    42  		// access an attribute then we'll produce a specialized message.
    43  		key := step.Key
    44  		if key.Type() == cty.String && key.IsKnown() && !key.IsNull() {
    45  			maybeName := key.AsString()
    46  			if hclsyntax.ValidIdentifier(maybeName) {
    47  				diags = diags.Append(&hcl.Diagnostic{
    48  					Severity: hcl.DiagError,
    49  					Summary:  `Invalid index operation`,
    50  					Detail:   fmt.Sprintf(`Only attribute access is allowed here. Did you mean to access attribute %q using the dot operator?`, maybeName),
    51  					Subject:  &step.SrcRange,
    52  				})
    53  				return diags
    54  			}
    55  		}
    56  		// If it looks like some other kind of index then we'll use a generic error.
    57  		diags = diags.Append(&hcl.Diagnostic{
    58  			Severity: hcl.DiagError,
    59  			Summary:  `Invalid index operation`,
    60  			Detail:   `Only attribute access is allowed here, using the dot operator.`,
    61  			Subject:  &step.SrcRange,
    62  		})
    63  		return diags
    64  	default:
    65  		// No other traversal types should appear in a normal valid traversal,
    66  		// but we'll handle this with a generic error anyway to be robust.
    67  		diags = diags.Append(&hcl.Diagnostic{
    68  			Severity: hcl.DiagError,
    69  			Summary:  `Invalid operation`,
    70  			Detail:   `Only attribute access is allowed here, using the dot operator.`,
    71  			Subject:  next.SourceRange().Ptr(),
    72  		})
    73  		return diags
    74  	}
    75  
    76  	if attrS, exists := b.Attributes[name]; exists {
    77  		// Check for Deprecated status of this attribute.
    78  		// We currently can't provide the user with any useful guidance because
    79  		// the deprecation string is not part of the schema, but we can at
    80  		// least warn them.
    81  		//
    82  		// This purposely does not attempt to recurse into nested attribute
    83  		// types. Because nested attribute values are often not accessed via a
    84  		// direct traversal to the leaf attributes, we cannot reliably detect
    85  		// if a nested, deprecated attribute value is actually used from the
    86  		// traversal alone. More precise detection of deprecated attributes
    87  		// would require adding metadata like marks to the cty value itself, to
    88  		// be caught during evaluation.
    89  		if attrS.Deprecated {
    90  			diags = diags.Append(&hcl.Diagnostic{
    91  				Severity: hcl.DiagWarning,
    92  				Summary:  `Deprecated attribute`,
    93  				Detail:   fmt.Sprintf(`The attribute %q is deprecated. Refer to the provider documentation for details.`, name),
    94  				Subject:  next.SourceRange().Ptr(),
    95  			})
    96  		}
    97  
    98  		// For attribute validation we will just apply the rest of the
    99  		// traversal to an unknown value of the attribute type and pass
   100  		// through HCL's own errors, since we don't want to replicate all
   101  		// of HCL's type checking rules here.
   102  		val := cty.UnknownVal(attrS.ImpliedType())
   103  		_, hclDiags := after.TraverseRel(val)
   104  		return diags.Append(hclDiags)
   105  	}
   106  
   107  	if blockS, exists := b.BlockTypes[name]; exists {
   108  		moreDiags := blockS.staticValidateTraversal(name, after)
   109  		diags = diags.Append(moreDiags)
   110  		return diags
   111  	}
   112  
   113  	// If we get here then the name isn't valid at all. We'll collect up
   114  	// all of the names that _are_ valid to use as suggestions.
   115  	var suggestions []string
   116  	for name := range b.Attributes {
   117  		suggestions = append(suggestions, name)
   118  	}
   119  	for name := range b.BlockTypes {
   120  		suggestions = append(suggestions, name)
   121  	}
   122  	sort.Strings(suggestions)
   123  	suggestion := didyoumean.NameSuggestion(name, suggestions)
   124  	if suggestion != "" {
   125  		suggestion = fmt.Sprintf(" Did you mean %q?", suggestion)
   126  	}
   127  	diags = diags.Append(&hcl.Diagnostic{
   128  		Severity: hcl.DiagError,
   129  		Summary:  `Unsupported attribute`,
   130  		Detail:   fmt.Sprintf(`This object has no argument, nested block, or exported attribute named %q.%s`, name, suggestion),
   131  		Subject:  next.SourceRange().Ptr(),
   132  	})
   133  
   134  	return diags
   135  }
   136  
   137  func (b *NestedBlock) staticValidateTraversal(typeName string, traversal hcl.Traversal) tfdiags.Diagnostics {
   138  	if b.Nesting == NestingSingle || b.Nesting == NestingGroup {
   139  		// Single blocks are easy: just pass right through.
   140  		return b.Block.StaticValidateTraversal(traversal)
   141  	}
   142  
   143  	if len(traversal) == 0 {
   144  		// It's always valid to access a nested block's attribute directly.
   145  		return nil
   146  	}
   147  
   148  	var diags tfdiags.Diagnostics
   149  	next := traversal[0]
   150  	after := traversal[1:]
   151  
   152  	switch b.Nesting {
   153  
   154  	case NestingSet:
   155  		// Can't traverse into a set at all, since it does not have any keys
   156  		// to index with.
   157  		diags = diags.Append(&hcl.Diagnostic{
   158  			Severity: hcl.DiagError,
   159  			Summary:  `Cannot index a set value`,
   160  			Detail:   fmt.Sprintf(`Block type %q is represented by a set of objects, and set elements do not have addressable keys. To find elements matching specific criteria, use a "for" expression with an "if" clause.`, typeName),
   161  			Subject:  next.SourceRange().Ptr(),
   162  		})
   163  		return diags
   164  
   165  	case NestingList:
   166  		if _, ok := next.(hcl.TraverseIndex); ok {
   167  			moreDiags := b.Block.StaticValidateTraversal(after)
   168  			diags = diags.Append(moreDiags)
   169  		} else {
   170  			diags = diags.Append(&hcl.Diagnostic{
   171  				Severity: hcl.DiagError,
   172  				Summary:  `Invalid operation`,
   173  				Detail:   fmt.Sprintf(`Block type %q is represented by a list of objects, so it must be indexed using a numeric key, like .%s[0].`, typeName, typeName),
   174  				Subject:  next.SourceRange().Ptr(),
   175  			})
   176  		}
   177  		return diags
   178  
   179  	case NestingMap:
   180  		// Both attribute and index steps are valid for maps, so we'll just
   181  		// pass through here and let normal evaluation catch an
   182  		// incorrectly-typed index key later, if present.
   183  		moreDiags := b.Block.StaticValidateTraversal(after)
   184  		diags = diags.Append(moreDiags)
   185  		return diags
   186  
   187  	default:
   188  		// Invalid nesting type is just ignored. It's checked by
   189  		// InternalValidate. (Note that we handled NestingSingle separately
   190  		// back at the start of this function.)
   191  		return nil
   192  	}
   193  }