github.com/hugorut/terraform@v1.1.3/src/addrs/parse_ref.go (about)

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