mirror of
https://github.com/opentffoundation/opentf.git
synced 2025-12-19 17:59:05 -05:00
docs: Diagnostics Guide
Signed-off-by: Martin Atkins <mart@degeneration.co.uk>
This commit is contained in:
@@ -25,6 +25,9 @@ to [the main OpenTofu CLI documentation](https://opentofu.org/docs/cli/index.htm
|
||||
instead; it presents similar information from the perspective of the SDK
|
||||
API, rather than the plugin wire protocol.)
|
||||
|
||||
* [Diagnostics](./diagnostics): how we report errors and warnings to end-users
|
||||
in OpenTofu.
|
||||
|
||||
* [Plugin Protocol](./plugin-protocol/): gRPC/protobuf definitions for the
|
||||
plugin wire protocol and information about its versioning strategy.
|
||||
|
||||
|
||||
495
docs/diagnostics.md
Normal file
495
docs/diagnostics.md
Normal file
@@ -0,0 +1,495 @@
|
||||
# OpenTofu Diagnostics Guide
|
||||
|
||||
"Diagnostics" is the general term we use to describe the error and warning
|
||||
messages that OpenTofu returns when there are problems with the configuration,
|
||||
or when interactions with external systems fail.
|
||||
|
||||
This document is an overview of how we typically use diagnostics in OpenTofu.
|
||||
It includes both some technical information about how we represent diagnostics
|
||||
in code, and some more subjective information about the writing style we most
|
||||
often use in diagnostic messages.
|
||||
|
||||
## Diagnostics in Code
|
||||
|
||||
Diagnostics are modelled using the types from
|
||||
[the `tfdiags` package](https://pkg.go.dev/github.com/opentofu/opentofu/internal/tfdiags).
|
||||
|
||||
In particular:
|
||||
- [`tfdiags.Diagnostics`](https://pkg.go.dev/github.com/opentofu/opentofu/internal/tfdiags#Diagnostics)
|
||||
represents a set of zero or more diagnostics.
|
||||
|
||||
A total lack of diagnostics is usually represented by a `nil` value of this
|
||||
type.
|
||||
|
||||
When constructing sets of diagnostics to return we typically don't worry
|
||||
about the order they are returned in, even though we return them using a
|
||||
slice type. The UI-layer code uses
|
||||
[`tfdiags.Diagnostics.Sort`](https://pkg.go.dev/github.com/opentofu/opentofu/internal/tfdiags#Diagnostics.Sort)
|
||||
to place all of the collected diagnostics into a predictable order before
|
||||
rendering them, and so that function effectively turns the set of
|
||||
diagnostics into an ordered list of diagnostics _just in time_.
|
||||
|
||||
- [`tfdiags.Diagnostic`](https://pkg.go.dev/github.com/opentofu/opentofu/internal/tfdiags#Diagnostic)
|
||||
is an interface type that all diagnostic values implement.
|
||||
|
||||
In practice values of this type are often created automatically as an
|
||||
implementation detail of [`Diagnostics.Append`](https://pkg.go.dev/github.com/opentofu/opentofu/internal/tfdiags#Diagnostics.Append),
|
||||
which accepts various types that _don't_ directly implement
|
||||
`Diagnostic` and then automatically wraps them in a type that does.
|
||||
In particular:
|
||||
|
||||
- We often use [`hcl.Diagnostic`](https://pkg.go.dev/github.com/hashicorp/hcl/v2#Diagnostic)
|
||||
to describe problems related to the configuration or operations that are
|
||||
strongly related to parts of the configuration, because it is the most
|
||||
fully-fledged type of diagnostic we allow including support for source
|
||||
ranges and relevant expressions as described later.
|
||||
|
||||
It's also acceptable to append a whole `hcl.Diagnostics` (the HCL
|
||||
equivalent of `tfdiags.Diagnostics`) in which case each diagnostic
|
||||
will be wrapped and appended in turn. This is common when calling
|
||||
HCL's own functions and passing on its diagnostics verbatim.
|
||||
- Normal `error` values can be appended to a `tfdiags.Diagnostics`, but
|
||||
that's mainly for historical reasons -- adapting code that was present
|
||||
before the diagnostic models were added -- and should not be used in new
|
||||
code because it typically results in low-quality diagnostics that don't
|
||||
meet the style guidelines later in this document.
|
||||
|
||||
One exception is for "should never happen" cases: we sometimes use
|
||||
`error` directly in that case to avoid overwhelming the surrounding
|
||||
code with the construction of a full diagnostic.
|
||||
|
||||
Package `tfdiags` also includes some functions for constructing other kinds
|
||||
of diagnostics, including:
|
||||
|
||||
- [`tfdiags.Sourceless`](https://pkg.go.dev/github.com/opentofu/opentofu/internal/tfdiags#Sourceless)
|
||||
is good for diagnostics that don't relate to any part of the configuration,
|
||||
such as when reporting incorrect usage of a command line argument.
|
||||
- [`tfdiags.AttributeValue`](https://pkg.go.dev/github.com/opentofu/opentofu/internal/tfdiags#AttributeValue) and
|
||||
[`tfdiags.WholeContainingBody`](https://pkg.go.dev/github.com/opentofu/opentofu/internal/tfdiags#WholeContainingBody)
|
||||
produce special "contextual diagnostics" that must be transformed by
|
||||
calling [`InConfigBody`](https://pkg.go.dev/github.com/opentofu/opentofu/internal/tfdiags#Diagnostics.InConfigBody)
|
||||
on the resulting `Diagnostics` value. This is a special mechanism used
|
||||
when the subsystem generating the diagnostic does not have direct access
|
||||
to the configuration itself, such as when a provider returns a diagnostic
|
||||
via the provider wire protocol.
|
||||
- [`tfdiags.Severity`](https://pkg.go.dev/github.com/opentofu/opentofu/internal/tfdiags#Severity)
|
||||
(and its HCL equivalent [`hcl.DiagnosticSeverity`](https://pkg.go.dev/github.com/hashicorp/hcl/v2#DiagnosticSeverity))
|
||||
are how we distinguish between "error" and "warning" diagnostics.
|
||||
|
||||
The `tfdiags.Diagnostics.HasErrors` method returns true if the diagnostics
|
||||
contains at least one with the severity `tfdiags.Error`.
|
||||
|
||||
The most common pattern for handling diagnostics in code is:
|
||||
1. Declare `var diags tfdiags.Diagnostics` at the very start of a function.
|
||||
2. During the function's body, whenever calling another function that might
|
||||
produce its own diagnostics, capture them into a separate variable
|
||||
(often called `moreDiags`, or `hclDiags` if the return type is
|
||||
`hcl.Diagnostics`) and then immediately append them to the main `diags`
|
||||
using `tfdiags.Diagnostics.Append`.
|
||||
|
||||
If subsequent code depends on the success of the call, check
|
||||
`moreDiags.HasErrors()` (or similar) and return early if it returns `true`.
|
||||
3. If the function generates any diagnostics of its own, append them directly
|
||||
to `diags`.
|
||||
4. At all exit points of the function, return `diags` regardless of whether
|
||||
it has been assigned to or whether it contains errors. This ensures that
|
||||
we always return any warnings that might have been produced and avoids
|
||||
the risk of missing certain return paths under future maintenence if we
|
||||
introduce additional diagnostics later.
|
||||
|
||||
Here's a code-example version of the above advice:
|
||||
|
||||
```go
|
||||
func Example() (anything, tfdiags.Diagnostics) {
|
||||
var diags tfdiags.Diagnostics
|
||||
|
||||
somethingElse, moreDiags := otherFunction()
|
||||
diags = diags.Append(moreDiags)
|
||||
if moreDiags.HasErrors() {
|
||||
// NOTE: it isn't _always_ necessary to return immediately when there
|
||||
// are errors, as long as the callee clearly documents what it
|
||||
// guarantees about an errored result and the caller is able to
|
||||
// work within those limitations. Collecting multiple errors to
|
||||
// return together is often desirable.
|
||||
//
|
||||
// If the caller cannot continue at all though, or if continuing is
|
||||
// likely to cause redundant errors that just restate the same problem
|
||||
// in more confusing terms, then...
|
||||
return nil, diags
|
||||
}
|
||||
if isProblematic(somethingElse) {
|
||||
// A function might need to generate its own diagnostics if it detects
|
||||
// a problem directly.
|
||||
diags = diags.Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
// ...
|
||||
})
|
||||
return nil, diags
|
||||
}
|
||||
|
||||
// ...
|
||||
|
||||
// The final return statement should include diags even if no errors
|
||||
// were detected along the way, because it might contain warnings.
|
||||
return something, diags
|
||||
}
|
||||
```
|
||||
|
||||
Some functions diverge from this pattern for special reasons, such as capturing
|
||||
multiple sets of child function diagnostics and then using some logic to decide
|
||||
which ones to append, or processing multiple items in a loop and appending
|
||||
new diagnostics for each iteration. The above is just a general example of the
|
||||
most common case, not a fixed template to follow in all cases.
|
||||
|
||||
## Information in a Diagnostic
|
||||
|
||||
The general model of `tfdiags.Diagnostic` has the following parts, though not
|
||||
all implementations of the interface make use of all of them:
|
||||
|
||||
- Severity: either `tfdiags.Error` or `tfdiags.Warning`.
|
||||
- Description: the main human-readable text describing the problem. This
|
||||
has the following fields:
|
||||
|
||||
- Summary: A short, terse description of the general type of problem
|
||||
that has occurred.
|
||||
- Detail: A longer description of the problem, sometimes including multiple
|
||||
paragraphs of information.
|
||||
- Address: The address of some object that the error relates to, which
|
||||
is most often a resource instance address.
|
||||
|
||||
OpenTofu does not currently have a localized UI, so built-in diagnostics
|
||||
always have their summary and detail written in US English. There's more
|
||||
subjective guidance about the content of these fields in sections below.
|
||||
- Source location information: optional references to parts of the configuration
|
||||
that the problem relates to. This has the following fields:
|
||||
|
||||
- Subject: source range for the part of the configuration that caused the
|
||||
problem or that the problem is directly about.
|
||||
- Context: optional source range of a larger section of configuration that
|
||||
might make the cause of the problem easier to quickly understand if
|
||||
included in the diagnostic message. The Context source range must always
|
||||
contain the Subject source range within it.
|
||||
|
||||
The UI uses the context and subject together to display a source code
|
||||
snippet. The lines of code included in the snippet cover both the context
|
||||
and the subject, and then the subject itself is rendered with an underline
|
||||
if we're rendering into a terminal that supports that style.
|
||||
|
||||
We don't use "context" very often, but it can be useful if the problem
|
||||
we're describing is that just one part of a larger source element is
|
||||
problematic. For example, if one of the operands to the `+` operator
|
||||
isn't a number then that operand would be the "subject" but the entire
|
||||
addition operation could be returned as "context", so that both of the
|
||||
operands and the `+` symbol will definitely be included in the rendered
|
||||
diagnostic too.
|
||||
- Expression-related information: optional information about an expression whose
|
||||
evaluation cause the problem. This has the following fields:
|
||||
|
||||
- Expression: The `hcl.Expression` representing the expression itself.
|
||||
- EvalContext: The `hcl.EvalContext` that the expression was being evaluated
|
||||
in.
|
||||
|
||||
The diagnostic renderer for the UI uses this information, when available,
|
||||
to offer some extra hints about the values of any symbols that were used
|
||||
in the expression, because it's often the dynamic values that cause a
|
||||
problem, rather than the syntax used to obtain them.
|
||||
- Extra info: this is a rather underspecified collection of assorted other
|
||||
information that's only relevant in very specific contexts. Refer to the
|
||||
`tfdiags` package documentation for more information.
|
||||
|
||||
There's _some_ guidance on this later in this document, but it's focused
|
||||
only on a few main cases.
|
||||
|
||||
## Diagnostic Description Writing Style
|
||||
|
||||
Although there is some variation in diagnostic writing style, particularly in
|
||||
parts of the system like state storage backends which were originally written by
|
||||
third-parties, most of the _built-in_ diagnostics follow a relatively consistent
|
||||
writing style that is in turn based on the writing style used by HCL itself in
|
||||
its own diagnostics, because HCL and OpenTofu diagnostics often mix together
|
||||
in the same set of problems.
|
||||
|
||||
The "summary" should typically be a very short and concise description of
|
||||
what was wrong and what was wrong about it. Our summaries typically don't
|
||||
include any user-chosen information such as symbol names, because that means a
|
||||
particular kind of problem is always described using the same text and so
|
||||
readers can become familiar enough with the summaries of problems they see
|
||||
frequently to skip reading the rest of the diagnostic when skimming.
|
||||
|
||||
The following are some real examples of summaries currently used across both
|
||||
HCL and OpenTofu:
|
||||
|
||||
- Unsupported operator
|
||||
- Duplicate argument
|
||||
- Invalid index
|
||||
- Unexpected end of template
|
||||
- Invalid template interpolation value
|
||||
- Invalid default value for variable
|
||||
- Required variable not set
|
||||
- Invalid "count" attribute
|
||||
|
||||
The "detail" text is where we tend to put most of the information, and so
|
||||
there's a lot more variation here but ideally a good diagnostic detail
|
||||
should mention the following information, usually in the following order:
|
||||
|
||||
- What was wrong and what was wrong about it: similar to the summary but this
|
||||
time including information about specifically what was wrong, such as the
|
||||
name of the input variable whose default value was invalid.
|
||||
- Why the situation is problematic, if knowing that relies on some
|
||||
characteristic of OpenTofu's design that might not be obvious to a newcomer.
|
||||
- What should be done to fix it, or (if it's unclear what the author's intention
|
||||
was) a question-sentence that implies a _possible_ solution, often starting
|
||||
with the words "Did you mean" and ending with a question mark.
|
||||
|
||||
While the summary message is often terse and uses only minimal punctuation,
|
||||
the detail message should always be written in full sentences including
|
||||
end-of-sentence punctuation (`.`, `?`). If "what was wrong about it" is
|
||||
coming from the string representation of an `error` value, we typically
|
||||
present it with a prefix ending with a colon and then append a period `.`
|
||||
after the error string, and format the error itself using `tfdiags.FormatError`,
|
||||
like this:
|
||||
|
||||
```go
|
||||
Detail: fmt.Sprintf("Unsuitable value for thingy: %s.", tfdiags.FormatError(err))
|
||||
```
|
||||
|
||||
If the second and third items in the above take more than a few words, it's
|
||||
helpful to split them into their own paragraphs for easier scanning. When
|
||||
writing multiple paragraphs in a detail message they should be separated by
|
||||
`\n\n` -- two newline characters.
|
||||
|
||||
In many cases our diagnostics only include a subset of this information because
|
||||
either the reason why it's problematic is relatively clear or because we don't
|
||||
have any specific suggestion for how to solve the problem, but the following
|
||||
is an example of a real diagnostic message from OpenTofu at the time of writing
|
||||
this documentation which includes all of these parts:
|
||||
|
||||
```
|
||||
Error: Invalid for_each argument
|
||||
|
||||
The "for_each" map includes keys derived from resource attributes that cannot
|
||||
be determined until apply, and so OpenTofu cannot determine the full set of keys
|
||||
that will identify the instances of this resource.
|
||||
|
||||
When working with unknown values in for_each, it's better to define the map keys
|
||||
statically in your configuration and place apply-time results only in the map
|
||||
values.
|
||||
|
||||
Alternatively, you could use the planning option -exclude=aws_instance.example
|
||||
to first apply without this object, and then apply normally to converge.
|
||||
```
|
||||
|
||||
The text immediately after "Error:" above is the summary for this diagnostic.
|
||||
The paragraphs that follow are all a single "detail" string.
|
||||
|
||||
That was a particularly extreme diagnostic message with lots of information to
|
||||
communicate. Most diagnostics are not so complicated; the following is an
|
||||
example with less information to communicate:
|
||||
|
||||
```
|
||||
Error: Invalid value for input variable
|
||||
|
||||
The given value is not suitable for var.example declared
|
||||
at example.tf:12,1: a string is required.
|
||||
```
|
||||
|
||||
This example also illustrates a situation where there are two different source
|
||||
locations that could be relevant: the input variable's declaration or the
|
||||
expression that's used to define its value. Because this message is talking
|
||||
about a problem with the _value_, the diagnostic should have the source
|
||||
"Subject" set to the expression that defined it, but it also mentions the
|
||||
location of the declaration as part of the detail text as some additional
|
||||
context.
|
||||
|
||||
Some other notes about some other specific situations that arise sometimes:
|
||||
|
||||
- If a diagnostic message includes a suggestion for a shell command to run
|
||||
or a URL to visit for more information, use a paragraph that ends with a
|
||||
colon, followed by a single newline, four spaces for indentation, and then the
|
||||
command or URL:
|
||||
|
||||
```
|
||||
To view the root module output values, run:
|
||||
tofu output
|
||||
```
|
||||
|
||||
The goal of this formatting is to make it very clear what part of the
|
||||
message is intended to be copied and used elsewhere, by placing it on a
|
||||
line of its own without any surrounding punctuation. The indented text
|
||||
should ideally be formatted so that the user can copy it _verbatim_ into
|
||||
whatever place it will be used.
|
||||
|
||||
The diagnostic renderer also has a special case where it will not try to
|
||||
word-wrap a line that begins with spaces, and so this layout has the
|
||||
useful side-effect of avoiding introducing extra newline characters into
|
||||
a command line that is intended to be copied.
|
||||
|
||||
- There are some terminology choices we use to refer to some OpenTofu-specific
|
||||
ideas and concepts that disagree slightly with terminology used in the code.
|
||||
These differences are the result of learning from feedback from folks who
|
||||
had been confused by the original terminology, even though the code still
|
||||
often uses the original terminology:
|
||||
|
||||
- Instead of referring to "unknown values" or "computed values" we say that
|
||||
values are "known after apply" or "cannot be determined until apply".
|
||||
- In HCL the word "variable" means anything that's available to refer to
|
||||
in the current evaluation context, which is confusing because OpenTofu
|
||||
itself uses that word to refer only to input variables.
|
||||
|
||||
Sometimes messages are generated by HCL itself and so it's unavoidably
|
||||
confusing, but when we're generating messages _inside OpenTofu_ we
|
||||
use the two words "input variable" to refer to an input variable,
|
||||
and "symbol" or "object" (depending on whether we're talking about
|
||||
the name itself or what the name refers to) as the general word for
|
||||
something you can refer to in an expression.
|
||||
- For consistency with our use of "input variable" to distinguish from
|
||||
HCL's more general meaning of "variable", we also tend to write
|
||||
"local value" and "output value" when referring to those concepts, rather
|
||||
than using the shorthands "locals" and "outputs".
|
||||
- HCL distinguishes between "attributes" meaning the named keys inside an
|
||||
object type, and "arguments" meaning the names used for individual
|
||||
settings inside a configuration block.
|
||||
|
||||
OpenTofu itself uses those words a little more interchangably because
|
||||
in _many_ cases the configuration arguments in a block directly
|
||||
correspond to the attributes of an object created by evaluating that
|
||||
block.
|
||||
|
||||
However, if a particular error message is talking about a configuration
|
||||
setting inside a block it's better to use "argument" rather than
|
||||
"attribute" because that's then consistent with error messages that
|
||||
HCL itself might generate.
|
||||
|
||||
Go uses the term "field" to describe an element of a struct type, and
|
||||
JavaScript and JSON use the word "property" to describe an element of
|
||||
an object type. We don't use either of those words in OpenTofu: the
|
||||
elements of an object are its _attributes_, and the settings available
|
||||
in a configuration block are its _arguments_. The string values that
|
||||
identify elements of a map are called "keys".
|
||||
- The `cty` terminology "marks" or "value marks" refers to an implementation
|
||||
detail that should never be mentioned directly in an error message.
|
||||
|
||||
Instead, we use specific terminology related to what each mark type
|
||||
is representing: "sensitive values", "ephemeral values", etc.
|
||||
- `aws_instance` is an example of a "resource _type_", not of a "resource",
|
||||
even though the provider protocol uses the single noun "resource" to refer
|
||||
to both ideas.
|
||||
|
||||
A "resource" is what's declared by a `resource`, `data`, or `ephemeral`
|
||||
block. A "resource _instance_" is what such a block can declare zero
|
||||
or more of, when using the `count`, `for_each`, or `enabled` arguments.
|
||||
- Although there are certainly some historical diagnostic messages that
|
||||
predate this adjustment of terminology, new error messages should use
|
||||
"managed resource" to refer to the kind of resource that's declared
|
||||
using a `resource` block, "data resource" for `data` blocks, and
|
||||
"ephemeral resource" for an `ephemeral` block.
|
||||
|
||||
In the code we refer to these three as "resource _modes_", but that is
|
||||
internal terminology that should never appear in a diagnostic message.
|
||||
- When a file or directory path appears as part of a diagnostic message, it
|
||||
should typically be presented relative to the current working directory and
|
||||
should use the syntax conventions of the platform where OpenTofu is running.
|
||||
|
||||
In particular, we return paths using backslashes as the separator when we
|
||||
are running on Windows, but normal slashes otherwise. Using the Go
|
||||
`filepath` package is a good way to get this right, though you might need
|
||||
to add some complexity to your tests to make them pass on all platforms.
|
||||
- If an error message is describing a "should never happen" case, we typically
|
||||
end the detail string with the sentence "This is a bug in OpenTofu.". This
|
||||
hopefully prompts the reader that this wasn't directly caused by something
|
||||
they did, and so they should probably open a bug report in the
|
||||
OpenTofu repository instead of just trying to solve it themselves.
|
||||
|
||||
For this kind of error message we often relax our preference against
|
||||
mentioning implementation details in the error message, because the most
|
||||
likely next step is for the user to copy-paste the entire message into their
|
||||
bug report text and so the final reader of the message is OpenTofu
|
||||
maintainers rather than OpenTofu users.
|
||||
|
||||
For example, it can be okay to use internal terminology like "cty marks" and
|
||||
use the `GoString` representations of values in a "This is a bug in
|
||||
OpenTofu" detail message, if that's the most concise way to capture the
|
||||
information the OpenTofu maintainers would need to debug the problem.
|
||||
|
||||
## Diagnostics caused by unknown or sensitive values
|
||||
|
||||
When a diagnostic has expression information associated with it, the diagnostic
|
||||
renderer for the UI includes some additional information about the values
|
||||
that were in scope, like this:
|
||||
|
||||
```
|
||||
var.greeting is "Hello"
|
||||
var.items is list of string with 5 elements
|
||||
```
|
||||
|
||||
By default, this renderer will not mention any symbol which refers to an unknown
|
||||
or sensitive value. That was not historically true: originally, this could
|
||||
say something like "var.example is a string, known only after apply".
|
||||
|
||||
Those who are less familiar with these concepts often misunderstood the
|
||||
"known only after apply" part of the message as being _the problem itself_,
|
||||
rather than just context to help diagnose the problem, and so the UI no longer
|
||||
mentions "unknown-ness" or "sensitive-ness" in most cases.
|
||||
|
||||
However, there are some diagnostics messages that _are_ directly caused by the
|
||||
presence of an unknown or sensitive value, in which case it's helpful to
|
||||
mention that in the summary of values that were in scope.
|
||||
|
||||
To allow for this, we set the "extra info" field of a diagnostic to contain
|
||||
an implementation of one of the following interfaces:
|
||||
|
||||
- [`tfdiags.DiagnosticExtraBecauseUnknown`](https://pkg.go.dev/github.com/opentofu/opentofu/internal/tfdiags#DiagnosticExtraBecauseUnknown)
|
||||
for a problem that's caused by an unknown value.
|
||||
|
||||
(Remember that the _text_ of the error message should refer to this as "known
|
||||
only after apply", or similar.)
|
||||
- [`tfdiags.DiagnosticExtraBecauseSensitive`](https://pkg.go.dev/github.com/opentofu/opentofu/internal/tfdiags#DiagnosticExtraBecauseSensitive)
|
||||
for situations where a sensitive value was used in a location that OpenTofu
|
||||
cannot permit it, such as in the instance key of a resource instance.
|
||||
|
||||
These extra markers should be used only when mentioning the unknown or sensitive
|
||||
values in the diagnostic message is likely to help with debugging a problem.
|
||||
If the problem is not directly caused by unknown or sensitive values then
|
||||
neither of these should be used, to avoid creating a distracting
|
||||
[red herring](https://en.wikipedia.org/wiki/Red_herring) for the reader.
|
||||
|
||||
## Consolidation of Diagnostics
|
||||
|
||||
The UI layer has some special rules for finding sets of similar diagnostics
|
||||
and showing them as just a single diagnostic referring to the first example
|
||||
of a problem, with a short extra note about how many other similar diagnostics
|
||||
there are.
|
||||
|
||||
```
|
||||
(and 2 similar warnings elsewhere)
|
||||
```
|
||||
|
||||
The main implementation of this behavior is in
|
||||
[`tfdiags.Diagnostics.Consolidate`](https://pkg.go.dev/github.com/opentofu/opentofu/internal/tfdiags#Diagnostics.Consolidate),
|
||||
but we allow end-users to customize (using command line options) whether this
|
||||
consolidation applies to errors or warnings separately. By default, we
|
||||
consolidate only warnings.
|
||||
|
||||
For a severity that is subject to consolidation, the main behavior is to group
|
||||
together diagnostics that have the same "summary" text, and this is part of
|
||||
why we tend to use terse, fixed strings in the summary field.
|
||||
|
||||
There are two extra mechanisms for customizing this behavior for specific
|
||||
diagnostic messages:
|
||||
|
||||
- If the "extra info" of a diagnostic contains an implementation of
|
||||
[`tfdiags.DiagnosticExtraDoNotConsolidate`](https://pkg.go.dev/github.com/opentofu/opentofu/internal/tfdiags#DiagnosticExtraDoNotConsolidate)
|
||||
then that diagnostic is not eligible for consolidation at all, regardless
|
||||
of how similar it might be to other diagnostics in the same set.
|
||||
- If the "extra info" of a diagnostic contains an implementation of
|
||||
[`tfdiags.Keyable`](https://pkg.go.dev/github.com/opentofu/opentofu/internal/tfdiags#Keyable)
|
||||
then the string returned by its `ExtraInfoKey` method is used _in addition to_
|
||||
the summary text for deciding what to consolidate.
|
||||
|
||||
For example, if there were three warnings with the same summary text but
|
||||
two of them have the same `ExtraInfoKey` and the third has a different
|
||||
one then only the first two would be able to consolidate.
|
||||
|
||||
The `ExtraInfoKey` is an internal key used for comparison only and is never
|
||||
exposed in the UI, so it can be set to whatever makes sense to define
|
||||
separate consolidation groups for diagnostics with a specific summary.
|
||||
Reference in New Issue
Block a user