github.com/opentofu/opentofu@v1.7.1/internal/plugin/grpc_error.go (about)

     1  // Copyright (c) The OpenTofu Authors
     2  // SPDX-License-Identifier: MPL-2.0
     3  // Copyright (c) 2023 HashiCorp, Inc.
     4  // SPDX-License-Identifier: MPL-2.0
     5  
     6  package plugin
     7  
     8  import (
     9  	"fmt"
    10  	"path"
    11  	"runtime"
    12  
    13  	"github.com/opentofu/opentofu/internal/tfdiags"
    14  	"google.golang.org/grpc/codes"
    15  	"google.golang.org/grpc/status"
    16  )
    17  
    18  // grpcErr extracts some known error types and formats them into better
    19  // representations for core. This must only be called from plugin methods.
    20  // Since we don't use RPC status errors for the plugin protocol, these do not
    21  // contain any useful details, and we can return some text that at least
    22  // indicates the plugin call and possible error condition.
    23  func grpcErr(err error) (diags tfdiags.Diagnostics) {
    24  	if err == nil {
    25  		return
    26  	}
    27  
    28  	// extract the method name from the caller.
    29  	pc, _, _, ok := runtime.Caller(1)
    30  	if !ok {
    31  		logger.Error("unknown grpc call", "error", err)
    32  		return diags.Append(err)
    33  	}
    34  
    35  	f := runtime.FuncForPC(pc)
    36  
    37  	// Function names will contain the full import path. Take the last
    38  	// segment, which will let users know which method was being called.
    39  	_, requestName := path.Split(f.Name())
    40  
    41  	// Here we can at least correlate the error in the logs to a particular binary.
    42  	logger.Error(requestName, "error", err)
    43  
    44  	// TODO: while this expands the error codes into somewhat better messages,
    45  	// this still does not easily link the error to an actual user-recognizable
    46  	// plugin. The grpc plugin does not know its configured name, and the
    47  	// errors are in a list of diagnostics, making it hard for the caller to
    48  	// annotate the returned errors.
    49  	switch status.Code(err) {
    50  	case codes.Unavailable:
    51  		// This case is when the plugin has stopped running for some reason,
    52  		// and is usually the result of a crash.
    53  		diags = diags.Append(tfdiags.WholeContainingBody(
    54  			tfdiags.Error,
    55  			"Plugin did not respond",
    56  			fmt.Sprintf("The plugin encountered an error, and failed to respond to the %s call. "+
    57  				"The plugin logs may contain more details.", requestName),
    58  		))
    59  	case codes.Canceled:
    60  		diags = diags.Append(tfdiags.WholeContainingBody(
    61  			tfdiags.Error,
    62  			"Request cancelled",
    63  			fmt.Sprintf("The %s request was cancelled.", requestName),
    64  		))
    65  	case codes.Unimplemented:
    66  		diags = diags.Append(tfdiags.WholeContainingBody(
    67  			tfdiags.Error,
    68  			"Unsupported plugin method",
    69  			fmt.Sprintf("The %s method is not supported by this plugin.", requestName),
    70  		))
    71  	default:
    72  		diags = diags.Append(tfdiags.WholeContainingBody(
    73  			tfdiags.Error,
    74  			"Plugin error",
    75  			fmt.Sprintf("The plugin returned an unexpected error from %s: %v", requestName, err),
    76  		))
    77  	}
    78  	return
    79  }