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 name, rng, remain, diags := parseSingleAttrRef(traversal)
91 Subject: ForEachAttr{Name: name},
92 SourceRange: tfdiags.SourceRangeFromHCL(rng),
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(),
106 remain := traversal[1:] // trim off "data" so we can use our shared resource reference parser
107 return parseResourceRef(DataResourceMode, rootRange, remain)
110 name, rng, remain, diags := parseSingleAttrRef(traversal)
112 Subject: LocalValue{Name: name},
113 SourceRange: tfdiags.SourceRangeFromHCL(rng),
118 callName, callRange, remain, diags := parseSingleAttrRef(traversal)
119 if diags.HasErrors() {
123 // A traversal starting with "module" can either be a reference to
124 // an entire module instance or to a single output from a module
125 // instance, depending on what we find after this introducer.
127 callInstance := ModuleCallInstance{
134 if len(remain) == 0 {
135 // Reference to an entire module instance. Might alternatively
136 // be a reference to a collection of instances of a particular
137 // module, but the caller will need to deal with that ambiguity
138 // since we don't have enough context here.
140 Subject: callInstance,
141 SourceRange: tfdiags.SourceRangeFromHCL(callRange),
146 if idxTrav, ok := remain[0].(hcl.TraverseIndex); ok {
148 callInstance.Key, err = ParseInstanceKey(idxTrav.Key)
150 diags = diags.Append(&hcl.Diagnostic{
151 Severity: hcl.DiagError,
152 Summary: "Invalid index key",
153 Detail: fmt.Sprintf("Invalid index for module instance: %s.", err),
154 Subject: &idxTrav.SrcRange,
160 if len(remain) == 0 {
161 // Also a reference to an entire module instance, but we have a key
164 Subject: callInstance,
165 SourceRange: tfdiags.SourceRangeFromHCL(hcl.RangeBetween(callRange, idxTrav.SrcRange)),
171 if attrTrav, ok := remain[0].(hcl.TraverseAttr); ok {
174 Subject: ModuleCallOutput{
178 SourceRange: tfdiags.SourceRangeFromHCL(hcl.RangeBetween(callRange, attrTrav.SrcRange)),
183 diags = diags.Append(&hcl.Diagnostic{
184 Severity: hcl.DiagError,
185 Summary: "Invalid reference",
186 Detail: "Module instance objects do not support this operation.",
187 Subject: remain[0].SourceRange().Ptr(),
192 name, rng, remain, diags := parseSingleAttrRef(traversal)
194 Subject: PathAttr{Name: name},
195 SourceRange: tfdiags.SourceRangeFromHCL(rng),
202 SourceRange: tfdiags.SourceRangeFromHCL(rootRange),
203 Remaining: traversal[1:],
207 name, rng, remain, diags := parseSingleAttrRef(traversal)
209 Subject: TerraformAttr{Name: name},
210 SourceRange: tfdiags.SourceRangeFromHCL(rng),
215 name, rng, remain, diags := parseSingleAttrRef(traversal)
217 Subject: InputVariable{Name: name},
218 SourceRange: tfdiags.SourceRangeFromHCL(rng),
223 return parseResourceRef(ManagedResourceMode, rootRange, traversal)
227 func parseResourceRef(mode ResourceMode, startRange hcl.Range, traversal hcl.Traversal) (*Reference, tfdiags.Diagnostics) {
228 var diags tfdiags.Diagnostics
230 if len(traversal) < 2 {
231 diags = diags.Append(&hcl.Diagnostic{
232 Severity: hcl.DiagError,
233 Summary: "Invalid reference",
234 Detail: `A reference to a resource type must be followed by at least one attribute access, specifying the resource name.`,
235 Subject: hcl.RangeBetween(traversal[0].SourceRange(), traversal[len(traversal)-1].SourceRange()).Ptr(),
240 var typeName, name string
241 switch tt := traversal[0].(type) { // Could be either root or attr, depending on our resource mode
242 case hcl.TraverseRoot:
244 case hcl.TraverseAttr:
247 // If it isn't a TraverseRoot then it must be a "data" reference.
248 diags = diags.Append(&hcl.Diagnostic{
249 Severity: hcl.DiagError,
250 Summary: "Invalid reference",
251 Detail: `The "data" object does not support this operation.`,
252 Subject: traversal[0].SourceRange().Ptr(),
257 attrTrav, ok := traversal[1].(hcl.TraverseAttr)
261 case DataResourceMode:
264 what = "resource type"
266 diags = diags.Append(&hcl.Diagnostic{
267 Severity: hcl.DiagError,
268 Summary: "Invalid reference",
269 Detail: fmt.Sprintf(`A reference to a %s must be followed by at least one attribute access, specifying the resource name.`, what),
270 Subject: traversal[1].SourceRange().Ptr(),
275 rng := hcl.RangeBetween(startRange, attrTrav.SrcRange)
276 remain := traversal[2:]
278 resourceAddr := Resource{
283 resourceInstAddr := ResourceInstance{
284 Resource: resourceAddr,
288 if len(remain) == 0 {
289 // This might actually be a reference to the collection of all instances
290 // of the resource, but we don't have enough context here to decide
291 // so we'll let the caller resolve that ambiguity.
293 Subject: resourceInstAddr,
294 SourceRange: tfdiags.SourceRangeFromHCL(rng),
298 if idxTrav, ok := remain[0].(hcl.TraverseIndex); ok {
300 resourceInstAddr.Key, err = ParseInstanceKey(idxTrav.Key)
302 diags = diags.Append(&hcl.Diagnostic{
303 Severity: hcl.DiagError,
304 Summary: "Invalid index key",
305 Detail: fmt.Sprintf("Invalid index for resource instance: %s.", err),
306 Subject: &idxTrav.SrcRange,
311 rng = hcl.RangeBetween(rng, idxTrav.SrcRange)
315 Subject: resourceInstAddr,
316 SourceRange: tfdiags.SourceRangeFromHCL(rng),
321 func parseSingleAttrRef(traversal hcl.Traversal) (string, hcl.Range, hcl.Traversal, tfdiags.Diagnostics) {
322 var diags tfdiags.Diagnostics
324 root := traversal.RootName()
325 rootRange := traversal[0].SourceRange()
327 if len(traversal) < 2 {
328 diags = diags.Append(&hcl.Diagnostic{
329 Severity: hcl.DiagError,
330 Summary: "Invalid reference",
331 Detail: fmt.Sprintf("The %q object cannot be accessed directly. Instead, access one of its attributes.", root),
334 return "", hcl.Range{}, nil, diags
336 if attrTrav, ok := traversal[1].(hcl.TraverseAttr); ok {
337 return attrTrav.Name, hcl.RangeBetween(rootRange, attrTrav.SrcRange), traversal[2:], diags
339 diags = diags.Append(&hcl.Diagnostic{
340 Severity: hcl.DiagError,
341 Summary: "Invalid reference",
342 Detail: fmt.Sprintf("The %q object does not support this operation.", root),
343 Subject: traversal[1].SourceRange().Ptr(),
345 return "", hcl.Range{}, nil, diags