]> git.immae.eu Git - github/fretlink/terraform-provider-statuscake.git/blob - vendor/github.com/hashicorp/terraform/addrs/parse_ref.go
Upgrade to 0.12
[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 "data":
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(),
95 })
96 return nil, diags
97 }
98 remain := traversal[1:] // trim off "data" so we can use our shared resource reference parser
99 return parseResourceRef(DataResourceMode, rootRange, remain)
100
101 case "local":
102 name, rng, remain, diags := parseSingleAttrRef(traversal)
103 return &Reference{
104 Subject: LocalValue{Name: name},
105 SourceRange: tfdiags.SourceRangeFromHCL(rng),
106 Remaining: remain,
107 }, diags
108
109 case "module":
110 callName, callRange, remain, diags := parseSingleAttrRef(traversal)
111 if diags.HasErrors() {
112 return nil, diags
113 }
114
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.
118
119 callInstance := ModuleCallInstance{
120 Call: ModuleCall{
121 Name: callName,
122 },
123 Key: NoKey,
124 }
125
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.
131 return &Reference{
132 Subject: callInstance,
133 SourceRange: tfdiags.SourceRangeFromHCL(callRange),
134 Remaining: remain,
135 }, diags
136 }
137
138 if idxTrav, ok := remain[0].(hcl.TraverseIndex); ok {
139 var err error
140 callInstance.Key, err = ParseInstanceKey(idxTrav.Key)
141 if err != nil {
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,
147 })
148 return nil, diags
149 }
150 remain = remain[1:]
151
152 if len(remain) == 0 {
153 // Also a reference to an entire module instance, but we have a key
154 // now.
155 return &Reference{
156 Subject: callInstance,
157 SourceRange: tfdiags.SourceRangeFromHCL(hcl.RangeBetween(callRange, idxTrav.SrcRange)),
158 Remaining: remain,
159 }, diags
160 }
161 }
162
163 if attrTrav, ok := remain[0].(hcl.TraverseAttr); ok {
164 remain = remain[1:]
165 return &Reference{
166 Subject: ModuleCallOutput{
167 Name: attrTrav.Name,
168 Call: callInstance,
169 },
170 SourceRange: tfdiags.SourceRangeFromHCL(hcl.RangeBetween(callRange, attrTrav.SrcRange)),
171 Remaining: remain,
172 }, diags
173 }
174
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(),
180 })
181 return nil, diags
182
183 case "path":
184 name, rng, remain, diags := parseSingleAttrRef(traversal)
185 return &Reference{
186 Subject: PathAttr{Name: name},
187 SourceRange: tfdiags.SourceRangeFromHCL(rng),
188 Remaining: remain,
189 }, diags
190
191 case "self":
192 return &Reference{
193 Subject: Self,
194 SourceRange: tfdiags.SourceRangeFromHCL(rootRange),
195 Remaining: traversal[1:],
196 }, diags
197
198 case "terraform":
199 name, rng, remain, diags := parseSingleAttrRef(traversal)
200 return &Reference{
201 Subject: TerraformAttr{Name: name},
202 SourceRange: tfdiags.SourceRangeFromHCL(rng),
203 Remaining: remain,
204 }, diags
205
206 case "var":
207 name, rng, remain, diags := parseSingleAttrRef(traversal)
208 return &Reference{
209 Subject: InputVariable{Name: name},
210 SourceRange: tfdiags.SourceRangeFromHCL(rng),
211 Remaining: remain,
212 }, diags
213
214 default:
215 return parseResourceRef(ManagedResourceMode, rootRange, traversal)
216 }
217 }
218
219 func parseResourceRef(mode ResourceMode, startRange hcl.Range, traversal hcl.Traversal) (*Reference, tfdiags.Diagnostics) {
220 var diags tfdiags.Diagnostics
221
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(),
228 })
229 return nil, diags
230 }
231
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:
235 typeName = tt.Name
236 case hcl.TraverseAttr:
237 typeName = tt.Name
238 default:
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(),
245 })
246 return nil, diags
247 }
248
249 attrTrav, ok := traversal[1].(hcl.TraverseAttr)
250 if !ok {
251 var what string
252 switch mode {
253 case DataResourceMode:
254 what = "data source"
255 default:
256 what = "resource type"
257 }
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(),
263 })
264 return nil, diags
265 }
266 name = attrTrav.Name
267 rng := hcl.RangeBetween(startRange, attrTrav.SrcRange)
268 remain := traversal[2:]
269
270 resourceAddr := Resource{
271 Mode: mode,
272 Type: typeName,
273 Name: name,
274 }
275 resourceInstAddr := ResourceInstance{
276 Resource: resourceAddr,
277 Key: NoKey,
278 }
279
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.
284 return &Reference{
285 Subject: resourceInstAddr,
286 SourceRange: tfdiags.SourceRangeFromHCL(rng),
287 }, diags
288 }
289
290 if idxTrav, ok := remain[0].(hcl.TraverseIndex); ok {
291 var err error
292 resourceInstAddr.Key, err = ParseInstanceKey(idxTrav.Key)
293 if err != nil {
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,
299 })
300 return nil, diags
301 }
302 remain = remain[1:]
303 rng = hcl.RangeBetween(rng, idxTrav.SrcRange)
304 }
305
306 return &Reference{
307 Subject: resourceInstAddr,
308 SourceRange: tfdiags.SourceRangeFromHCL(rng),
309 Remaining: remain,
310 }, diags
311 }
312
313 func parseSingleAttrRef(traversal hcl.Traversal) (string, hcl.Range, hcl.Traversal, tfdiags.Diagnostics) {
314 var diags tfdiags.Diagnostics
315
316 root := traversal.RootName()
317 rootRange := traversal[0].SourceRange()
318
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),
324 Subject: &rootRange,
325 })
326 return "", hcl.Range{}, nil, diags
327 }
328 if attrTrav, ok := traversal[1].(hcl.TraverseAttr); ok {
329 return attrTrav.Name, hcl.RangeBetween(rootRange, attrTrav.SrcRange), traversal[2:], diags
330 }
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(),
336 })
337 return "", hcl.Range{}, nil, diags
338 }