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