diff options
Diffstat (limited to 'vendor/github.com/hashicorp/terraform/tfdiags')
11 files changed, 683 insertions, 2 deletions
diff --git a/vendor/github.com/hashicorp/terraform/tfdiags/config_traversals.go b/vendor/github.com/hashicorp/terraform/tfdiags/config_traversals.go new file mode 100644 index 0000000..8e41f46 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform/tfdiags/config_traversals.go | |||
@@ -0,0 +1,68 @@ | |||
1 | package tfdiags | ||
2 | |||
3 | import ( | ||
4 | "bytes" | ||
5 | "fmt" | ||
6 | "strconv" | ||
7 | |||
8 | "github.com/zclconf/go-cty/cty" | ||
9 | ) | ||
10 | |||
11 | // FormatCtyPath is a helper function to produce a user-friendly string | ||
12 | // representation of a cty.Path. The result uses a syntax similar to the | ||
13 | // HCL expression language in the hope of it being familiar to users. | ||
14 | func FormatCtyPath(path cty.Path) string { | ||
15 | var buf bytes.Buffer | ||
16 | for _, step := range path { | ||
17 | switch ts := step.(type) { | ||
18 | case cty.GetAttrStep: | ||
19 | fmt.Fprintf(&buf, ".%s", ts.Name) | ||
20 | case cty.IndexStep: | ||
21 | buf.WriteByte('[') | ||
22 | key := ts.Key | ||
23 | keyTy := key.Type() | ||
24 | switch { | ||
25 | case key.IsNull(): | ||
26 | buf.WriteString("null") | ||
27 | case !key.IsKnown(): | ||
28 | buf.WriteString("(not yet known)") | ||
29 | case keyTy == cty.Number: | ||
30 | bf := key.AsBigFloat() | ||
31 | buf.WriteString(bf.Text('g', -1)) | ||
32 | case keyTy == cty.String: | ||
33 | buf.WriteString(strconv.Quote(key.AsString())) | ||
34 | default: | ||
35 | buf.WriteString("...") | ||
36 | } | ||
37 | buf.WriteByte(']') | ||
38 | } | ||
39 | } | ||
40 | return buf.String() | ||
41 | } | ||
42 | |||
43 | // FormatError is a helper function to produce a user-friendly string | ||
44 | // representation of certain special error types that we might want to | ||
45 | // include in diagnostic messages. | ||
46 | // | ||
47 | // This currently has special behavior only for cty.PathError, where a | ||
48 | // non-empty path is rendered in a HCL-like syntax as context. | ||
49 | func FormatError(err error) string { | ||
50 | perr, ok := err.(cty.PathError) | ||
51 | if !ok || len(perr.Path) == 0 { | ||
52 | return err.Error() | ||
53 | } | ||
54 | |||
55 | return fmt.Sprintf("%s: %s", FormatCtyPath(perr.Path), perr.Error()) | ||
56 | } | ||
57 | |||
58 | // FormatErrorPrefixed is like FormatError except that it presents any path | ||
59 | // information after the given prefix string, which is assumed to contain | ||
60 | // an HCL syntax representation of the value that errors are relative to. | ||
61 | func FormatErrorPrefixed(err error, prefix string) string { | ||
62 | perr, ok := err.(cty.PathError) | ||
63 | if !ok || len(perr.Path) == 0 { | ||
64 | return fmt.Sprintf("%s: %s", prefix, err.Error()) | ||
65 | } | ||
66 | |||
67 | return fmt.Sprintf("%s%s: %s", prefix, FormatCtyPath(perr.Path), perr.Error()) | ||
68 | } | ||
diff --git a/vendor/github.com/hashicorp/terraform/tfdiags/contextual.go b/vendor/github.com/hashicorp/terraform/tfdiags/contextual.go new file mode 100644 index 0000000..25b2140 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform/tfdiags/contextual.go | |||
@@ -0,0 +1,372 @@ | |||
1 | package tfdiags | ||
2 | |||
3 | import ( | ||
4 | "github.com/hashicorp/hcl2/hcl" | ||
5 | "github.com/zclconf/go-cty/cty" | ||
6 | "github.com/zclconf/go-cty/cty/gocty" | ||
7 | ) | ||
8 | |||
9 | // The "contextual" family of diagnostics are designed to allow separating | ||
10 | // the detection of a problem from placing that problem in context. For | ||
11 | // example, some code that is validating an object extracted from configuration | ||
12 | // may not have access to the configuration that generated it, but can still | ||
13 | // report problems within that object which the caller can then place in | ||
14 | // context by calling IsConfigBody on the returned diagnostics. | ||
15 | // | ||
16 | // When contextual diagnostics are used, the documentation for a method must | ||
17 | // be very explicit about what context is implied for any diagnostics returned, | ||
18 | // to help ensure the expected result. | ||
19 | |||
20 | // contextualFromConfig is an interface type implemented by diagnostic types | ||
21 | // that can elaborate themselves when given information about the configuration | ||
22 | // body they are embedded in. | ||
23 | // | ||
24 | // Usually this entails extracting source location information in order to | ||
25 | // populate the "Subject" range. | ||
26 | type contextualFromConfigBody interface { | ||
27 | ElaborateFromConfigBody(hcl.Body) Diagnostic | ||
28 | } | ||
29 | |||
30 | // InConfigBody returns a copy of the receiver with any config-contextual | ||
31 | // diagnostics elaborated in the context of the given body. | ||
32 | func (d Diagnostics) InConfigBody(body hcl.Body) Diagnostics { | ||
33 | if len(d) == 0 { | ||
34 | return nil | ||
35 | } | ||
36 | |||
37 | ret := make(Diagnostics, len(d)) | ||
38 | for i, srcDiag := range d { | ||
39 | if cd, isCD := srcDiag.(contextualFromConfigBody); isCD { | ||
40 | ret[i] = cd.ElaborateFromConfigBody(body) | ||
41 | } else { | ||
42 | ret[i] = srcDiag | ||
43 | } | ||
44 | } | ||
45 | |||
46 | return ret | ||
47 | } | ||
48 | |||
49 | // AttributeValue returns a diagnostic about an attribute value in an implied current | ||
50 | // configuration context. This should be returned only from functions whose | ||
51 | // interface specifies a clear configuration context that this will be | ||
52 | // resolved in. | ||
53 | // | ||
54 | // The given path is relative to the implied configuration context. To describe | ||
55 | // a top-level attribute, it should be a single-element cty.Path with a | ||
56 | // cty.GetAttrStep. It's assumed that the path is returning into a structure | ||
57 | // that would be produced by our conventions in the configschema package; it | ||
58 | // may return unexpected results for structures that can't be represented by | ||
59 | // configschema. | ||
60 | // | ||
61 | // Since mapping attribute paths back onto configuration is an imprecise | ||
62 | // operation (e.g. dynamic block generation may cause the same block to be | ||
63 | // evaluated multiple times) the diagnostic detail should include the attribute | ||
64 | // name and other context required to help the user understand what is being | ||
65 | // referenced in case the identified source range is not unique. | ||
66 | // | ||
67 | // The returned attribute will not have source location information until | ||
68 | // context is applied to the containing diagnostics using diags.InConfigBody. | ||
69 | // After context is applied, the source location is the value assigned to the | ||
70 | // named attribute, or the containing body's "missing item range" if no | ||
71 | // value is present. | ||
72 | func AttributeValue(severity Severity, summary, detail string, attrPath cty.Path) Diagnostic { | ||
73 | return &attributeDiagnostic{ | ||
74 | diagnosticBase: diagnosticBase{ | ||
75 | severity: severity, | ||
76 | summary: summary, | ||
77 | detail: detail, | ||
78 | }, | ||
79 | attrPath: attrPath, | ||
80 | } | ||
81 | } | ||
82 | |||
83 | // GetAttribute extracts an attribute cty.Path from a diagnostic if it contains | ||
84 | // one. Normally this is not accessed directly, and instead the config body is | ||
85 | // added to the Diagnostic to create a more complete message for the user. In | ||
86 | // some cases however, we may want to know just the name of the attribute that | ||
87 | // generated the Diagnostic message. | ||
88 | // This returns a nil cty.Path if it does not exist in the Diagnostic. | ||
89 | func GetAttribute(d Diagnostic) cty.Path { | ||
90 | if d, ok := d.(*attributeDiagnostic); ok { | ||
91 | return d.attrPath | ||
92 | } | ||
93 | return nil | ||
94 | } | ||
95 | |||
96 | type attributeDiagnostic struct { | ||
97 | diagnosticBase | ||
98 | attrPath cty.Path | ||
99 | subject *SourceRange // populated only after ElaborateFromConfigBody | ||
100 | } | ||
101 | |||
102 | // ElaborateFromConfigBody finds the most accurate possible source location | ||
103 | // for a diagnostic's attribute path within the given body. | ||
104 | // | ||
105 | // Backing out from a path back to a source location is not always entirely | ||
106 | // possible because we lose some information in the decoding process, so | ||
107 | // if an exact position cannot be found then the returned diagnostic will | ||
108 | // refer to a position somewhere within the containing body, which is assumed | ||
109 | // to be better than no location at all. | ||
110 | // | ||
111 | // If possible it is generally better to report an error at a layer where | ||
112 | // source location information is still available, for more accuracy. This | ||
113 | // is not always possible due to system architecture, so this serves as a | ||
114 | // "best effort" fallback behavior for such situations. | ||
115 | func (d *attributeDiagnostic) ElaborateFromConfigBody(body hcl.Body) Diagnostic { | ||
116 | if len(d.attrPath) < 1 { | ||
117 | // Should never happen, but we'll allow it rather than crashing. | ||
118 | return d | ||
119 | } | ||
120 | |||
121 | if d.subject != nil { | ||
122 | // Don't modify an already-elaborated diagnostic. | ||
123 | return d | ||
124 | } | ||
125 | |||
126 | ret := *d | ||
127 | |||
128 | // This function will often end up re-decoding values that were already | ||
129 | // decoded by an earlier step. This is non-ideal but is architecturally | ||
130 | // more convenient than arranging for source location information to be | ||
131 | // propagated to every place in Terraform, and this happens only in the | ||
132 | // presence of errors where performance isn't a concern. | ||
133 | |||
134 | traverse := d.attrPath[:] | ||
135 | final := d.attrPath[len(d.attrPath)-1] | ||
136 | |||
137 | // Index should never be the first step | ||
138 | // as indexing of top blocks (such as resources & data sources) | ||
139 | // is handled elsewhere | ||
140 | if _, isIdxStep := traverse[0].(cty.IndexStep); isIdxStep { | ||
141 | subject := SourceRangeFromHCL(body.MissingItemRange()) | ||
142 | ret.subject = &subject | ||
143 | return &ret | ||
144 | } | ||
145 | |||
146 | // Process index separately | ||
147 | idxStep, hasIdx := final.(cty.IndexStep) | ||
148 | if hasIdx { | ||
149 | final = d.attrPath[len(d.attrPath)-2] | ||
150 | traverse = d.attrPath[:len(d.attrPath)-1] | ||
151 | } | ||
152 | |||
153 | // If we have more than one step after removing index | ||
154 | // then we'll first try to traverse to a child body | ||
155 | // corresponding to the requested path. | ||
156 | if len(traverse) > 1 { | ||
157 | body = traversePathSteps(traverse, body) | ||
158 | } | ||
159 | |||
160 | // Default is to indicate a missing item in the deepest body we reached | ||
161 | // while traversing. | ||
162 | subject := SourceRangeFromHCL(body.MissingItemRange()) | ||
163 | ret.subject = &subject | ||
164 | |||
165 | // Once we get here, "final" should be a GetAttr step that maps to an | ||
166 | // attribute in our current body. | ||
167 | finalStep, isAttr := final.(cty.GetAttrStep) | ||
168 | if !isAttr { | ||
169 | return &ret | ||
170 | } | ||
171 | |||
172 | content, _, contentDiags := body.PartialContent(&hcl.BodySchema{ | ||
173 | Attributes: []hcl.AttributeSchema{ | ||
174 | { | ||
175 | Name: finalStep.Name, | ||
176 | Required: true, | ||
177 | }, | ||
178 | }, | ||
179 | }) | ||
180 | if contentDiags.HasErrors() { | ||
181 | return &ret | ||
182 | } | ||
183 | |||
184 | if attr, ok := content.Attributes[finalStep.Name]; ok { | ||
185 | hclRange := attr.Expr.Range() | ||
186 | if hasIdx { | ||
187 | // Try to be more precise by finding index range | ||
188 | hclRange = hclRangeFromIndexStepAndAttribute(idxStep, attr) | ||
189 | } | ||
190 | subject = SourceRangeFromHCL(hclRange) | ||
191 | ret.subject = &subject | ||
192 | } | ||
193 | |||
194 | return &ret | ||
195 | } | ||
196 | |||
197 | func traversePathSteps(traverse []cty.PathStep, body hcl.Body) hcl.Body { | ||
198 | for i := 0; i < len(traverse); i++ { | ||
199 | step := traverse[i] | ||
200 | |||
201 | switch tStep := step.(type) { | ||
202 | case cty.GetAttrStep: | ||
203 | |||
204 | var next cty.PathStep | ||
205 | if i < (len(traverse) - 1) { | ||
206 | next = traverse[i+1] | ||
207 | } | ||
208 | |||
209 | // Will be indexing into our result here? | ||
210 | var indexType cty.Type | ||
211 | var indexVal cty.Value | ||
212 | if nextIndex, ok := next.(cty.IndexStep); ok { | ||
213 | indexVal = nextIndex.Key | ||
214 | indexType = indexVal.Type() | ||
215 | i++ // skip over the index on subsequent iterations | ||
216 | } | ||
217 | |||
218 | var blockLabelNames []string | ||
219 | if indexType == cty.String { | ||
220 | // Map traversal means we expect one label for the key. | ||
221 | blockLabelNames = []string{"key"} | ||
222 | } | ||
223 | |||
224 | // For intermediate steps we expect to be referring to a child | ||
225 | // block, so we'll attempt decoding under that assumption. | ||
226 | content, _, contentDiags := body.PartialContent(&hcl.BodySchema{ | ||
227 | Blocks: []hcl.BlockHeaderSchema{ | ||
228 | { | ||
229 | Type: tStep.Name, | ||
230 | LabelNames: blockLabelNames, | ||
231 | }, | ||
232 | }, | ||
233 | }) | ||
234 | if contentDiags.HasErrors() { | ||
235 | return body | ||
236 | } | ||
237 | filtered := make([]*hcl.Block, 0, len(content.Blocks)) | ||
238 | for _, block := range content.Blocks { | ||
239 | if block.Type == tStep.Name { | ||
240 | filtered = append(filtered, block) | ||
241 | } | ||
242 | } | ||
243 | if len(filtered) == 0 { | ||
244 | // Step doesn't refer to a block | ||
245 | continue | ||
246 | } | ||
247 | |||
248 | switch indexType { | ||
249 | case cty.NilType: // no index at all | ||
250 | if len(filtered) != 1 { | ||
251 | return body | ||
252 | } | ||
253 | body = filtered[0].Body | ||
254 | case cty.Number: | ||
255 | var idx int | ||
256 | err := gocty.FromCtyValue(indexVal, &idx) | ||
257 | if err != nil || idx >= len(filtered) { | ||
258 | return body | ||
259 | } | ||
260 | body = filtered[idx].Body | ||
261 | case cty.String: | ||
262 | key := indexVal.AsString() | ||
263 | var block *hcl.Block | ||
264 | for _, candidate := range filtered { | ||
265 | if candidate.Labels[0] == key { | ||
266 | block = candidate | ||
267 | break | ||
268 | } | ||
269 | } | ||
270 | if block == nil { | ||
271 | // No block with this key, so we'll just indicate a | ||
272 | // missing item in the containing block. | ||
273 | return body | ||
274 | } | ||
275 | body = block.Body | ||
276 | default: | ||
277 | // Should never happen, because only string and numeric indices | ||
278 | // are supported by cty collections. | ||
279 | return body | ||
280 | } | ||
281 | |||
282 | default: | ||
283 | // For any other kind of step, we'll just return our current body | ||
284 | // as the subject and accept that this is a little inaccurate. | ||
285 | return body | ||
286 | } | ||
287 | } | ||
288 | return body | ||
289 | } | ||
290 | |||
291 | func hclRangeFromIndexStepAndAttribute(idxStep cty.IndexStep, attr *hcl.Attribute) hcl.Range { | ||
292 | switch idxStep.Key.Type() { | ||
293 | case cty.Number: | ||
294 | var idx int | ||
295 | err := gocty.FromCtyValue(idxStep.Key, &idx) | ||
296 | items, diags := hcl.ExprList(attr.Expr) | ||
297 | if diags.HasErrors() { | ||
298 | return attr.Expr.Range() | ||
299 | } | ||
300 | if err != nil || idx >= len(items) { | ||
301 | return attr.NameRange | ||
302 | } | ||
303 | return items[idx].Range() | ||
304 | case cty.String: | ||
305 | pairs, diags := hcl.ExprMap(attr.Expr) | ||
306 | if diags.HasErrors() { | ||
307 | return attr.Expr.Range() | ||
308 | } | ||
309 | stepKey := idxStep.Key.AsString() | ||
310 | for _, kvPair := range pairs { | ||
311 | key, err := kvPair.Key.Value(nil) | ||
312 | if err != nil { | ||
313 | return attr.Expr.Range() | ||
314 | } | ||
315 | if key.AsString() == stepKey { | ||
316 | startRng := kvPair.Value.StartRange() | ||
317 | return startRng | ||
318 | } | ||
319 | } | ||
320 | return attr.NameRange | ||
321 | } | ||
322 | return attr.Expr.Range() | ||
323 | } | ||
324 | |||
325 | func (d *attributeDiagnostic) Source() Source { | ||
326 | return Source{ | ||
327 | Subject: d.subject, | ||
328 | } | ||
329 | } | ||
330 | |||
331 | // WholeContainingBody returns a diagnostic about the body that is an implied | ||
332 | // current configuration context. This should be returned only from | ||
333 | // functions whose interface specifies a clear configuration context that this | ||
334 | // will be resolved in. | ||
335 | // | ||
336 | // The returned attribute will not have source location information until | ||
337 | // context is applied to the containing diagnostics using diags.InConfigBody. | ||
338 | // After context is applied, the source location is currently the missing item | ||
339 | // range of the body. In future, this may change to some other suitable | ||
340 | // part of the containing body. | ||
341 | func WholeContainingBody(severity Severity, summary, detail string) Diagnostic { | ||
342 | return &wholeBodyDiagnostic{ | ||
343 | diagnosticBase: diagnosticBase{ | ||
344 | severity: severity, | ||
345 | summary: summary, | ||
346 | detail: detail, | ||
347 | }, | ||
348 | } | ||
349 | } | ||
350 | |||
351 | type wholeBodyDiagnostic struct { | ||
352 | diagnosticBase | ||
353 | subject *SourceRange // populated only after ElaborateFromConfigBody | ||
354 | } | ||
355 | |||
356 | func (d *wholeBodyDiagnostic) ElaborateFromConfigBody(body hcl.Body) Diagnostic { | ||
357 | if d.subject != nil { | ||
358 | // Don't modify an already-elaborated diagnostic. | ||
359 | return d | ||
360 | } | ||
361 | |||
362 | ret := *d | ||
363 | rng := SourceRangeFromHCL(body.MissingItemRange()) | ||
364 | ret.subject = &rng | ||
365 | return &ret | ||
366 | } | ||
367 | |||
368 | func (d *wholeBodyDiagnostic) Source() Source { | ||
369 | return Source{ | ||
370 | Subject: d.subject, | ||
371 | } | ||
372 | } | ||
diff --git a/vendor/github.com/hashicorp/terraform/tfdiags/diagnostic.go b/vendor/github.com/hashicorp/terraform/tfdiags/diagnostic.go index 2c23f76..c91ba9a 100644 --- a/vendor/github.com/hashicorp/terraform/tfdiags/diagnostic.go +++ b/vendor/github.com/hashicorp/terraform/tfdiags/diagnostic.go | |||
@@ -1,9 +1,18 @@ | |||
1 | package tfdiags | 1 | package tfdiags |
2 | 2 | ||
3 | import ( | ||
4 | "github.com/hashicorp/hcl2/hcl" | ||
5 | ) | ||
6 | |||
3 | type Diagnostic interface { | 7 | type Diagnostic interface { |
4 | Severity() Severity | 8 | Severity() Severity |
5 | Description() Description | 9 | Description() Description |
6 | Source() Source | 10 | Source() Source |
11 | |||
12 | // FromExpr returns the expression-related context for the diagnostic, if | ||
13 | // available. Returns nil if the diagnostic is not related to an | ||
14 | // expression evaluation. | ||
15 | FromExpr() *FromExpr | ||
7 | } | 16 | } |
8 | 17 | ||
9 | type Severity rune | 18 | type Severity rune |
@@ -24,3 +33,8 @@ type Source struct { | |||
24 | Subject *SourceRange | 33 | Subject *SourceRange |
25 | Context *SourceRange | 34 | Context *SourceRange |
26 | } | 35 | } |
36 | |||
37 | type FromExpr struct { | ||
38 | Expression hcl.Expression | ||
39 | EvalContext *hcl.EvalContext | ||
40 | } | ||
diff --git a/vendor/github.com/hashicorp/terraform/tfdiags/diagnostic_base.go b/vendor/github.com/hashicorp/terraform/tfdiags/diagnostic_base.go new file mode 100644 index 0000000..50bf9d8 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform/tfdiags/diagnostic_base.go | |||
@@ -0,0 +1,31 @@ | |||
1 | package tfdiags | ||
2 | |||
3 | // diagnosticBase can be embedded in other diagnostic structs to get | ||
4 | // default implementations of Severity and Description. This type also | ||
5 | // has default implementations of Source and FromExpr that return no source | ||
6 | // location or expression-related information, so embedders should generally | ||
7 | // override those method to return more useful results where possible. | ||
8 | type diagnosticBase struct { | ||
9 | severity Severity | ||
10 | summary string | ||
11 | detail string | ||
12 | } | ||
13 | |||
14 | func (d diagnosticBase) Severity() Severity { | ||
15 | return d.severity | ||
16 | } | ||
17 | |||
18 | func (d diagnosticBase) Description() Description { | ||
19 | return Description{ | ||
20 | Summary: d.summary, | ||
21 | Detail: d.detail, | ||
22 | } | ||
23 | } | ||
24 | |||
25 | func (d diagnosticBase) Source() Source { | ||
26 | return Source{} | ||
27 | } | ||
28 | |||
29 | func (d diagnosticBase) FromExpr() *FromExpr { | ||
30 | return nil | ||
31 | } | ||
diff --git a/vendor/github.com/hashicorp/terraform/tfdiags/diagnostics.go b/vendor/github.com/hashicorp/terraform/tfdiags/diagnostics.go index 667ba80..465b230 100644 --- a/vendor/github.com/hashicorp/terraform/tfdiags/diagnostics.go +++ b/vendor/github.com/hashicorp/terraform/tfdiags/diagnostics.go | |||
@@ -3,6 +3,9 @@ package tfdiags | |||
3 | import ( | 3 | import ( |
4 | "bytes" | 4 | "bytes" |
5 | "fmt" | 5 | "fmt" |
6 | "path/filepath" | ||
7 | "sort" | ||
8 | "strings" | ||
6 | 9 | ||
7 | "github.com/hashicorp/errwrap" | 10 | "github.com/hashicorp/errwrap" |
8 | multierror "github.com/hashicorp/go-multierror" | 11 | multierror "github.com/hashicorp/go-multierror" |
@@ -54,6 +57,8 @@ func (diags Diagnostics) Append(new ...interface{}) Diagnostics { | |||
54 | diags = append(diags, ti...) // flatten | 57 | diags = append(diags, ti...) // flatten |
55 | case diagnosticsAsError: | 58 | case diagnosticsAsError: |
56 | diags = diags.Append(ti.Diagnostics) // unwrap | 59 | diags = diags.Append(ti.Diagnostics) // unwrap |
60 | case NonFatalError: | ||
61 | diags = diags.Append(ti.Diagnostics) // unwrap | ||
57 | case hcl.Diagnostics: | 62 | case hcl.Diagnostics: |
58 | for _, hclDiag := range ti { | 63 | for _, hclDiag := range ti { |
59 | diags = append(diags, hclDiagnostic{hclDiag}) | 64 | diags = append(diags, hclDiagnostic{hclDiag}) |
@@ -136,6 +141,54 @@ func (diags Diagnostics) Err() error { | |||
136 | return diagnosticsAsError{diags} | 141 | return diagnosticsAsError{diags} |
137 | } | 142 | } |
138 | 143 | ||
144 | // ErrWithWarnings is similar to Err except that it will also return a non-nil | ||
145 | // error if the receiver contains only warnings. | ||
146 | // | ||
147 | // In the warnings-only situation, the result is guaranteed to be of dynamic | ||
148 | // type NonFatalError, allowing diagnostics-aware callers to type-assert | ||
149 | // and unwrap it, treating it as non-fatal. | ||
150 | // | ||
151 | // This should be used only in contexts where the caller is able to recognize | ||
152 | // and handle NonFatalError. For normal callers that expect a lack of errors | ||
153 | // to be signaled by nil, use just Diagnostics.Err. | ||
154 | func (diags Diagnostics) ErrWithWarnings() error { | ||
155 | if len(diags) == 0 { | ||
156 | return nil | ||
157 | } | ||
158 | if diags.HasErrors() { | ||
159 | return diags.Err() | ||
160 | } | ||
161 | return NonFatalError{diags} | ||
162 | } | ||
163 | |||
164 | // NonFatalErr is similar to Err except that it always returns either nil | ||
165 | // (if there are no diagnostics at all) or NonFatalError. | ||
166 | // | ||
167 | // This allows diagnostics to be returned over an error return channel while | ||
168 | // being explicit that the diagnostics should not halt processing. | ||
169 | // | ||
170 | // This should be used only in contexts where the caller is able to recognize | ||
171 | // and handle NonFatalError. For normal callers that expect a lack of errors | ||
172 | // to be signaled by nil, use just Diagnostics.Err. | ||
173 | func (diags Diagnostics) NonFatalErr() error { | ||
174 | if len(diags) == 0 { | ||
175 | return nil | ||
176 | } | ||
177 | return NonFatalError{diags} | ||
178 | } | ||
179 | |||
180 | // Sort applies an ordering to the diagnostics in the receiver in-place. | ||
181 | // | ||
182 | // The ordering is: warnings before errors, sourceless before sourced, | ||
183 | // short source paths before long source paths, and then ordering by | ||
184 | // position within each file. | ||
185 | // | ||
186 | // Diagnostics that do not differ by any of these sortable characteristics | ||
187 | // will remain in the same relative order after this method returns. | ||
188 | func (diags Diagnostics) Sort() { | ||
189 | sort.Stable(sortDiagnostics(diags)) | ||
190 | } | ||
191 | |||
139 | type diagnosticsAsError struct { | 192 | type diagnosticsAsError struct { |
140 | Diagnostics | 193 | Diagnostics |
141 | } | 194 | } |
@@ -179,3 +232,99 @@ func (dae diagnosticsAsError) WrappedErrors() []error { | |||
179 | } | 232 | } |
180 | return errs | 233 | return errs |
181 | } | 234 | } |
235 | |||
236 | // NonFatalError is a special error type, returned by | ||
237 | // Diagnostics.ErrWithWarnings and Diagnostics.NonFatalErr, | ||
238 | // that indicates that the wrapped diagnostics should be treated as non-fatal. | ||
239 | // Callers can conditionally type-assert an error to this type in order to | ||
240 | // detect the non-fatal scenario and handle it in a different way. | ||
241 | type NonFatalError struct { | ||
242 | Diagnostics | ||
243 | } | ||
244 | |||
245 | func (woe NonFatalError) Error() string { | ||
246 | diags := woe.Diagnostics | ||
247 | switch { | ||
248 | case len(diags) == 0: | ||
249 | // should never happen, since we don't create this wrapper if | ||
250 | // there are no diagnostics in the list. | ||
251 | return "no errors or warnings" | ||
252 | case len(diags) == 1: | ||
253 | desc := diags[0].Description() | ||
254 | if desc.Detail == "" { | ||
255 | return desc.Summary | ||
256 | } | ||
257 | return fmt.Sprintf("%s: %s", desc.Summary, desc.Detail) | ||
258 | default: | ||
259 | var ret bytes.Buffer | ||
260 | if diags.HasErrors() { | ||
261 | fmt.Fprintf(&ret, "%d problems:\n", len(diags)) | ||
262 | } else { | ||
263 | fmt.Fprintf(&ret, "%d warnings:\n", len(diags)) | ||
264 | } | ||
265 | for _, diag := range woe.Diagnostics { | ||
266 | desc := diag.Description() | ||
267 | if desc.Detail == "" { | ||
268 | fmt.Fprintf(&ret, "\n- %s", desc.Summary) | ||
269 | } else { | ||
270 | fmt.Fprintf(&ret, "\n- %s: %s", desc.Summary, desc.Detail) | ||
271 | } | ||
272 | } | ||
273 | return ret.String() | ||
274 | } | ||
275 | } | ||
276 | |||
277 | // sortDiagnostics is an implementation of sort.Interface | ||
278 | type sortDiagnostics []Diagnostic | ||
279 | |||
280 | var _ sort.Interface = sortDiagnostics(nil) | ||
281 | |||
282 | func (sd sortDiagnostics) Len() int { | ||
283 | return len(sd) | ||
284 | } | ||
285 | |||
286 | func (sd sortDiagnostics) Less(i, j int) bool { | ||
287 | iD, jD := sd[i], sd[j] | ||
288 | iSev, jSev := iD.Severity(), jD.Severity() | ||
289 | iSrc, jSrc := iD.Source(), jD.Source() | ||
290 | |||
291 | switch { | ||
292 | |||
293 | case iSev != jSev: | ||
294 | return iSev == Warning | ||
295 | |||
296 | case (iSrc.Subject == nil) != (jSrc.Subject == nil): | ||
297 | return iSrc.Subject == nil | ||
298 | |||
299 | case iSrc.Subject != nil && *iSrc.Subject != *jSrc.Subject: | ||
300 | iSubj := iSrc.Subject | ||
301 | jSubj := jSrc.Subject | ||
302 | switch { | ||
303 | case iSubj.Filename != jSubj.Filename: | ||
304 | // Path with fewer segments goes first if they are different lengths | ||
305 | sep := string(filepath.Separator) | ||
306 | iCount := strings.Count(iSubj.Filename, sep) | ||
307 | jCount := strings.Count(jSubj.Filename, sep) | ||
308 | if iCount != jCount { | ||
309 | return iCount < jCount | ||
310 | } | ||
311 | return iSubj.Filename < jSubj.Filename | ||
312 | case iSubj.Start.Byte != jSubj.Start.Byte: | ||
313 | return iSubj.Start.Byte < jSubj.Start.Byte | ||
314 | case iSubj.End.Byte != jSubj.End.Byte: | ||
315 | return iSubj.End.Byte < jSubj.End.Byte | ||
316 | } | ||
317 | fallthrough | ||
318 | |||
319 | default: | ||
320 | // The remaining properties do not have a defined ordering, so | ||
321 | // we'll leave it unspecified. Since we use sort.Stable in | ||
322 | // the caller of this, the ordering of remaining items will | ||
323 | // be preserved. | ||
324 | return false | ||
325 | } | ||
326 | } | ||
327 | |||
328 | func (sd sortDiagnostics) Swap(i, j int) { | ||
329 | sd[i], sd[j] = sd[j], sd[i] | ||
330 | } | ||
diff --git a/vendor/github.com/hashicorp/terraform/tfdiags/error.go b/vendor/github.com/hashicorp/terraform/tfdiags/error.go index 35edc30..13f7a71 100644 --- a/vendor/github.com/hashicorp/terraform/tfdiags/error.go +++ b/vendor/github.com/hashicorp/terraform/tfdiags/error.go | |||
@@ -13,7 +13,7 @@ func (e nativeError) Severity() Severity { | |||
13 | 13 | ||
14 | func (e nativeError) Description() Description { | 14 | func (e nativeError) Description() Description { |
15 | return Description{ | 15 | return Description{ |
16 | Summary: e.err.Error(), | 16 | Summary: FormatError(e.err), |
17 | } | 17 | } |
18 | } | 18 | } |
19 | 19 | ||
@@ -21,3 +21,8 @@ func (e nativeError) Source() Source { | |||
21 | // No source information available for a native error | 21 | // No source information available for a native error |
22 | return Source{} | 22 | return Source{} |
23 | } | 23 | } |
24 | |||
25 | func (e nativeError) FromExpr() *FromExpr { | ||
26 | // Native errors are not expression-related | ||
27 | return nil | ||
28 | } | ||
diff --git a/vendor/github.com/hashicorp/terraform/tfdiags/hcl.go b/vendor/github.com/hashicorp/terraform/tfdiags/hcl.go index 24851f4..f9aec41 100644 --- a/vendor/github.com/hashicorp/terraform/tfdiags/hcl.go +++ b/vendor/github.com/hashicorp/terraform/tfdiags/hcl.go | |||
@@ -40,6 +40,16 @@ func (d hclDiagnostic) Source() Source { | |||
40 | return ret | 40 | return ret |
41 | } | 41 | } |
42 | 42 | ||
43 | func (d hclDiagnostic) FromExpr() *FromExpr { | ||
44 | if d.diag.Expression == nil || d.diag.EvalContext == nil { | ||
45 | return nil | ||
46 | } | ||
47 | return &FromExpr{ | ||
48 | Expression: d.diag.Expression, | ||
49 | EvalContext: d.diag.EvalContext, | ||
50 | } | ||
51 | } | ||
52 | |||
43 | // SourceRangeFromHCL constructs a SourceRange from the corresponding range | 53 | // SourceRangeFromHCL constructs a SourceRange from the corresponding range |
44 | // type within the HCL package. | 54 | // type within the HCL package. |
45 | func SourceRangeFromHCL(hclRange hcl.Range) SourceRange { | 55 | func SourceRangeFromHCL(hclRange hcl.Range) SourceRange { |
diff --git a/vendor/github.com/hashicorp/terraform/tfdiags/rpc_friendly.go b/vendor/github.com/hashicorp/terraform/tfdiags/rpc_friendly.go index 6cc95cc..485063b 100644 --- a/vendor/github.com/hashicorp/terraform/tfdiags/rpc_friendly.go +++ b/vendor/github.com/hashicorp/terraform/tfdiags/rpc_friendly.go | |||
@@ -48,6 +48,12 @@ func (d *rpcFriendlyDiag) Source() Source { | |||
48 | } | 48 | } |
49 | } | 49 | } |
50 | 50 | ||
51 | func (d rpcFriendlyDiag) FromExpr() *FromExpr { | ||
52 | // RPC-friendly diagnostics cannot preserve expression information because | ||
53 | // expressions themselves are not RPC-friendly. | ||
54 | return nil | ||
55 | } | ||
56 | |||
51 | func init() { | 57 | func init() { |
52 | gob.Register((*rpcFriendlyDiag)(nil)) | 58 | gob.Register((*rpcFriendlyDiag)(nil)) |
53 | } | 59 | } |
diff --git a/vendor/github.com/hashicorp/terraform/tfdiags/severity_string.go b/vendor/github.com/hashicorp/terraform/tfdiags/severity_string.go index 0b1249b..78a7210 100644 --- a/vendor/github.com/hashicorp/terraform/tfdiags/severity_string.go +++ b/vendor/github.com/hashicorp/terraform/tfdiags/severity_string.go | |||
@@ -4,6 +4,14 @@ package tfdiags | |||
4 | 4 | ||
5 | import "strconv" | 5 | import "strconv" |
6 | 6 | ||
7 | func _() { | ||
8 | // An "invalid array index" compiler error signifies that the constant values have changed. | ||
9 | // Re-run the stringer command to generate them again. | ||
10 | var x [1]struct{} | ||
11 | _ = x[Error-69] | ||
12 | _ = x[Warning-87] | ||
13 | } | ||
14 | |||
7 | const ( | 15 | const ( |
8 | _Severity_name_0 = "Error" | 16 | _Severity_name_0 = "Error" |
9 | _Severity_name_1 = "Warning" | 17 | _Severity_name_1 = "Warning" |
diff --git a/vendor/github.com/hashicorp/terraform/tfdiags/simple_warning.go b/vendor/github.com/hashicorp/terraform/tfdiags/simple_warning.go index fb3ac98..b0f1ecd 100644 --- a/vendor/github.com/hashicorp/terraform/tfdiags/simple_warning.go +++ b/vendor/github.com/hashicorp/terraform/tfdiags/simple_warning.go | |||
@@ -20,6 +20,11 @@ func (e simpleWarning) Description() Description { | |||
20 | } | 20 | } |
21 | 21 | ||
22 | func (e simpleWarning) Source() Source { | 22 | func (e simpleWarning) Source() Source { |
23 | // No source information available for a native error | 23 | // No source information available for a simple warning |
24 | return Source{} | 24 | return Source{} |
25 | } | 25 | } |
26 | |||
27 | func (e simpleWarning) FromExpr() *FromExpr { | ||
28 | // Simple warnings are not expression-related | ||
29 | return nil | ||
30 | } | ||
diff --git a/vendor/github.com/hashicorp/terraform/tfdiags/sourceless.go b/vendor/github.com/hashicorp/terraform/tfdiags/sourceless.go new file mode 100644 index 0000000..eaa2737 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform/tfdiags/sourceless.go | |||
@@ -0,0 +1,13 @@ | |||
1 | package tfdiags | ||
2 | |||
3 | // Sourceless creates and returns a diagnostic with no source location | ||
4 | // information. This is generally used for operational-type errors that are | ||
5 | // caused by or relate to the environment where Terraform is running rather | ||
6 | // than to the provided configuration. | ||
7 | func Sourceless(severity Severity, summary, detail string) Diagnostic { | ||
8 | return diagnosticBase{ | ||
9 | severity: severity, | ||
10 | summary: summary, | ||
11 | detail: detail, | ||
12 | } | ||
13 | } | ||