make grpcErr work for either plugin type
Extract a better function name and make the errors generic for different plugin types.
This commit is contained in:
parent
3225d9ac11
commit
5f063ae94a
|
@ -0,0 +1,74 @@
|
||||||
|
package plugin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"path"
|
||||||
|
"runtime"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/tfdiags"
|
||||||
|
"google.golang.org/grpc/codes"
|
||||||
|
"google.golang.org/grpc/status"
|
||||||
|
)
|
||||||
|
|
||||||
|
// grpcErr extracts some known error types and formats them into better
|
||||||
|
// representations for core. This must only be called from plugin methods.
|
||||||
|
// Since we don't use RPC status errors for the plugin protocol, these do not
|
||||||
|
// contain any useful details, and we can return some text that at least
|
||||||
|
// indicates the plugin call and possible error condition.
|
||||||
|
func grpcErr(err error) (diags tfdiags.Diagnostics) {
|
||||||
|
if err == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// extract the method name from the caller.
|
||||||
|
pc, _, _, ok := runtime.Caller(1)
|
||||||
|
if !ok {
|
||||||
|
logger.Error("unknown grpc call", "error", err)
|
||||||
|
return diags.Append(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
f := runtime.FuncForPC(pc)
|
||||||
|
|
||||||
|
// Function names will contain the full import path. Take the last
|
||||||
|
// segment, which will let users know which method was being called.
|
||||||
|
_, requestName := path.Split(f.Name())
|
||||||
|
|
||||||
|
// Here we can at least correlate the error in the logs to a particular binary.
|
||||||
|
logger.Error(requestName, "error", err)
|
||||||
|
|
||||||
|
// TODO: while this expands the error codes into somewhat better messages,
|
||||||
|
// this still does not easily link the error to an actual user-recognizable
|
||||||
|
// plugin. The grpc plugin does not know its configured name, and the
|
||||||
|
// errors are in a list of diagnostics, making it hard for the caller to
|
||||||
|
// annotate the returned errors.
|
||||||
|
switch status.Code(err) {
|
||||||
|
case codes.Unavailable:
|
||||||
|
// This case is when the plugin has stopped running for some reason,
|
||||||
|
// and is usually the result of a crash.
|
||||||
|
diags = diags.Append(tfdiags.Sourceless(
|
||||||
|
tfdiags.Error,
|
||||||
|
"Plugin did not respond",
|
||||||
|
fmt.Sprintf("The plugin encountered an error, and failed to respond to the %s call. "+
|
||||||
|
"The plugin logs may contain more details.", requestName),
|
||||||
|
))
|
||||||
|
case codes.Canceled:
|
||||||
|
diags = diags.Append(tfdiags.Sourceless(
|
||||||
|
tfdiags.Error,
|
||||||
|
"Request cancelled",
|
||||||
|
fmt.Sprintf("The %s request was cancelled.", requestName),
|
||||||
|
))
|
||||||
|
case codes.Unimplemented:
|
||||||
|
diags = diags.Append(tfdiags.Sourceless(
|
||||||
|
tfdiags.Error,
|
||||||
|
"Unsupported plugin method",
|
||||||
|
fmt.Sprintf("The %s method is not supported by this plugin.", requestName),
|
||||||
|
))
|
||||||
|
default:
|
||||||
|
diags = diags.Append(tfdiags.Sourceless(
|
||||||
|
tfdiags.Error,
|
||||||
|
"Plugin error",
|
||||||
|
fmt.Sprintf("The plugin returned an unexpected error from %s: %v", requestName, err),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
|
@ -3,9 +3,6 @@ package plugin
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
|
||||||
"runtime"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/zclconf/go-cty/cty"
|
"github.com/zclconf/go-cty/cty"
|
||||||
|
@ -15,12 +12,9 @@ import (
|
||||||
proto "github.com/hashicorp/terraform/internal/tfplugin5"
|
proto "github.com/hashicorp/terraform/internal/tfplugin5"
|
||||||
"github.com/hashicorp/terraform/plugin/convert"
|
"github.com/hashicorp/terraform/plugin/convert"
|
||||||
"github.com/hashicorp/terraform/providers"
|
"github.com/hashicorp/terraform/providers"
|
||||||
"github.com/hashicorp/terraform/tfdiags"
|
|
||||||
ctyjson "github.com/zclconf/go-cty/cty/json"
|
ctyjson "github.com/zclconf/go-cty/cty/json"
|
||||||
"github.com/zclconf/go-cty/cty/msgpack"
|
"github.com/zclconf/go-cty/cty/msgpack"
|
||||||
"google.golang.org/grpc"
|
"google.golang.org/grpc"
|
||||||
"google.golang.org/grpc/codes"
|
|
||||||
"google.golang.org/grpc/status"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var logger = logging.HCLogger()
|
var logger = logging.HCLogger()
|
||||||
|
@ -625,66 +619,3 @@ func decodeDynamicValue(v *proto.DynamicValue, ty cty.Type) (cty.Value, error) {
|
||||||
}
|
}
|
||||||
return res, err
|
return res, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// grpcErr extracts some known error types and formats them into better
|
|
||||||
// representations for core. This must only be called from plugin methods.
|
|
||||||
// Since we don't use RPC status errors for the plugin protocol, these do not
|
|
||||||
// contain any useful details, and we can return some text that at least
|
|
||||||
// indicates the plugin call and possible error condition.
|
|
||||||
func grpcErr(err error) (diags tfdiags.Diagnostics) {
|
|
||||||
if err == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// extract the method name from the caller.
|
|
||||||
pc, _, _, ok := runtime.Caller(1)
|
|
||||||
if !ok {
|
|
||||||
return diags.Append(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
f := runtime.FuncForPC(pc)
|
|
||||||
requestName := f.Name()
|
|
||||||
dot := strings.LastIndex(requestName, ".")
|
|
||||||
if dot > 0 {
|
|
||||||
requestName = requestName[dot+1:]
|
|
||||||
}
|
|
||||||
|
|
||||||
// Here we can at least correlate the error in the logs to a particular binary.
|
|
||||||
logger.Error("GRPCProvider."+requestName, "error", err)
|
|
||||||
|
|
||||||
// TODO: while this expands the error codes into somewhat better messages,
|
|
||||||
// this still does not easily link the error to the an actual
|
|
||||||
// user-recognizable provider. The GRPCProvider does not know its
|
|
||||||
// configured name, and the errors are in a list of diagnostics, making it
|
|
||||||
// hard for the the caller to annotate the returned errors.
|
|
||||||
switch status.Code(err) {
|
|
||||||
case codes.Unavailable:
|
|
||||||
// This case is when the provider has stopped running for some reason,
|
|
||||||
// and is usually the result of a crash.
|
|
||||||
diags = diags.Append(tfdiags.Sourceless(
|
|
||||||
tfdiags.Error,
|
|
||||||
"Provider did not respond",
|
|
||||||
fmt.Sprintf("The provider encountered an error, and failed to respond to the %s call. "+
|
|
||||||
"The provider logs may contain more details", requestName),
|
|
||||||
))
|
|
||||||
case codes.Canceled:
|
|
||||||
diags = diags.Append(tfdiags.Sourceless(
|
|
||||||
tfdiags.Error,
|
|
||||||
"Request cancelled",
|
|
||||||
fmt.Sprintf("The %s request was cancelled.", requestName),
|
|
||||||
))
|
|
||||||
case codes.Unimplemented:
|
|
||||||
diags = diags.Append(tfdiags.Sourceless(
|
|
||||||
tfdiags.Error,
|
|
||||||
"Unsupported plugin method",
|
|
||||||
fmt.Sprintf("The %s method is not supported by this provider", requestName),
|
|
||||||
))
|
|
||||||
default:
|
|
||||||
diags = diags.Append(tfdiags.Sourceless(
|
|
||||||
tfdiags.Error,
|
|
||||||
"Provider error",
|
|
||||||
fmt.Sprintf("The provider returned an unexpected error from %s: %v", requestName, err),
|
|
||||||
))
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
|
@ -4,7 +4,6 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"io"
|
"io"
|
||||||
"log"
|
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
plugin "github.com/hashicorp/go-plugin"
|
plugin "github.com/hashicorp/go-plugin"
|
||||||
|
@ -61,7 +60,7 @@ func (p *GRPCProvisioner) GetSchema() (resp provisioners.GetSchemaResponse) {
|
||||||
|
|
||||||
protoResp, err := p.client.GetSchema(p.ctx, new(proto.GetProvisionerSchema_Request))
|
protoResp, err := p.client.GetSchema(p.ctx, new(proto.GetProvisionerSchema_Request))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
resp.Diagnostics = resp.Diagnostics.Append(err)
|
resp.Diagnostics = resp.Diagnostics.Append(grpcErr(err))
|
||||||
return resp
|
return resp
|
||||||
}
|
}
|
||||||
resp.Diagnostics = resp.Diagnostics.Append(convert.ProtoToDiagnostics(protoResp.Diagnostics))
|
resp.Diagnostics = resp.Diagnostics.Append(convert.ProtoToDiagnostics(protoResp.Diagnostics))
|
||||||
|
@ -96,7 +95,7 @@ func (p *GRPCProvisioner) ValidateProvisionerConfig(r provisioners.ValidateProvi
|
||||||
}
|
}
|
||||||
protoResp, err := p.client.ValidateProvisionerConfig(p.ctx, protoReq)
|
protoResp, err := p.client.ValidateProvisionerConfig(p.ctx, protoReq)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
resp.Diagnostics = resp.Diagnostics.Append(err)
|
resp.Diagnostics = resp.Diagnostics.Append(grpcErr(err))
|
||||||
return resp
|
return resp
|
||||||
}
|
}
|
||||||
resp.Diagnostics = resp.Diagnostics.Append(convert.ProtoToDiagnostics(protoResp.Diagnostics))
|
resp.Diagnostics = resp.Diagnostics.Append(convert.ProtoToDiagnostics(protoResp.Diagnostics))
|
||||||
|
@ -130,7 +129,7 @@ func (p *GRPCProvisioner) ProvisionResource(r provisioners.ProvisionResourceRequ
|
||||||
|
|
||||||
outputClient, err := p.client.ProvisionResource(p.ctx, protoReq)
|
outputClient, err := p.client.ProvisionResource(p.ctx, protoReq)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
resp.Diagnostics = resp.Diagnostics.Append(err)
|
resp.Diagnostics = resp.Diagnostics.Append(grpcErr(err))
|
||||||
return resp
|
return resp
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -169,7 +168,7 @@ func (p *GRPCProvisioner) Stop() error {
|
||||||
func (p *GRPCProvisioner) Close() error {
|
func (p *GRPCProvisioner) Close() error {
|
||||||
// check this since it's not automatically inserted during plugin creation
|
// check this since it's not automatically inserted during plugin creation
|
||||||
if p.PluginClient == nil {
|
if p.PluginClient == nil {
|
||||||
log.Println("[DEBUG] provider has no plugin.Client")
|
logger.Debug("provisioner has no plugin.Client")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue