A RetroSearch Logo

Home - News ( United States | United Kingdom | Italy | Germany ) - Football scores

Search Query:

Showing content from https://developer.hashicorp.com/terraform/plugin/framework/diagnostics below:

Errors and warnings | Terraform

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.

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.

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.

Argument

Argument identifies the specific function argument position that caused the error or warning. Only diagnostics that pertain to a function argument will include this information.

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.

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 ...
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:

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 ...
}

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 include attribute path information, the diag.DiagnosticWithPath interface can be implemented with the additional Path() method:

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

To include function argument information, the diag.DiagnosticWithFunctionArgument interface can be implemented with the additional FunctionArgument() method:

type DiagnosticWithFunctionArgument interface {
    Diagnostic
    FunctionArgument() int
}

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
}

RetroSearch is an open source project built by @garambo | Open a GitHub Issue

Search and Browse the WWW like it's 1997 | Search results from DuckDuckGo

HTML: 3.2 | Encoding: UTF-8 | Version: 0.7.4