]> git.immae.eu Git - github/fretlink/terraform-provider-statuscake.git/blob - vendor/github.com/hashicorp/terraform/addrs/parse_ref.go
update vendor and go.mod
[github/fretlink/terraform-provider-statuscake.git] / vendor / github.com / hashicorp / terraform / addrs / parse_ref.go
1 package addrs
2
3 import (
4 "fmt"
5
6 "github.com/hashicorp/hcl2/hcl"
7 "github.com/hashicorp/hcl2/hcl/hclsyntax"
8 "github.com/hashicorp/terraform/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 "local":
110 name, rng, remain, diags := parseSingleAttrRef(traversal)
111 return &Reference{
112 Subject: LocalValue{Name: name},
113 SourceRange: tfdiags.SourceRangeFromHCL(rng),
114 Remaining: remain,
115 }, diags
116
117 case "module":
118 callName, callRange, remain, diags := parseSingleAttrRef(traversal)
119 if diags.HasErrors() {
120 return nil, diags
121 }
122
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.
126
127 callInstance := ModuleCallInstance{
128 Call: ModuleCall{
129 Name: callName,
130 },
131 Key: NoKey,
132 }
133
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.
139 return &Reference{
140 Subject: callInstance,
141 SourceRange: tfdiags.SourceRangeFromHCL(callRange),
142 Remaining: remain,
143 }, diags
144 }
145
146 if idxTrav, ok := remain[0].(hcl.TraverseIndex); ok {
147 var err error
148 callInstance.Key, err = ParseInstanceKey(idxTrav.Key)
149 if err != nil {
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,
155 })
156 return nil, diags
157 }
158 remain = remain[1:]
159
160 if len(remain) == 0 {
161 // Also a reference to an entire module instance, but we have a key
162 // now.
163 return &Reference{
164 Subject: callInstance,
165 SourceRange: tfdiags.SourceRangeFromHCL(hcl.RangeBetween(callRange, idxTrav.SrcRange)),
166 Remaining: remain,
167 }, diags
168 }
169 }
170
171 if attrTrav, ok := remain[0].(hcl.TraverseAttr); ok {
172 remain = remain[1:]
173 return &Reference{
174 Subject: ModuleCallOutput{
175 Name: attrTrav.Name,
176 Call: callInstance,
177 },
178 SourceRange: tfdiags.SourceRangeFromHCL(hcl.RangeBetween(callRange, attrTrav.SrcRange)),
179 Remaining: remain,
180 }, diags
181 }
182
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(),
188 })
189 return nil, diags
190
191 case "path":
192 name, rng, remain, diags := parseSingleAttrRef(traversal)
193 return &Reference{
194 Subject: PathAttr{Name: name},
195 SourceRange: tfdiags.SourceRangeFromHCL(rng),
196 Remaining: remain,
197 }, diags
198
199 case "self":
200 return &Reference{
201 Subject: Self,
202 SourceRange: tfdiags.SourceRangeFromHCL(rootRange),
203 Remaining: traversal[1:],
204 }, diags
205
206 case "terraform":
207 name, rng, remain, diags := parseSingleAttrRef(traversal)
208 return &Reference{
209 Subject: TerraformAttr{Name: name},
210 SourceRange: tfdiags.SourceRangeFromHCL(rng),
211 Remaining: remain,
212 }, diags
213
214 case "var":
215 name, rng, remain, diags := parseSingleAttrRef(traversal)
216 return &Reference{
217 Subject: InputVariable{Name: name},
218 SourceRange: tfdiags.SourceRangeFromHCL(rng),
219 Remaining: remain,
220 }, diags
221
222 default:
223 return parseResourceRef(ManagedResourceMode, rootRange, traversal)
224 }
225 }
226
227 func parseResourceRef(mode ResourceMode, startRange hcl.Range, traversal hcl.Traversal) (*Reference, tfdiags.Diagnostics) {
228 var diags tfdiags.Diagnostics
229
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(),
236 })
237 return nil, diags
238 }
239
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:
243 typeName = tt.Name
244 case hcl.TraverseAttr:
245 typeName = tt.Name
246 default:
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(),
253 })
254 return nil, diags
255 }
256
257 attrTrav, ok := traversal[1].(hcl.TraverseAttr)
258 if !ok {
259 var what string
260 switch mode {
261 case DataResourceMode:
262 what = "data source"
263 default:
264 what = "resource type"
265 }
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(),
271 })
272 return nil, diags
273 }
274 name = attrTrav.Name
275 rng := hcl.RangeBetween(startRange, attrTrav.SrcRange)
276 remain := traversal[2:]
277
278 resourceAddr := Resource{
279 Mode: mode,
280 Type: typeName,
281 Name: name,
282 }
283 resourceInstAddr := ResourceInstance{
284 Resource: resourceAddr,
285 Key: NoKey,
286 }
287
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.
292 return &Reference{
293 Subject: resourceInstAddr,
294 SourceRange: tfdiags.SourceRangeFromHCL(rng),
295 }, diags
296 }
297
298 if idxTrav, ok := remain[0].(hcl.TraverseIndex); ok {
299 var err error
300 resourceInstAddr.Key, err = ParseInstanceKey(idxTrav.Key)
301 if err != nil {
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,
307 })
308 return nil, diags
309 }
310 remain = remain[1:]
311 rng = hcl.RangeBetween(rng, idxTrav.SrcRange)
312 }
313
314 return &Reference{
315 Subject: resourceInstAddr,
316 SourceRange: tfdiags.SourceRangeFromHCL(rng),
317 Remaining: remain,
318 }, diags
319 }
320
321 func parseSingleAttrRef(traversal hcl.Traversal) (string, hcl.Range, hcl.Traversal, tfdiags.Diagnostics) {
322 var diags tfdiags.Diagnostics
323
324 root := traversal.RootName()
325 rootRange := traversal[0].SourceRange()
326
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),
332 Subject: &rootRange,
333 })
334 return "", hcl.Range{}, nil, diags
335 }
336 if attrTrav, ok := traversal[1].(hcl.TraverseAttr); ok {
337 return attrTrav.Name, hcl.RangeBetween(rootRange, attrTrav.SrcRange), traversal[2:], diags
338 }
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(),
344 })
345 return "", hcl.Range{}, nil, diags
346 }