handle predefined errors and handle types so we can tailor the experience
(err error, logger zerolog.Logger)
| 53 | |
| 54 | // handle predefined errors and handle types so we can tailor the experience |
| 55 | func errorInfo(err error, logger zerolog.Logger) (string, int) { |
| 56 | var msg string |
| 57 | exitCode := 1 |
| 58 | |
| 59 | // Extract message from grpc message if applicable |
| 60 | st, ok := status.FromError(err) |
| 61 | // It's a regular error |
| 62 | if !ok { |
| 63 | msg = err.Error() |
| 64 | } else { |
| 65 | msg = st.Message() |
| 66 | // Sanitize error message for validation and other known errors |
| 67 | // by default status.fromError(err).Message() returns the whole error chain |
| 68 | // i.e "creating API token: creating API token: rpc error: code = AlreadyExists desc = duplicated: name already taken" |
| 69 | // We do not want to show that in some specific error codes, we just want to show the part of the gRPC response |
| 70 | // i.e "duplicated: name already taken" |
| 71 | // To do what we perform an additional parsing of the error similar to |
| 72 | // https://github.com/grpc/grpc-go/blob/ced812e3287e15a009eab5b271c25750050a2f82/status/status.go#L123 |
| 73 | type grpcstatus interface{ GRPCStatus() *status.Status } |
| 74 | var gs grpcstatus |
| 75 | if errors.As(err, &gs) { |
| 76 | knownCodes := []codes.Code{ |
| 77 | codes.AlreadyExists, codes.InvalidArgument, codes.NotFound, codes.PermissionDenied, |
| 78 | codes.FailedPrecondition, |
| 79 | } |
| 80 | |
| 81 | grpcStatus := gs.GRPCStatus() |
| 82 | for _, code := range knownCodes { |
| 83 | if st.Code() == code { |
| 84 | msg = grpcStatus.Message() |
| 85 | break |
| 86 | } |
| 87 | } |
| 88 | } |
| 89 | } |
| 90 | |
| 91 | var gateErr *cmd.GateError |
| 92 | |
| 93 | // Make overrides |
| 94 | switch { |
| 95 | case v1.IsCasBackendErrorReasonRequired(err): |
| 96 | msg = "you need to enable a CAS backend first. Refer to `chainloop cas-backend` command or contact your administrator." |
| 97 | case v1.IsCasBackendErrorReasonInvalid(err): |
| 98 | msg = "the CAS backend you provided is invalid. Refer to `chainloop cas-backend update` command or contact your administrator." |
| 99 | case isWrappedErr(st, jwtMiddleware.ErrTokenExpired): |
| 100 | msg = "your authentication token has expired, please run \"chainloop auth login\" again" |
| 101 | case isWrappedErr(st, jwtMiddleware.ErrTokenInvalid): |
| 102 | msg = "your authentication token is invalid, please run \"chainloop auth login\" again" |
| 103 | case isWrappedErr(st, jwtMiddleware.ErrTokenParseFail): |
| 104 | msg = "failed to parse authentication token, please run \"chainloop auth login\" again" |
| 105 | case isWrappedErr(st, jwtMiddleware.ErrMissingJwtToken): |
| 106 | msg = "authentication required, please run \"chainloop auth login\"" |
| 107 | case v1.IsUserNotMemberOfOrgErrorNotInOrg(err): |
| 108 | msg = "the organization you are trying to access does not exist or you are not part of it, please run \"chainloop auth login\"" |
| 109 | case v1.IsUserWithNoMembershipErrorNotInOrg(err): |
| 110 | msg = "you are not part of any organization, please run \"chainloop organization create --name ORG_NAME\" to create one" |
| 111 | case isUnmatchedAuthErr(st): |
| 112 | // Fallback for any other 401/Unauthenticated errors not matched above. |
no test coverage detected