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 }