errdefs/http_helpers.go
2a9c987e
 package errdefs // import "github.com/docker/docker/errdefs"
a793564b
 
 import (
ebcb7d6b
 	"fmt"
a793564b
 	"net/http"
 
7b2d5556
 	containerderrors "github.com/containerd/containerd/errdefs"
cdcea6f4
 	"github.com/docker/distribution/registry/api/errcode"
1009e6a4
 	"github.com/sirupsen/logrus"
f257f77c
 	"google.golang.org/grpc/codes"
72b0b038
 	"google.golang.org/grpc/status"
a793564b
 )
 
ac9c47e2
 // GetHTTPErrorStatusCode retrieves status code from error message.
91e5bb95
 func GetHTTPErrorStatusCode(err error) int {
 	if err == nil {
 		logrus.WithFields(logrus.Fields{"error": err}).Error("unexpected HTTP error handling")
 		return http.StatusInternalServerError
a793564b
 	}
 
 	var statusCode int
 
ebcb7d6b
 	// Stop right there
 	// Are you sure you should be adding a new error class here? Do one of the existing ones work?
 
 	// Note that the below functions are already checking the error causal chain for matches.
 	switch {
2a9c987e
 	case IsNotFound(err):
ebcb7d6b
 		statusCode = http.StatusNotFound
2a9c987e
 	case IsInvalidParameter(err):
a793564b
 		statusCode = http.StatusBadRequest
7d4b7883
 	case IsConflict(err):
ebcb7d6b
 		statusCode = http.StatusConflict
2a9c987e
 	case IsUnauthorized(err):
ebcb7d6b
 		statusCode = http.StatusUnauthorized
2a9c987e
 	case IsUnavailable(err):
ebcb7d6b
 		statusCode = http.StatusServiceUnavailable
2a9c987e
 	case IsForbidden(err):
ebcb7d6b
 		statusCode = http.StatusForbidden
2a9c987e
 	case IsNotModified(err):
ebcb7d6b
 		statusCode = http.StatusNotModified
2a9c987e
 	case IsNotImplemented(err):
ebcb7d6b
 		statusCode = http.StatusNotImplemented
2a9c987e
 	case IsSystem(err) || IsUnknown(err) || IsDataLoss(err) || IsDeadline(err) || IsCancelled(err):
ebcb7d6b
 		statusCode = http.StatusInternalServerError
a793564b
 	default:
f257f77c
 		statusCode = statusCodeFromGRPCError(err)
 		if statusCode != http.StatusInternalServerError {
 			return statusCode
 		}
7b2d5556
 		statusCode = statusCodeFromContainerdError(err)
 		if statusCode != http.StatusInternalServerError {
 			return statusCode
 		}
cdcea6f4
 		statusCode = statusCodeFromDistributionError(err)
 		if statusCode != http.StatusInternalServerError {
 			return statusCode
 		}
ebcb7d6b
 		if e, ok := err.(causer); ok {
 			return GetHTTPErrorStatusCode(e.Cause())
a793564b
 		}
ebcb7d6b
 
 		logrus.WithFields(logrus.Fields{
 			"module":     "api",
 			"error_type": fmt.Sprintf("%T", err),
 		}).Debugf("FIXME: Got an API for which error does not match any expected type!!!: %+v", err)
a793564b
 	}
 
 	if statusCode == 0 {
 		statusCode = http.StatusInternalServerError
 	}
 
91e5bb95
 	return statusCode
 }
 
2a9c987e
 // FromStatusCode creates an errdef error, based on the provided HTTP status-code
