6 "github.com/hashicorp/hcl2/hcl"
7 "github.com/hashicorp/hcl2/hcl/hclsyntax"
8 "github.com/hashicorp/terraform/tfdiags"
11 // Reference describes a reference to an address with source location
13 type Reference struct {
15 SourceRange tfdiags.SourceRange
16 Remaining hcl.Traversal
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
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
28 // If error diagnostics are returned then the Reference value is invalid and
30 func ParseRef(traversal hcl.Traversal) (*Reference, tfdiags.Diagnostics) {
31 ref, diags := parseRef(traversal)
33 // Normalize a little to make life easier for callers.
35 if len(ref.Remaining) == 0 {
43 // ParseRefStr is a helper wrapper around ParseRef that takes a string
44 // and parses it with the HCL native syntax traversal parser before
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
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
61 traversal, parseDiags := hclsyntax.ParseTraversalAbs([]byte(str), "", hcl.Pos{Line: 1, Column: 1})
62 diags = diags.Append(parseDiags)
63 if parseDiags.HasErrors() {
67 ref, targetDiags := ParseRef(traversal)
68 diags = diags.Append(targetDiags)
72 func parseRef(traversal hcl.Traversal) (*Reference, tfdiags.Diagnostics) {
73 var diags tfdiags.Diagnostics
75 root := traversal.RootName()
76 rootRange := traversal[0].SourceRange()
81 name, rng, remain, diags := parseSingleAttrRef(traversal)
83 Subject: CountAttr{Name: name},
84 SourceRange: tfdiags.SourceRangeFromHCL(rng),
89 if len(traversal) < 3 {
90 diags = diags.Append(&hcl.Diagnostic{
91 Severity: hcl.DiagError,
92 Summary: "Invalid reference",
93 Detail: `The "data" object must be followed by two attribute names: the data source type and the resource name.`,
94 Subject: traversal.SourceRange().Ptr(),
98 remain := traversal[1:] // trim off "data" so we can use our shared resource reference parser
99 return parseResourceRef(DataResourceMode, rootRange, remain)
102 name, rng, remain, diags := parseSingleAttrRef(traversal)
104 Subject: LocalValue{Name: name},
105 SourceRange: tfdiags.SourceRangeFromHCL(rng),
110 callName, callRange, remain, diags := parseSingleAttrRef(traversal)
111 if diags.HasErrors() {
115 // A traversal starting with "module" can either be a reference to
116 // an entire module instance or to a single output from a module
117 // instance, depending on what we find after this introducer.
119 callInstance := ModuleCallInstance{
126 if len(remain) == 0 {
127 // Reference to an entire module instance. Might alternatively
128 // be a reference to a collection of instances of a particular
129 // module, but the caller will need to deal with that ambiguity
130 // since we don't have enough context here.
132 Subject: callInstance,
133 SourceRange: tfdiags.SourceRangeFromHCL(callRange),
138 if idxTrav, ok := remain[0].(hcl.TraverseIndex); ok {
140 callInstance.Key, err = ParseInstanceKey(idxTrav.Key)
142 diags = diags.Append(&hcl.Diagnostic{
143 Severity: hcl.DiagError,
144 Summary: "Invalid index key",
145 Detail: fmt.Sprintf("Invalid index for module instance: %s.", err),
146 Subject: &idxTrav.SrcRange,
152 if len(remain) == 0 {
153 // Also a reference to an entire module instance, but we have a key
156 Subject: callInstance,
157 SourceRange: tfdiags.SourceRangeFromHCL(hcl.RangeBetween(callRange, idxTrav.SrcRange)),
163 if attrTrav, ok := remain[0].(hcl.TraverseAttr); ok {
166 Subject: ModuleCallOutput{
170 SourceRange: tfdiags.SourceRangeFromHCL(hcl.RangeBetween(callRange, attrTrav.SrcRange)),
175 diags = diags.Append(&hcl.Diagnostic{
176 Severity: hcl.DiagError,
177 Summary: "Invalid reference",
178 Detail: "Module instance objects do not support this operation.",
179 Subject: remain[0].SourceRange().Ptr(),
184 name, rng, remain, diags := parseSingleAttrRef(traversal)
186 Subject: PathAttr{Name: name},
187 SourceRange: tfdiags.SourceRangeFromHCL(rng),
194 SourceRange: tfdiags.SourceRangeFromHCL(rootRange),
195 Remaining: traversal[1:],
199 name, rng, remain, diags := parseSingleAttrRef(traversal)
201 Subject: TerraformAttr{Name: name},
202 SourceRange: tfdiags.SourceRangeFromHCL(rng),
207 name, rng, remain, diags := parseSingleAttrRef(traversal)
209 Subject: InputVariable{Name: name},
210 SourceRange: tfdiags.SourceRangeFromHCL(rng),
215 return parseResourceRef(ManagedResourceMode, rootRange, traversal)
219 func parseResourceRef(mode ResourceMode, startRange hcl.Range, traversal hcl.Traversal) (*Reference, tfdiags.Diagnostics) {
220 var diags tfdiags.Diagnostics
222 if len(traversal) < 2 {
223 diags = diags.Append(&hcl.Diagnostic{
224 Severity: hcl.DiagError,
225 Summary: "Invalid reference",
226 Detail: `A reference to a resource type must be followed by at least one attribute access, specifying the resource name.`,
227 Subject: hcl.RangeBetween(traversal[0].SourceRange(), traversal[len(traversal)-1].SourceRange()).Ptr(),
232 var typeName, name string
233 switch tt := traversal[0].(type) { // Could be either root or attr, depending on our resource mode
234 case hcl.TraverseRoot:
236 case hcl.TraverseAttr:
239 // If it isn't a TraverseRoot then it must be a "data" reference.
240 diags = diags.Append(&hcl.Diagnostic{
241 Severity: hcl.DiagError,
242 Summary: "Invalid reference",
243 Detail: `The "data" object does not support this operation.`,
244 Subject: traversal[0].SourceRange().Ptr(),
249 attrTrav, ok := traversal[1].(hcl.TraverseAttr)
253 case DataResourceMode:
256 what = "resource type"
258 diags = diags.Append(&hcl.Diagnostic{
259 Severity: hcl.DiagError,
260 Summary: "Invalid reference",
261 Detail: fmt.Sprintf(`A reference to a %s must be followed by at least one attribute access, specifying the resource name.`, what),
262 Subject: traversal[1].SourceRange().Ptr(),
267 rng := hcl.RangeBetween(startRange, attrTrav.SrcRange)
268 remain := traversal[2:]
270 resourceAddr := Resource{
275 resourceInstAddr := ResourceInstance{
276 Resource: resourceAddr,
280 if len(remain) == 0 {
281 // This might actually be a reference to the collection of all instances
282 // of the resource, but we don't have enough context here to decide
283 // so we'll let the caller resolve that ambiguity.
285 Subject: resourceInstAddr,
286 SourceRange: tfdiags.SourceRangeFromHCL(rng),
290 if idxTrav, ok := remain[0].(hcl.TraverseIndex); ok {
292 resourceInstAddr.Key, err = ParseInstanceKey(idxTrav.Key)
294 diags = diags.Append(&hcl.Diagnostic{
295 Severity: hcl.DiagError,
296 Summary: "Invalid index key",
297 Detail: fmt.Sprintf("Invalid index for resource instance: %s.", err),
298 Subject: &idxTrav.SrcRange,
303 rng = hcl.RangeBetween(rng, idxTrav.SrcRange)
307 Subject: resourceInstAddr,
308 SourceRange: tfdiags.SourceRangeFromHCL(rng),
313 func parseSingleAttrRef(traversal hcl.Traversal) (string, hcl.Range, hcl.Traversal, tfdiags.Diagnostics) {
314 var diags tfdiags.Diagnostics
316 root := traversal.RootName()
317 rootRange := traversal[0].SourceRange()
319 if len(traversal) < 2 {
320 diags = diags.Append(&hcl.Diagnostic{
321 Severity: hcl.DiagError,
322 Summary: "Invalid reference",
323 Detail: fmt.Sprintf("The %q object cannot be accessed directly. Instead, access one of its attributes.", root),
326 return "", hcl.Range{}, nil, diags
328 if attrTrav, ok := traversal[1].(hcl.TraverseAttr); ok {
329 return attrTrav.Name, hcl.RangeBetween(rootRange, attrTrav.SrcRange), traversal[2:], diags
331 diags = diags.Append(&hcl.Diagnostic{
332 Severity: hcl.DiagError,
333 Summary: "Invalid reference",
334 Detail: fmt.Sprintf("The %q object does not support this operation.", root),
335 Subject: traversal[1].SourceRange().Ptr(),
337 return "", hcl.Range{}, nil, diags