github.com/argoproj/argo-cd/v3@v3.2.1/util/grpc/errors.go (about)

     1  package grpc
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  
     7  	giterr "github.com/go-git/go-git/v5/plumbing/transport"
     8  	"google.golang.org/grpc"
     9  	"google.golang.org/grpc/codes"
    10  	"google.golang.org/grpc/status"
    11  	apierrors "k8s.io/apimachinery/pkg/api/errors"
    12  )
    13  
    14  func rewrapError(err error, code codes.Code) error {
    15  	return status.Error(code, err.Error())
    16  }
    17  
    18  func gitErrToGRPC(err error) error {
    19  	if err == nil {
    20  		return nil
    21  	}
    22  	errMsg := err.Error()
    23  	if grpcStatus := UnwrapGRPCStatus(err); grpcStatus != nil {
    24  		errMsg = grpcStatus.Message()
    25  	}
    26  
    27  	if errMsg == giterr.ErrRepositoryNotFound.Error() {
    28  		err = rewrapError(errors.New(errMsg), codes.NotFound)
    29  	}
    30  	return err
    31  }
    32  
    33  // UnwrapGRPCStatus will attempt to cast the given error into a grpc Status
    34  // object unwrapping all existing inner errors. Will return nil if none of the
    35  // nested errors can be casted.
    36  func UnwrapGRPCStatus(err error) *status.Status {
    37  	if se, ok := err.(interface{ GRPCStatus() *status.Status }); ok {
    38  		return se.GRPCStatus()
    39  	}
    40  	e := errors.Unwrap(err)
    41  	if e == nil {
    42  		return nil
    43  	}
    44  	return UnwrapGRPCStatus(e)
    45  }
    46  
    47  // kubeErrToGRPC converts a Kubernetes error into a gRPC code + error. The gRPC code we translate
    48  // it to is significant, because it eventually maps back to an HTTP status code determined by
    49  // grpc-gateway. See:
    50  // https://github.com/grpc-ecosystem/grpc-gateway/blob/v2.11.3/runtime/errors.go#L36
    51  // https://go.dev/src/net/http/status.go
    52  func kubeErrToGRPC(err error) error {
    53  	/*
    54  		Unmapped source Kubernetes API errors as of 2022-10-05:
    55  		* IsGone => 410 (DEPRECATED by ResourceExpired)
    56  		* IsResourceExpired => 410
    57  		* IsUnexpectedServerError
    58  		* IsUnexpectedObjectError
    59  
    60  		Unmapped target gRPC codes as of 2022-10-05:
    61  		* Canceled Code = 1
    62  		* Unknown Code = 2
    63  		* OutOfRange Code = 11
    64  		* DataLoss Code = 15
    65  	*/
    66  
    67  	switch {
    68  	case apierrors.IsNotFound(err):
    69  		err = rewrapError(err, codes.NotFound)
    70  	case apierrors.IsAlreadyExists(err):
    71  		err = rewrapError(err, codes.AlreadyExists)
    72  	case apierrors.IsInvalid(err):
    73  		err = rewrapError(err, codes.InvalidArgument)
    74  	case apierrors.IsMethodNotSupported(err):
    75  		err = rewrapError(err, codes.Unimplemented)
    76  	case apierrors.IsServiceUnavailable(err):
    77  		err = rewrapError(err, codes.Unavailable)
    78  	case apierrors.IsBadRequest(err):
    79  		err = rewrapError(err, codes.FailedPrecondition)
    80  	case apierrors.IsUnauthorized(err):
    81  		err = rewrapError(err, codes.Unauthenticated)
    82  	case apierrors.IsForbidden(err):
    83  		err = rewrapError(err, codes.PermissionDenied)
    84  	case apierrors.IsTimeout(err):
    85  		err = rewrapError(err, codes.DeadlineExceeded)
    86  	case apierrors.IsServerTimeout(err):
    87  		err = rewrapError(err, codes.Unavailable)
    88  	case apierrors.IsConflict(err):
    89  		err = rewrapError(err, codes.Aborted)
    90  	case apierrors.IsTooManyRequests(err):
    91  		err = rewrapError(err, codes.ResourceExhausted)
    92  	case apierrors.IsInternalError(err):
    93  		err = rewrapError(err, codes.Internal)
    94  	default:
    95  		// This is necessary as GRPC Status don't support wrapped errors:
    96  		// https://github.com/grpc/grpc-go/issues/2934
    97  		if grpcStatus := UnwrapGRPCStatus(err); grpcStatus != nil {
    98  			err = status.Error(grpcStatus.Code(), grpcStatus.Message())
    99  		}
   100  	}
   101  	return err
   102  }
   103  
   104  // ErrorCodeGitUnaryServerInterceptor replaces Kubernetes errors with relevant gRPC equivalents, if any.
   105  func ErrorCodeGitUnaryServerInterceptor() grpc.UnaryServerInterceptor {
   106  	return func(ctx context.Context, req any, _ *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp any, err error) {
   107  		resp, err = handler(ctx, req)
   108  		return resp, gitErrToGRPC(err)
   109  	}
   110  }
   111  
   112  // ErrorCodeGitStreamServerInterceptor replaces Kubernetes errors with relevant gRPC equivalents, if any.
   113  func ErrorCodeGitStreamServerInterceptor() grpc.StreamServerInterceptor {
   114  	return func(srv any, ss grpc.ServerStream, _ *grpc.StreamServerInfo, handler grpc.StreamHandler) error {
   115  		err := handler(srv, ss)
   116  		return gitErrToGRPC(err)
   117  	}
   118  }
   119  
   120  // ErrorCodeK8sUnaryServerInterceptor replaces Kubernetes errors with relevant gRPC equivalents, if any.
   121  func ErrorCodeK8sUnaryServerInterceptor() grpc.UnaryServerInterceptor {
   122  	return func(ctx context.Context, req any, _ *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp any, err error) {
   123  		resp, err = handler(ctx, req)
   124  		return resp, kubeErrToGRPC(err)
   125  	}
   126  }
   127  
   128  // ErrorCodeK8sStreamServerInterceptor replaces Kubernetes errors with relevant gRPC equivalents, if any.
   129  func ErrorCodeK8sStreamServerInterceptor() grpc.StreamServerInterceptor {
   130  	return func(srv any, ss grpc.ServerStream, _ *grpc.StreamServerInfo, handler grpc.StreamHandler) error {
   131  		err := handler(srv, ss)
   132  		return kubeErrToGRPC(err)
   133  	}
   134  }