Skip to content

Latest commit

 

History

History
273 lines (215 loc) · 10 KB

File metadata and controls

273 lines (215 loc) · 10 KB
page_title description
Plugin Development - Framework: Errors and Warnings
How to return errors and warnings from the Terraform provider development framework.

Returning Errors and Warnings

Providers use Diagnostics to surface errors and warnings to practitioners, such as contextual messages returned from Terraform CLI at the end of command 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 from functions 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 more errors appended to it. This approach allows your provider to inform practitioners about all relevant errors and warnings at the same time, allowing practitioners to fix their configuration or environment more quickly. You should only append to Diagnostics slices and never replace or remove information from them.

The next section will detail the concepts and typical behaviors of diagnostics, while the final section will outline the typical methods for working with diagnostics, using functionality from the available diag package.

Diagnostic Concepts

Severity

Severity specifies whether the diagnostic is an error or a warning.

  • An error will be displayed to the practitioner and halt Terraform's execution, not continuing to apply changes to later resources in the graph. We recommend using errors to inform practitioners about a situation the provider could not recover from.
  • A warning will be displayed to the practitioner, but will not halt further execution, and is considered informative only. We recommend using warnings to inform practitioners about suboptimal situations that the practitioner 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. Good summaries are general—they don't contain specific details about values—and concise. For example, "Error creating resource", "Invalid value for foo", or "Field foo is deprecated".

Detail

Detail is a longer, more specific practitioner-oriented description of precisely what went wrong. Good details are specific—they tell the practitioner exactly what they need to fix and how. For example, "The API is currently unavailable, please try the request again.", "foo can only contain letters, 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 a future release.".

Attribute

Attribute identifies the specific part of a configuration that caused the error or warning. Only diagnostics that pertain to a whole attribute or a specific 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 diagnostic is returned with it. This is to allow Terraform to persist the values that have already been modified when a resource modification requires multiple API requests or an API request fails after an earlier one succeeded.

When returning error diagnostics, we recommend resetting the state in the response to the prior state available in the configuration.

diag Package

The framework provides the diag package for interacting with diagnostics. While the Go documentation contains the complete functionality, this section will highlight the most common methods.

Working With Existing Diagnostics

Append

When receiving diag.Diagnostics from a function or method, such as Config.Get() or State.Set(), these should typically be appended to the response diagnostics for the method. This can be accomplished with the Append(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 and deduplicates where possible.

HasError

The most typical form of diagnostics checking is ensuring that execution should not stop due to encountering an error, potentially causing further confusing errors or crashes. The HasError() method will 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, checking the response diagnostics can help ensure that any response will include the expected diagnostics.

Creating Diagnostics

When working with logic outside the framework, such as interacting with the vendor or net/http library to make the actual calls to manage infrastructure or creating custom plan modifiers and validators, it will be necessary to create diagnostics. The diag package provides helper methods and allows custom abstractions as described below.

To craft the summary of a diagnostic, it is recommended to use a concise title or single sentence that immediately can allow the practitioner to determine the error cause and when it occurred.

To craft the details portion of diagnostics, it is recommended to provide practitioners (and potentially you as the maintainer) as much contextual, troubleshooting, and next action information as possible. These details can use 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 sufficient
permissions to perform the Example API call against the resource.

Region: example
ID: example123
API Response: 403 Access Denied

AddError and AddWarning

When creating diagnostics that affect an entire data source, provider, or resource, and where a diag.Diagnostics is already available such as within a response type, the AddError(summary string, detail string) method and AddWarning(summary string, detail string) method can 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 is typical of attribute-level plan modifiers and validators, the AddAttributeError(path path.Path, summary string, detail string) method and AddAttributeWarning(path path.Path, summary string, detail string) method can append a new error or warning diagnostic pointing specifically at the attribute path. This provides additional context to practitioners, such as showing 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 ...

Custom Diagnostics Types

Advanced provider developers may need to further differentiate or store additional data in error and warning types. The diag.Diagnostic type is an interface type 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 type can be implemented with the additional Path() method:

type DiagnosticWithPath interface {
    Diagnostic
    Path() path.Path
}