Providers use Diagnostics
to surface errors and warnings to practitioners,such as contextual messages returned from Terraform CLI at the end ofcommand output:
$ terraform plan# ... other plan output ...╷│ Error: Summary│ │ on example.tf line #:│ #: source configuration line│ │ Details╵
In the framework, you may encounter them in response structs or as returns fromfunctions or methods:
func (m myResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse)
This is the most common form for Diagnostics: a slice that has one or moreerrors appended to it. This approach allows your provider to informpractitioners about all relevant errors and warnings at the same time, allowingpractitioners to fix their configuration or environment more quickly. Youshould only append to Diagnostics slices and never replace or removeinformation from them.
The next section will detail the concepts and typical behaviors ofdiagnostics, while the final section will outline the typical methods forworking with diagnostics, using functionality from the availablediag
package.
Severity
Severity
specifies whether the diagnostic is an error or a warning. Neither Terraform, nor the framework, supports other severity levels. Use logging for debugging or informational purposes.
- An error will be displayed to the practitioner and halt Terraform'sexecution, not continuing to apply changes to later resources in the graph.We recommend using errors to inform practitioners about a situation theprovider could not recover from.
- A warning will be displayed to the practitioner, but will not haltfurther execution, and is considered informative only. We recommend usingwarnings to inform practitioners about suboptimal situations that thepractitioner should resolve to ensure stable functioning (e.g., deprecations)or to inform practitioners about possible unexpected behaviors.
Summary
Summary
is a short, practitioner-oriented description of the problem. Goodsummaries are general—they don't contain specific details aboutvalues—and concise. For example, "Error creating resource", "Invalidvalue for foo", or "Field foo is deprecated".
Detail
Detail
is a longer, more specific practitioner-oriented description ofprecisely what went wrong. Good details are specific—they tell thepractitioner exactly what they need to fix and how. For example, "The APIis currently unavailable, please try the request again.", "foo can only containletters, numbers, and digits.", or "foo has been deprecated in favor of bar.Please update your configuration to use bar instead. foo will be removed in afuture release.".
Attribute
Attribute
identifies the specific part of a configuration that caused theerror or warning. Only diagnostics that pertain to a whole attribute or aspecific attribute value will include this information.
How Errors Affect State
Returning an error diagnostic does not stop the state from being updated.Terraform will still persist the returned state even when an error diagnosticis returned with it. This is to allow Terraform to persist the values that havealready been modified when a resource modification requires multiple APIrequests or an API request fails after an earlier one succeeded.
When returning error diagnostics, we recommend resetting the state in theresponse to the prior state available in the configuration.
The framework provides the diag
package for interacting with diagnostics.While the Go documentationcontains the complete functionality, this section will highlight the mostcommon methods.
Working With Existing Diagnostics
Append
When receiving diag.Diagnostics
from a function or method, such asConfig.Get()
or State.Set()
, these should typically be appended to theresponse diagnostics for the method. This can be accomplished with theAppend(in ...diag.Diagnostics)
method.
For example:
func (m myResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { // ... prior logic ... diags := req.Config.Get(ctx, &resourceData) resp.Diagnostics.Append(diags...) // ... further logic ...}
This method automatically ignores nil
or empty slice diagnostics anddeduplicates where possible.
HasError
The most typical form of diagnostics checking is ensuring that execution shouldnot stop due to encountering an error, potentially causing further confusingerrors or crashes. The HasError()
methodwill check each of the diagnostics for error severity and return true if found.
For example:
func (m myResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { // ... prior logic ... diags := req.Config.Get(ctx, &resourceData) resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { return } // ... further logic ...}
In this example, you will note that we opted to check resp.Diagnostics
instead of diags
. Technically checking either is correct, however, checkingthe response diagnostics can help ensure that any response will include theexpected diagnostics.
Creating Diagnostics
When working with logic outside the framework, such as interacting with thevendor or net/http
library to make the actual calls to manage infrastructureor creating custom plan modifiers and validators, it will be necessary tocreate diagnostics. The diag
package provides helper methods and allowscustom abstractions as described below.
To craft the summary of a diagnostic, it is recommended to use a concise titleor single sentence that immediately can allow the practitioner to determinethe error cause and when it occurred.
To craft the details portion of diagnostics, it is recommended to providepractitioners (and potentially you as the maintainer) as much contextual,troubleshooting, and next action information as possible. These details canuse newlines for easier readability where necessary.
For example, with the top line as a summary and below as details:
API Error Reading Resource
An unexpected error was encountered while reading the resource.Please check that the credentials being used are active and have sufficientpermissions to perform the Example API call against the resource.Region: exampleID: example123API Response: 403 Access Denied
AddError and AddWarning
When creating diagnostics that affect an entire data source, provider, orresource, and where a diag.Diagnostics
is already available such as withina response type, the AddError(summary string, detail string)
methodand AddWarning(summary string, detail string)
methodcan append a new error or warning diagnostic.
For example:
func (m myResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { // ... prior logic ... resp, err := http.Post("https://example.com") if err != nil { resp.Diagnostics.AddError( "API Error Creating Resource", fmt.Sprintf("... details ... %s", err) ) return } // ... further logic ...}
AddAttributeError and AddAttributeWarning
When creating diagnostics that affect only a single attribute, which istypical of attribute-level plan modifiers and validators, theAddAttributeError(path path.Path, summary string, detail string)
methodand AddAttributeWarning(path path.Path, summary string, detail string)
methodcan append a new error or warning diagnostic pointing specifically at theattribute path. This provides additional context to practitioners, such asshowing the specific line(s) and value(s) of configuration where possible.
For example:
func (s exampleType) Validate(ctx context.Context, in tftypes.Value, path path.Path) diag.Diagnostics { var diags diag.Diagnostics if !in.Type().Is(tftypes.Set{}) { err := fmt.Errorf() diags.AddAttributeError( path, "Example Type Validation Error", "An unexpected error was encountered trying to validate an attribute value. "+ "This is always an error in the provider. "+ "Please report the following to the provider developer:\n\n"+ fmt.Sprintf("Expected Set value, received %T with value: %v", in, in), ) return diags } // ... further logic ...
Consistent Diagnostic Creation
Create a helper function in your provider code using the diagnostic creation functions available in the diag
package to generate consistent diagnostics for types of errors/warnings. It is also possible to use custom diagnostics types to accomplish this same goal.
The diag
package provides these functions to create various diagnostics:
Function | Description | |
---|---|---|
diag.NewAttributeErrorDiagnostic() | Create a new error diagnostic with a path. | . |
diag.NewAttributeWarningDiagnostic() | Create a new warning diagnostic with a path. | . |
diag.NewErrorDiagnostic() | Create a new error diagnostic without a path. | . |
diag.NewWarningDiagnostic() | Create a new warning diagnostic without a path. | . |
In this example, the provider code is setup to always convert error
returns from the API SDK to a consistent error diagnostic.
func APIErrorDiagnostic(err error) diag.Diagnostic { return diag.NewErrorDiagnostic( "Unexpected API Error", "While calling the API, an unexpected error was returned in the response. "+ "Please contact support if you are unsure how to resolve the error.\n\n"+ "Error: "+err.Error(), )}
This enables calling code in the provider, such as:
func (r ThingResource) Read(ctx context.Context, req resource.ReadRequest, resp resource.ReadResponse) { // ... other logic ... apiResp, err := examplesdk.Read(/* ... */) // example API SDK call that may return an error if err != nil { resp.Diagnostics.Append(APIErrorDiagnostic(err)) return } // ... further logic ...}
Custom Diagnostics Types
Advanced provider developers may want to store additional data in diagnostics for other logic or create custom diagnostics that include specialized logic.
The diag.Diagnostic
interface that can be implemented with these methods:
type Diagnostic interface { Severity() Severity Summary() string Detail() string Equal(Diagnostic) bool}
To also include attribute path information, the diag.DiagnosticWithPath
interface can be implemented with the additional Path()
method:
type DiagnosticWithPath interface { Diagnostic Path() path.Path}
In this example, a custom diagnostic type stores an underlying error
that caused the diagnostic:
// UnderlyingErrorDiagnostic is an error diagnostic// which also stores the underlying error.type UnderlyingErrorDiagnostic struct { Detail string Summary string UnderlyingError error}func (d UnderlyingErrorDiagnostic) Detail() string { return d.Detail}func (d UnderlyingErrorDiagnostic) Equal(o SpecialDiagnostic) bool { if d.Detail() != o.Detail() { return false } if d.Summary() != o.Summary() { return false } if d.UnderlyingError == nil { return o.UnderlyingError == nil } if o.UnderlyingError == nil { return false } if d.UnderlyingError.Error() != o.UnderlyingError.Error() { return false } return true}func (d UnderlyingErrorDiagnostic) Severity() diag.Severity { return diag.SeverityError}func (d UnderlyingErrorDiagnostic) Summary() string { return d.Summary}