1/* 2 Copyright The containerd Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15*/ 16 17package errdefs 18 19import ( 20 "strings" 21 22 "github.com/pkg/errors" 23 "google.golang.org/grpc/codes" 24 "google.golang.org/grpc/status" 25) 26 27// ToGRPC will attempt to map the backend containerd error into a grpc error, 28// using the original error message as a description. 29// 30// Further information may be extracted from certain errors depending on their 31// type. 32// 33// If the error is unmapped, the original error will be returned to be handled 34// by the regular grpc error handling stack. 35func ToGRPC(err error) error { 36 if err == nil { 37 return nil 38 } 39 40 if isGRPCError(err) { 41 // error has already been mapped to grpc 42 return err 43 } 44 45 switch { 46 case IsInvalidArgument(err): 47 return status.Errorf(codes.InvalidArgument, err.Error()) 48 case IsNotFound(err): 49 return status.Errorf(codes.NotFound, err.Error()) 50 case IsAlreadyExists(err): 51 return status.Errorf(codes.AlreadyExists, err.Error()) 52 case IsFailedPrecondition(err): 53 return status.Errorf(codes.FailedPrecondition, err.Error()) 54 case IsUnavailable(err): 55 return status.Errorf(codes.Unavailable, err.Error()) 56 case IsNotImplemented(err): 57 return status.Errorf(codes.Unimplemented, err.Error()) 58 } 59 60 return err 61} 62 63// ToGRPCf maps the error to grpc error codes, assembling the formatting string 64// and combining it with the target error string. 65// 66// This is equivalent to errors.ToGRPC(errors.Wrapf(err, format, args...)) 67func ToGRPCf(err error, format string, args ...interface{}) error { 68 return ToGRPC(errors.Wrapf(err, format, args...)) 69} 70 71// FromGRPC returns the underlying error from a grpc service based on the grpc error code 72func FromGRPC(err error) error { 73 if err == nil { 74 return nil 75 } 76 77 var cls error // divide these into error classes, becomes the cause 78 79 switch code(err) { 80 case codes.InvalidArgument: 81 cls = ErrInvalidArgument 82 case codes.AlreadyExists: 83 cls = ErrAlreadyExists 84 case codes.NotFound: 85 cls = ErrNotFound 86 case codes.Unavailable: 87 cls = ErrUnavailable 88 case codes.FailedPrecondition: 89 cls = ErrFailedPrecondition 90 case codes.Unimplemented: 91 cls = ErrNotImplemented 92 default: 93 cls = ErrUnknown 94 } 95 96 msg := rebaseMessage(cls, err) 97 if msg != "" { 98 err = errors.Wrapf(cls, msg) 99 } else { 100 err = errors.WithStack(cls) 101 } 102 103 return err 104} 105 106// rebaseMessage removes the repeats for an error at the end of an error 107// string. This will happen when taking an error over grpc then remapping it. 108// 109// Effectively, we just remove the string of cls from the end of err if it 110// appears there. 111func rebaseMessage(cls error, err error) string { 112 desc := errDesc(err) 113 clss := cls.Error() 114 if desc == clss { 115 return "" 116 } 117 118 return strings.TrimSuffix(desc, ": "+clss) 119} 120 121func isGRPCError(err error) bool { 122 _, ok := status.FromError(err) 123 return ok 124} 125 126func code(err error) codes.Code { 127 if s, ok := status.FromError(err); ok { 128 return s.Code() 129 } 130 return codes.Unknown 131} 132 133func errDesc(err error) string { 134 if s, ok := status.FromError(err); ok { 135 return s.Message() 136 } 137 return err.Error() 138} 139