status.go 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152
  1. package httpstatus // import "github.com/docker/docker/api/server/httpstatus"
  2. import (
  3. "context"
  4. "fmt"
  5. "net/http"
  6. cerrdefs "github.com/containerd/containerd/errdefs"
  7. "github.com/containerd/log"
  8. "github.com/docker/distribution/registry/api/errcode"
  9. "github.com/docker/docker/errdefs"
  10. "google.golang.org/grpc/codes"
  11. "google.golang.org/grpc/status"
  12. )
  13. type causer interface {
  14. Cause() error
  15. }
  16. // FromError retrieves status code from error message.
  17. func FromError(err error) int {
  18. if err == nil {
  19. log.G(context.TODO()).WithError(err).Error("unexpected HTTP error handling")
  20. return http.StatusInternalServerError
  21. }
  22. var statusCode int
  23. // Stop right there
  24. // Are you sure you should be adding a new error class here? Do one of the existing ones work?
  25. // Note that the below functions are already checking the error causal chain for matches.
  26. switch {
  27. case errdefs.IsNotFound(err):
  28. statusCode = http.StatusNotFound
  29. case errdefs.IsInvalidParameter(err):
  30. statusCode = http.StatusBadRequest
  31. case errdefs.IsConflict(err):
  32. statusCode = http.StatusConflict
  33. case errdefs.IsUnauthorized(err):
  34. statusCode = http.StatusUnauthorized
  35. case errdefs.IsUnavailable(err):
  36. statusCode = http.StatusServiceUnavailable
  37. case errdefs.IsForbidden(err):
  38. statusCode = http.StatusForbidden
  39. case errdefs.IsNotModified(err):
  40. statusCode = http.StatusNotModified
  41. case errdefs.IsNotImplemented(err):
  42. statusCode = http.StatusNotImplemented
  43. case errdefs.IsSystem(err) || errdefs.IsUnknown(err) || errdefs.IsDataLoss(err) || errdefs.IsDeadline(err) || errdefs.IsCancelled(err):
  44. statusCode = http.StatusInternalServerError
  45. default:
  46. statusCode = statusCodeFromGRPCError(err)
  47. if statusCode != http.StatusInternalServerError {
  48. return statusCode
  49. }
  50. statusCode = statusCodeFromContainerdError(err)
  51. if statusCode != http.StatusInternalServerError {
  52. return statusCode
  53. }
  54. statusCode = statusCodeFromDistributionError(err)
  55. if statusCode != http.StatusInternalServerError {
  56. return statusCode
  57. }
  58. if e, ok := err.(causer); ok {
  59. return FromError(e.Cause())
  60. }
  61. log.G(context.TODO()).WithFields(log.Fields{
  62. "module": "api",
  63. "error": err,
  64. "error_type": fmt.Sprintf("%T", err),
  65. }).Debug("FIXME: Got an API for which error does not match any expected type!!!")
  66. }
  67. if statusCode == 0 {
  68. statusCode = http.StatusInternalServerError
  69. }
  70. return statusCode
  71. }
  72. // statusCodeFromGRPCError returns status code according to gRPC error
  73. func statusCodeFromGRPCError(err error) int {
  74. switch status.Code(err) {
  75. case codes.InvalidArgument: // code 3
  76. return http.StatusBadRequest
  77. case codes.NotFound: // code 5
  78. return http.StatusNotFound
  79. case codes.AlreadyExists: // code 6
  80. return http.StatusConflict
  81. case codes.PermissionDenied: // code 7
  82. return http.StatusForbidden
  83. case codes.FailedPrecondition: // code 9
  84. return http.StatusBadRequest
  85. case codes.Unauthenticated: // code 16
  86. return http.StatusUnauthorized
  87. case codes.OutOfRange: // code 11
  88. return http.StatusBadRequest
  89. case codes.Unimplemented: // code 12
  90. return http.StatusNotImplemented
  91. case codes.Unavailable: // code 14
  92. return http.StatusServiceUnavailable
  93. default:
  94. // codes.Canceled(1)
  95. // codes.Unknown(2)
  96. // codes.DeadlineExceeded(4)
  97. // codes.ResourceExhausted(8)
  98. // codes.Aborted(10)
  99. // codes.Internal(13)
  100. // codes.DataLoss(15)
  101. return http.StatusInternalServerError
  102. }
  103. }
  104. // statusCodeFromDistributionError returns status code according to registry errcode
  105. // code is loosely based on errcode.ServeJSON() in docker/distribution
  106. func statusCodeFromDistributionError(err error) int {
  107. switch errs := err.(type) {
  108. case errcode.Errors:
  109. if len(errs) < 1 {
  110. return http.StatusInternalServerError
  111. }
  112. if _, ok := errs[0].(errcode.ErrorCoder); ok {
  113. return statusCodeFromDistributionError(errs[0])
  114. }
  115. case errcode.ErrorCoder:
  116. return errs.ErrorCode().Descriptor().HTTPStatusCode
  117. }
  118. return http.StatusInternalServerError
  119. }
  120. // statusCodeFromContainerdError returns status code for containerd errors when
  121. // consumed directly (not through gRPC)
  122. func statusCodeFromContainerdError(err error) int {
  123. switch {
  124. case cerrdefs.IsInvalidArgument(err):
  125. return http.StatusBadRequest
  126. case cerrdefs.IsNotFound(err):
  127. return http.StatusNotFound
  128. case cerrdefs.IsAlreadyExists(err):
  129. return http.StatusConflict
  130. case cerrdefs.IsFailedPrecondition(err):
  131. return http.StatusPreconditionFailed
  132. case cerrdefs.IsUnavailable(err):
  133. return http.StatusServiceUnavailable
  134. case cerrdefs.IsNotImplemented(err):
  135. return http.StatusNotImplemented
  136. default:
  137. return http.StatusInternalServerError
  138. }
  139. }