1af30c50
 func FromStatusCode(err error, statusCode int) error {
 	if err == nil {
 		return err
 	}
 	switch statusCode {
 	case http.StatusNotFound:
2a9c987e
 		err = NotFound(err)
1af30c50
 	case http.StatusBadRequest:
2a9c987e
 		err = InvalidParameter(err)
1af30c50
 	case http.StatusConflict:
2a9c987e
 		err = Conflict(err)
1af30c50
 	case http.StatusUnauthorized:
2a9c987e
 		err = Unauthorized(err)
1af30c50
 	case http.StatusServiceUnavailable:
2a9c987e
 		err = Unavailable(err)
1af30c50
 	case http.StatusForbidden:
2a9c987e
 		err = Forbidden(err)
1af30c50
 	case http.StatusNotModified:
2a9c987e
 		err = NotModified(err)
1af30c50
 	case http.StatusNotImplemented:
2a9c987e
 		err = NotImplemented(err)
1af30c50
 	case http.StatusInternalServerError:
2a9c987e
 		if !IsSystem(err) && !IsUnknown(err) && !IsDataLoss(err) && !IsDeadline(err) && !IsCancelled(err) {
 			err = System(err)
1af30c50
 		}
 	default:
 		logrus.WithFields(logrus.Fields{
 			"module":      "api",
 			"status_code": fmt.Sprintf("%d", statusCode),
 		}).Debugf("FIXME: Got an status-code for which error does not match any expected type!!!: %d", statusCode)
 
 		switch {
 		case statusCode >= 200 && statusCode < 400:
 			// it's a client error
 		case statusCode >= 400 && statusCode < 500:
2a9c987e
 			err = InvalidParameter(err)
1af30c50
 		case statusCode >= 500 && statusCode < 600:
2a9c987e
 			err = System(err)
1af30c50
 		default:
2a9c987e
 			err = Unknown(err)
1af30c50
 		}
 	}
 	return err
 }
 
f257f77c
 // statusCodeFromGRPCError returns status code according to gRPC error
 func statusCodeFromGRPCError(err error) int {
72b0b038
 	switch status.Code(err) {
f257f77c
 	case codes.InvalidArgument: // code 3
 		return http.StatusBadRequest
 	case codes.NotFound: // code 5
 		return http.StatusNotFound
 	case codes.AlreadyExists: // code 6
 		return http.StatusConflict
 	case codes.PermissionDenied: // code 7
 		return http.StatusForbidden
 	case codes.FailedPrecondition: // code 9
 		return http.StatusBadRequest
 	case codes.Unauthenticated: // code 16
 		return http.StatusUnauthorized
 	case codes.OutOfRange: // code 11
 		return http.StatusBadRequest
 	case codes.Unimplemented: // code 12
 		return http.StatusNotImplemented
 	case codes.Unavailable: // code 14
 		return http.StatusServiceUnavailable
 	default:
ebcb7d6b
 		if e, ok := err.(causer); ok {
 			return statusCodeFromGRPCError(e.Cause())
 		}
f257f77c
 		// codes.Canceled(1)
 		// codes.Unknown(2)
 		// codes.DeadlineExceeded(4)
 		// codes.ResourceExhausted(8)
 		// codes.Aborted(10)
 		// codes.Internal(13)
 		// codes.DataLoss(15)
 		return http.StatusInternalServerError
 	}
 }
cdcea6f4
 
 // statusCodeFromDistributionError returns status code according to registry errcode
 // code is loosely based on errcode.ServeJSON() in docker/distribution
 func statusCodeFromDistributionError(err error) int {
 	switch errs := err.(type) {
 	case errcode.Errors:
 		if len(errs) < 1 {
 			return http.StatusInternalServerError
 		}
 		if _, ok := errs[0].(errcode.ErrorCoder); ok {
 			return statusCodeFromDistributionError(errs[0])
 		}
 	case errcode.ErrorCoder:
 		return errs.ErrorCode().Descriptor().HTTPStatusCode
 	default:
 		if e, ok := err.(causer); ok {
 			return statusCodeFromDistributionError(e.Cause())
 		}
 	}
 	return http.StatusInternalServerError
 }
7b2d5556
 
 // statusCodeFromContainerdError returns status code for containerd errors when
8ab5e2a0
 // consumed directly (not through gRPC)
7b2d5556
 func statusCodeFromContainerdError(err error) int {
 	switch {
 	case containerderrors.IsInvalidArgument(err):
 		return http.StatusBadRequest
 	case containerderrors.IsNotFound(err):
 		return http.StatusNotFound
 	case containerderrors.IsAlreadyExists(err):
 		return http.StatusConflict
 	case containerderrors.IsFailedPrecondition(err):
 		return http.StatusPreconditionFailed
 	case containerderrors.IsUnavailable(err):
 		return http.StatusServiceUnavailable
 	case containerderrors.IsNotImplemented(err):
 		return http.StatusNotImplemented
 	default:
 		return http.StatusInternalServerError
 	}
 }