diff options
Diffstat (limited to 'vendor/github.com/hashicorp/terraform/addrs/parse_ref.go')
-rw-r--r-- | vendor/github.com/hashicorp/terraform/addrs/parse_ref.go | 338 |
1 files changed, 338 insertions, 0 deletions
diff --git a/vendor/github.com/hashicorp/terraform/addrs/parse_ref.go b/vendor/github.com/hashicorp/terraform/addrs/parse_ref.go new file mode 100644 index 0000000..84fe8a0 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform/addrs/parse_ref.go | |||
@@ -0,0 +1,338 @@ | |||
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 | } | ||