github.com/terraform-linters/tflint-plugin-sdk@v0.22.0/terraform/addrs/parse_ref.go (about)

     1  package addrs
     2  
     3  import (
     4  	"fmt"
     5  
     6  	"github.com/hashicorp/hcl/v2"
     7  )
     8  
     9  // Reference describes a reference to an address with source location
    10  // information.
    11  type Reference struct {
    12  	Subject     Referenceable
    13  	SourceRange hcl.Range
    14  	Remaining   hcl.Traversal
    15  }
    16  
    17  // ParseRef attempts to extract a referencable address from the prefix of the
    18  // given traversal, which must be an absolute traversal or this function
    19  // will panic.
    20  //
    21  // If no error diagnostics are returned, the returned reference includes the
    22  // address that was extracted, the source range it was extracted from, and any
    23  // remaining relative traversal that was not consumed as part of the
    24  // reference.
    25  //
    26  // If error diagnostics are returned then the Reference value is invalid and
    27  // must not be used.
    28  func ParseRef(traversal hcl.Traversal) (*Reference, hcl.Diagnostics) {
    29  	ref, diags := parseRef(traversal)
    30  
    31  	// Normalize a little to make life easier for callers.
    32  	if ref != nil {
    33  		if len(ref.Remaining) == 0 {
    34  			ref.Remaining = nil
    35  		}
    36  	}
    37  
    38  	return ref, diags
    39  }
    40  
    41  func parseRef(traversal hcl.Traversal) (*Reference, hcl.Diagnostics) {
    42  	var diags hcl.Diagnostics
    43  
    44  	root := traversal.RootName()
    45  	rootRange := traversal[0].SourceRange()
    46  
    47  	switch root {
    48  
    49  	case "count":
    50  		name, rng, remain, diags := parseSingleAttrRef(traversal)
    51  		return &Reference{
    52  			Subject:     CountAttr{Name: name},
    53  			SourceRange: rng,
    54  			Remaining:   remain,
    55  		}, diags
    56  
    57  	case "each":
    58  		name, rng, remain, diags := parseSingleAttrRef(traversal)
    59  		return &Reference{
    60  			Subject:     ForEachAttr{Name: name},
    61  			SourceRange: rng,
    62  			Remaining:   remain,
    63  		}, diags
    64  
    65  	case "data":
    66  		if len(traversal) < 3 {
    67  			diags = diags.Append(&hcl.Diagnostic{
    68  				Severity: hcl.DiagError,
    69  				Summary:  "Invalid reference",
    70  				Detail:   `The "data" object must be followed by two attribute names: the data source type and the resource name.`,
    71  				Subject:  traversal.SourceRange().Ptr(),
    72  			})
    73  			return nil, diags
    74  		}
    75  		remain := traversal[1:] // trim off "data" so we can use our shared resource reference parser
    76  		return parseResourceRef(DataResourceMode, rootRange, remain)
    77  
    78  	case "resource":
    79  		// This is an alias for the normal case of just using a managed resource
    80  		// type as a top-level symbol, which will serve as an escape mechanism
    81  		// if a later edition of the Terraform language introduces a new
    82  		// reference prefix that conflicts with a resource type name in an
    83  		// existing provider. In that case, the edition upgrade tool can
    84  		// rewrite foo.bar into resource.foo.bar to ensure that "foo" remains
    85  		// interpreted as a resource type name rather than as the new reserved
    86  		// word.
    87  		if len(traversal) < 3 {
    88  			diags = diags.Append(&hcl.Diagnostic{
    89  				Severity: hcl.DiagError,
    90  				Summary:  "Invalid reference",
    91  				Detail:   `The "resource" object must be followed by two attribute names: the resource type and the resource name.`,
    92  				Subject:  traversal.SourceRange().Ptr(),
    93  			})
    94  			return nil, diags
    95  		}
    96  		remain := traversal[1:] // trim off "resource" so we can use our shared resource reference parser
    97  		return parseResourceRef(ManagedResourceMode, rootRange, remain)
    98  
    99  	case "local":
   100  		name, rng, remain, diags := parseSingleAttrRef(traversal)
   101  		return &Reference{
   102  			Subject:     LocalValue{Name: name},
   103  			SourceRange: rng,
   104  			Remaining:   remain,
   105  		}, diags
   106  
   107  	case "module":
   108  		callName, callRange, remain, diags := parseSingleAttrRef(traversal)
   109  		if diags.HasErrors() {
   110  			return nil, diags
   111  		}
   112  
   113  		// A traversal starting with "module" can either be a reference to an
   114  		// entire module, or to a single output from a module instance,
   115  		// depending on what we find after this introducer.
   116  		callInstance := ModuleCallInstance{
   117  			Call: ModuleCall{
   118  				Name: callName,
   119  			},
   120  			Key: NoKey,
   121  		}
   122  
   123  		if len(remain) == 0 {
   124  			// Reference to an entire module. Might alternatively be a
   125  			// reference to a single instance of a particular module, but the
   126  			// caller will need to deal with that ambiguity since we don't have
   127  			// enough context here.
   128  			return &Reference{
   129  				Subject:     callInstance.Call,
   130  				SourceRange: callRange,
   131  				Remaining:   remain,
   132  			}, diags
   133  		}
   134  
   135  		if idxTrav, ok := remain[0].(hcl.TraverseIndex); ok {
   136  			var err error
   137  			callInstance.Key, err = ParseInstanceKey(idxTrav.Key)
   138  			if err != nil {
   139  				diags = diags.Append(&hcl.Diagnostic{
   140  					Severity: hcl.DiagError,
   141  					Summary:  "Invalid index key",
   142  					Detail:   fmt.Sprintf("Invalid index for module instance: %s.", err),
   143  					Subject:  &idxTrav.SrcRange,
   144  				})
   145  				return nil, diags
   146  			}
   147  			remain = remain[1:]
   148  
   149  			if len(remain) == 0 {
   150  				// Also a reference to an entire module instance, but we have a key
   151  				// now.
   152  				return &Reference{
   153  					Subject:     callInstance,
   154  					SourceRange: hcl.RangeBetween(callRange, idxTrav.SrcRange),
   155  					Remaining:   remain,
   156  				}, diags
   157  			}
   158  		}
   159  
   160  		if attrTrav, ok := remain[0].(hcl.TraverseAttr); ok {
   161  			remain = remain[1:]
   162  			return &Reference{
   163  				Subject: ModuleCallInstanceOutput{
   164  					Name: attrTrav.Name,
   165  					Call: callInstance,
   166  				},
   167  				SourceRange: hcl.RangeBetween(callRange, attrTrav.SrcRange),
   168  				Remaining:   remain,
   169  			}, diags
   170  		}
   171  
   172  		diags = diags.Append(&hcl.Diagnostic{
   173  			Severity: hcl.DiagError,
   174  			Summary:  "Invalid reference",
   175  			Detail:   "Module instance objects do not support this operation.",
   176  			Subject:  remain[0].SourceRange().Ptr(),
   177  		})
   178  		return nil, diags
   179  
   180  	case "path":
   181  		name, rng, remain, diags := parseSingleAttrRef(traversal)
   182  		return &Reference{
   183  			Subject:     PathAttr{Name: name},
   184  			SourceRange: rng,
   185  			Remaining:   remain,
   186  		}, diags
   187  
   188  	case "self":
   189  		return &Reference{
   190  			Subject:     Self,
   191  			SourceRange: rootRange,
   192  			Remaining:   traversal[1:],
   193  		}, diags
   194  
   195  	case "terraform":
   196  		name, rng, remain, diags := parseSingleAttrRef(traversal)
   197  		return &Reference{
   198  			Subject:     TerraformAttr{Name: name},
   199  			SourceRange: rng,
   200  			Remaining:   remain,
   201  		}, diags
   202  
   203  	case "var":
   204  		name, rng, remain, diags := parseSingleAttrRef(traversal)
   205  		return &Reference{
   206  			Subject:     InputVariable{Name: name},
   207  			SourceRange: rng,
   208  			Remaining:   remain,
   209  		}, diags
   210  
   211  	case "template", "lazy", "arg":
   212  		// These names are all pre-emptively reserved in the hope of landing
   213  		// some version of "template values" or "lazy expressions" feature
   214  		// before the next opt-in language edition, but don't yet do anything.
   215  		diags = diags.Append(&hcl.Diagnostic{
   216  			Severity: hcl.DiagError,
   217  			Summary:  "Reserved symbol name",
   218  			Detail:   fmt.Sprintf("The symbol name %q is reserved for use in a future Terraform version. If you are using a provider that already uses this as a resource type name, add the prefix \"resource.\" to force interpretation as a resource type name.", root),
   219  			Subject:  rootRange.Ptr(),
   220  		})
   221  		return nil, diags
   222  
   223  	default:
   224  		return parseResourceRef(ManagedResourceMode, rootRange, traversal)
   225  	}
   226  }
   227  
   228  func parseResourceRef(mode ResourceMode, startRange hcl.Range, traversal hcl.Traversal) (*Reference, hcl.Diagnostics) {
   229  	var diags hcl.Diagnostics
   230  
   231  	if len(traversal) < 2 {
   232  		diags = diags.Append(&hcl.Diagnostic{
   233  			Severity: hcl.DiagError,
   234  			Summary:  "Invalid reference",
   235  			Detail:   `A reference to a resource type must be followed by at least one attribute access, specifying the resource name.`,
   236  			Subject:  hcl.RangeBetween(traversal[0].SourceRange(), traversal[len(traversal)-1].SourceRange()).Ptr(),
   237  		})
   238  		return nil, diags
   239  	}
   240  
   241  	var typeName, name string
   242  	switch tt := traversal[0].(type) { // Could be either root or attr, depending on our resource mode
   243  	case hcl.TraverseRoot:
   244  		typeName = tt.Name
   245  	case hcl.TraverseAttr:
   246  		typeName = tt.Name
   247  	default:
   248  		// If it isn't a TraverseRoot then it must be a "data" reference.
   249  		diags = diags.Append(&hcl.Diagnostic{
   250  			Severity: hcl.DiagError,
   251  			Summary:  "Invalid reference",
   252  			Detail:   `The "data" object does not support this operation.`,
   253  			Subject:  traversal[0].SourceRange().Ptr(),
   254  		})
   255  		return nil, diags
   256  	}
   257  
   258  	attrTrav, ok := traversal[1].(hcl.TraverseAttr)
   259  	if !ok {
   260  		var what string
   261  		switch mode {
   262  		case DataResourceMode:
   263  			what = "data source"
   264  		default:
   265  			what = "resource type"
   266  		}
   267  		diags = diags.Append(&hcl.Diagnostic{
   268  			Severity: hcl.DiagError,
   269  			Summary:  "Invalid reference",
   270  			Detail:   fmt.Sprintf(`A reference to a %s must be followed by at least one attribute access, specifying the resource name.`, what),
   271  			Subject:  traversal[1].SourceRange().Ptr(),
   272  		})
   273  		return nil, diags
   274  	}
   275  	name = attrTrav.Name
   276  	rng := hcl.RangeBetween(startRange, attrTrav.SrcRange)
   277  	remain := traversal[2:]
   278  
   279  	resourceAddr := Resource{
   280  		Mode: mode,
   281  		Type: typeName,
   282  		Name: name,
   283  	}
   284  	resourceInstAddr := ResourceInstance{
   285  		Resource: resourceAddr,
   286  		Key:      NoKey,
   287  	}
   288  
   289  	if len(remain) == 0 {
   290  		// This might actually be a reference to the collection of all instances
   291  		// of the resource, but we don't have enough context here to decide
   292  		// so we'll let the caller resolve that ambiguity.
   293  		return &Reference{
   294  			Subject:     resourceAddr,
   295  			SourceRange: rng,
   296  		}, diags
   297  	}
   298  
   299  	if idxTrav, ok := remain[0].(hcl.TraverseIndex); ok {
   300  		var err error
   301  		resourceInstAddr.Key, err = ParseInstanceKey(idxTrav.Key)
   302  		if err != nil {
   303  			diags = diags.Append(&hcl.Diagnostic{
   304  				Severity: hcl.DiagError,
   305  				Summary:  "Invalid index key",
   306  				Detail:   fmt.Sprintf("Invalid index for resource instance: %s.", err),
   307  				Subject:  &idxTrav.SrcRange,
   308  			})
   309  			return nil, diags
   310  		}
   311  		remain = remain[1:]
   312  		rng = hcl.RangeBetween(rng, idxTrav.SrcRange)
   313  	}
   314  
   315  	return &Reference{
   316  		Subject:     resourceInstAddr,
   317  		SourceRange: rng,
   318  		Remaining:   remain,
   319  	}, diags
   320  }
   321  
   322  func parseSingleAttrRef(traversal hcl.Traversal) (string, hcl.Range, hcl.Traversal, hcl.Diagnostics) {
   323  	var diags hcl.Diagnostics
   324  
   325  	root := traversal.RootName()
   326  	rootRange := traversal[0].SourceRange()
   327  
   328  	if len(traversal) < 2 {
   329  		diags = diags.Append(&hcl.Diagnostic{
   330  			Severity: hcl.DiagError,
   331  			Summary:  "Invalid reference",
   332  			Detail:   fmt.Sprintf("The %q object cannot be accessed directly. Instead, access one of its attributes.", root),
   333  			Subject:  &rootRange,
   334  		})
   335  		return "", hcl.Range{}, nil, diags
   336  	}
   337  	if attrTrav, ok := traversal[1].(hcl.TraverseAttr); ok {
   338  		return attrTrav.Name, hcl.RangeBetween(rootRange, attrTrav.SrcRange), traversal[2:], diags
   339  	}
   340  	diags = diags.Append(&hcl.Diagnostic{
   341  		Severity: hcl.DiagError,
   342  		Summary:  "Invalid reference",
   343  		Detail:   fmt.Sprintf("The %q object does not support this operation.", root),
   344  		Subject:  traversal[1].SourceRange().Ptr(),
   345  	})
   346  	return "", hcl.Range{}, nil, diags
   347  }