feat: propagate detailed error msgs to client (OCI dist-spec format) (#1681)

Signed-off-by: Alexei Dodon <adodon@cisco.com>
This commit is contained in:
Alexei Dodon
2023-08-23 20:59:52 +03:00
committed by GitHub
parent 94429a82df
commit 247f6dcd3f
10 changed files with 292 additions and 200 deletions
+5 -6
View File
@@ -36,7 +36,7 @@ import (
"zotregistry.io/zot/pkg/api/config"
"zotregistry.io/zot/pkg/api/constants"
apiErr "zotregistry.io/zot/pkg/api/errors"
"zotregistry.io/zot/pkg/common"
zcommon "zotregistry.io/zot/pkg/common"
"zotregistry.io/zot/pkg/log"
localCtx "zotregistry.io/zot/pkg/requestcontext"
storageConstants "zotregistry.io/zot/pkg/storage/constants"
@@ -463,7 +463,7 @@ func bearerAuthHandler(ctlr *Controller) mux.MiddlewareFunc {
if err != nil {
ctlr.Log.Error().Err(err).Msg("issue parsing Authorization header")
response.Header().Set("Content-Type", "application/json")
common.WriteJSON(response, http.StatusInternalServerError, apiErr.NewErrorList(apiErr.NewError(apiErr.UNSUPPORTED)))
zcommon.WriteJSON(response, http.StatusInternalServerError, apiErr.NewError(apiErr.UNSUPPORTED))
return
}
@@ -472,8 +472,7 @@ func bearerAuthHandler(ctlr *Controller) mux.MiddlewareFunc {
response.Header().Set("Content-Type", "application/json")
response.Header().Set("WWW-Authenticate", permissions.WWWAuthenticateHeader)
common.WriteJSON(response, http.StatusUnauthorized,
apiErr.NewErrorList(apiErr.NewError(apiErr.UNAUTHORIZED)))
zcommon.WriteJSON(response, http.StatusUnauthorized, apiErr.NewError(apiErr.UNAUTHORIZED))
return
}
@@ -591,7 +590,7 @@ func getRelyingPartyArgs(cfg *config.Config, provider string) (
scopes := cfg.HTTP.Auth.OpenID.Providers[provider].Scopes
// openid scope must be the first one in list
if !common.Contains(scopes, oidc.ScopeOpenID) && config.IsOpenIDSupported(provider) {
if !zcommon.Contains(scopes, oidc.ScopeOpenID) && config.IsOpenIDSupported(provider) {
scopes = append([]string{oidc.ScopeOpenID}, scopes...)
}
@@ -663,7 +662,7 @@ func authFail(w http.ResponseWriter, r *http.Request, realm string, delay int) {
}
w.Header().Set("Content-Type", "application/json")
common.WriteJSON(w, http.StatusUnauthorized, apiErr.NewErrorList(apiErr.NewError(apiErr.UNAUTHORIZED)))
zcommon.WriteJSON(w, http.StatusUnauthorized, apiErr.NewError(apiErr.UNAUTHORIZED))
}
func isAuthorizationHeaderEmpty(request *http.Request) bool {
+44 -48
View File
@@ -5,16 +5,10 @@ import (
)
type Error struct {
Code string `json:"code"`
Message string `json:"message"`
Description string `json:"description"`
Detail interface{} `json:"detail,omitempty"`
}
func (e Error) WithMessage(msg string) Error {
e.Message = msg
return e
Code string `json:"code"`
Message string `json:"message"`
Description string `json:"-"`
Detail map[string]string `json:"detail"`
}
type ErrorList struct {
@@ -66,7 +60,7 @@ func (e ErrorCode) String() string {
return errMap[e]
}
func NewError(code ErrorCode, detail ...interface{}) Error {
func NewError(code ErrorCode) *Error {
errMap := map[ErrorCode]Error{
BLOB_UNKNOWN: {
Message: "blob unknown to registry",
@@ -77,12 +71,12 @@ func NewError(code ErrorCode, detail ...interface{}) Error {
BLOB_UPLOAD_INVALID: {
Message: "blob upload invalid",
Description: `The blob upload encountered an error and can no longer proceed.`,
Description: "The blob upload encountered an error and can no longer proceed.",
},
BLOB_UPLOAD_UNKNOWN: {
Message: "blob upload unknown to registry",
Description: `If a blob upload has been cancelled or was never started, this error code MAY be returned.`,
Description: "If a blob upload has been cancelled or was never started, this error code MAY be returned.",
},
DIGEST_INVALID: {
@@ -94,71 +88,68 @@ func NewError(code ErrorCode, detail ...interface{}) Error {
},
MANIFEST_BLOB_UNKNOWN: {
Message: "blob unknown to registry",
Description: `This error MAY be returned when a manifest blob is unknown
to the registry.`,
Message: "blob unknown to registry",
Description: "This error MAY be returned when a manifest blob is unknown to the registry.",
},
MANIFEST_INVALID: {
Message: "manifest invalid",
Description: `During upload, manifests undergo several checks ensuring
validity. If those checks fail, this error MAY be returned, unless a more
specific error is included. The detail will contain information the failed
validation.`,
Description: "During upload, manifests undergo several checks ensuring " +
"validity. If those checks fail, this error MAY be returned, unless a more " +
"specific error is included. The detail will contain information the failed validation.",
},
MANIFEST_UNKNOWN: {
Message: "manifest unknown",
Description: `This error is returned when the manifest, identified by name
and tag is unknown to the repository.`,
Description: "This error is returned when the manifest, identified by name " +
"and tag is unknown to the repository.",
},
MANIFEST_UNVERIFIED: {
Message: "manifest failed signature verification",
Description: `During manifest upload, if the manifest fails signature
verification, this error will be returned.`,
Description: "During manifest upload, if the manifest fails signature " +
"verification, this error will be returned.",
},
NAME_INVALID: {
Message: "invalid repository name",
Description: `Invalid repository name encountered either during manifest
validation or any API operation.`,
Description: "Invalid repository name encountered either during manifest " +
"validation or any API operation.",
},
NAME_UNKNOWN: {
Message: "repository name not known to registry",
Description: `This is returned if the name used during an operation is unknown to the registry.`,
Description: "This is returned if the name used during an operation is unknown to the registry.",
},
SIZE_INVALID: {
Message: "provided length did not match content length",
Description: "When a layer is uploaded, the provided size will be checked against the uploaded " +
"content. If they do not match, this error will be returned.",
Description: "When a layer is uploaded, the provided size will be checked against " +
"the uploaded content. If they do not match, this error will be returned.",
},
TAG_INVALID: {
Message: "manifest tag did not match URI",
Description: `During a manifest upload, if the tag in the manifest does
not match the uri tag, this error will be returned.`,
Description: "During a manifest upload, if the tag in the manifest does " +
"not match the uri tag, this error will be returned.",
},
UNAUTHORIZED: {
Message: "authentication required",
Description: `The access controller was unable to authenticate the client.
Often this will be accompanied by a Www-Authenticate HTTP response header
indicating how to authenticate.`,
Description: "The access controller was unable to authenticate the client." +
"Often this will be accompanied by a Www-Authenticate HTTP response header " +
"indicating how to authenticate.",
},
DENIED: {
Message: "requested access to the resource is denied",
Description: `The access controller denied access for the operation on a
resource.`,
Message: "requested access to the resource is denied",
Description: "The access controller denied access for the operation on a resource.",
},
UNSUPPORTED: {
Message: "The operation is unsupported.",
Description: `The operation was unsupported due to a missing
implementation or invalid set of parameters.`,
Description: "The operation was unsupported due to a missing " +
"implementation or invalid set of parameters.",
},
INVALID_INDEX: {
@@ -173,19 +164,24 @@ func NewError(code ErrorCode, detail ...interface{}) Error {
}
err.Code = code.String()
err.Detail = detail
err.Detail = map[string]string{
"description": err.Description,
}
return &err
}
func (err *Error) AddDetail(m map[string]string) *Error {
for k, v := range m {
err.Detail[k] = v
}
return err
}
func NewErrorList(errors ...Error) ErrorList {
errList := make([]*Error, 0)
err := Error{}
for _, e := range errors {
err = e
errList = append(errList, &err)
}
func NewErrorList(errors ...*Error) ErrorList {
var errList []*Error
errList = append(errList, errors...)
return ErrorList{errList}
}
+1 -1
View File
@@ -10,6 +10,6 @@ import (
func TestUnknownCodeError(t *testing.T) {
Convey("Retrieve a new error with unknown code", t, func() {
So(func() { _ = apiErr.NewError(123456789, nil) }, ShouldPanic)
So(func() { _ = apiErr.NewError(123456789) }, ShouldPanic)
})
}
+178 -127
View File
@@ -334,8 +334,8 @@ func (rh *RouteHandler) ListTags(response http.ResponseWriter, request *http.Req
tags, err := imgStore.GetImageTags(name)
if err != nil {
zcommon.WriteJSON(response, http.StatusNotFound,
apiErr.NewErrorList(apiErr.NewError(apiErr.NAME_UNKNOWN, map[string]string{"name": name})))
e := apiErr.NewError(apiErr.NAME_UNKNOWN).AddDetail(map[string]string{"name": name})
zcommon.WriteJSON(response, http.StatusNotFound, apiErr.NewErrorList(e))
return
}
@@ -422,25 +422,27 @@ func (rh *RouteHandler) CheckManifest(response http.ResponseWriter, request *htt
reference, ok := vars["reference"]
if !ok || reference == "" {
zcommon.WriteJSON(response,
http.StatusNotFound,
apiErr.NewErrorList(apiErr.NewError(apiErr.MANIFEST_INVALID, map[string]string{"reference": reference})))
e := apiErr.NewError(apiErr.MANIFEST_INVALID).AddDetail(map[string]string{"reference": reference})
zcommon.WriteJSON(response, http.StatusNotFound, apiErr.NewErrorList(e))
return
}
content, digest, mediaType, err := getImageManifest(rh, imgStore, name, reference) //nolint:contextcheck
if err != nil {
details := zerr.GetDetails(err)
details["reference"] = reference
if errors.Is(err, zerr.ErrRepoNotFound) { //nolint:gocritic // errorslint conflicts with gocritic:IfElseChain
zcommon.WriteJSON(response, http.StatusNotFound,
apiErr.NewErrorList(apiErr.NewError(apiErr.NAME_UNKNOWN, map[string]string{"reference": reference})))
e := apiErr.NewError(apiErr.NAME_UNKNOWN).AddDetail(details)
zcommon.WriteJSON(response, http.StatusNotFound, apiErr.NewErrorList(e))
} else if errors.Is(err, zerr.ErrManifestNotFound) {
zcommon.WriteJSON(response, http.StatusNotFound,
apiErr.NewErrorList(apiErr.NewError(apiErr.MANIFEST_UNKNOWN, map[string]string{"reference": reference})))
e := apiErr.NewError(apiErr.MANIFEST_UNKNOWN).AddDetail(details)
zcommon.WriteJSON(response, http.StatusNotFound, apiErr.NewErrorList(e))
} else {
rh.c.Log.Error().Err(err).Msg("unexpected error")
zcommon.WriteJSON(response, http.StatusInternalServerError,
apiErr.NewErrorList(apiErr.NewError(apiErr.MANIFEST_INVALID, map[string]string{"reference": reference})))
e := apiErr.NewError(apiErr.MANIFEST_INVALID).AddDetail(details)
zcommon.WriteJSON(response, http.StatusInternalServerError, apiErr.NewErrorList(e))
}
return
@@ -490,24 +492,27 @@ func (rh *RouteHandler) GetManifest(response http.ResponseWriter, request *http.
reference, ok := vars["reference"]
if !ok || reference == "" {
zcommon.WriteJSON(response,
http.StatusNotFound,
apiErr.NewErrorList(apiErr.NewError(apiErr.MANIFEST_UNKNOWN, map[string]string{"reference": reference})))
err := apiErr.NewError(apiErr.MANIFEST_UNKNOWN).AddDetail(map[string]string{"reference": reference})
zcommon.WriteJSON(response, http.StatusNotFound, apiErr.NewErrorList(err))
return
}
content, digest, mediaType, err := getImageManifest(rh, imgStore, name, reference) //nolint: contextcheck
if err != nil {
details := zerr.GetDetails(err)
if errors.Is(err, zerr.ErrRepoNotFound) { //nolint:gocritic // errorslint conflicts with gocritic:IfElseChain
zcommon.WriteJSON(response, http.StatusNotFound,
apiErr.NewErrorList(apiErr.NewError(apiErr.NAME_UNKNOWN, map[string]string{"name": name})))
details["name"] = name
e := apiErr.NewError(apiErr.NAME_UNKNOWN).AddDetail(details)
zcommon.WriteJSON(response, http.StatusNotFound, apiErr.NewErrorList(e))
} else if errors.Is(err, zerr.ErrRepoBadVersion) {
zcommon.WriteJSON(response, http.StatusNotFound,
apiErr.NewErrorList(apiErr.NewError(apiErr.NAME_UNKNOWN, map[string]string{"name": name})))
details["name"] = name
e := apiErr.NewError(apiErr.NAME_UNKNOWN).AddDetail(details)
zcommon.WriteJSON(response, http.StatusNotFound, apiErr.NewErrorList(e))
} else if errors.Is(err, zerr.ErrManifestNotFound) {
zcommon.WriteJSON(response, http.StatusNotFound,
apiErr.NewErrorList(apiErr.NewError(apiErr.MANIFEST_UNKNOWN, map[string]string{"reference": reference})))
details["reference"] = reference
e := apiErr.NewError(apiErr.MANIFEST_UNKNOWN).AddDetail(details)
zcommon.WriteJSON(response, http.StatusNotFound, apiErr.NewErrorList(e))
} else {
rh.c.Log.Error().Err(err).Msg("unexpected error")
response.WriteHeader(http.StatusInternalServerError)
@@ -655,18 +660,16 @@ func (rh *RouteHandler) UpdateManifest(response http.ResponseWriter, request *ht
reference, ok := vars["reference"]
if !ok || reference == "" {
zcommon.WriteJSON(response,
http.StatusNotFound,
apiErr.NewErrorList(apiErr.NewError(apiErr.MANIFEST_INVALID, map[string]string{"reference": reference})))
err := apiErr.NewError(apiErr.MANIFEST_INVALID).AddDetail(map[string]string{"reference": reference})
zcommon.WriteJSON(response, http.StatusNotFound, apiErr.NewErrorList(err))
return
}
mediaType := request.Header.Get("Content-Type")
if !storageCommon.IsSupportedMediaType(mediaType) {
// response.WriteHeader(http.StatusUnsupportedMediaType)
zcommon.WriteJSON(response, http.StatusUnsupportedMediaType,
apiErr.NewErrorList(apiErr.NewError(apiErr.MANIFEST_INVALID, map[string]string{"mediaType": mediaType})))
err := apiErr.NewError(apiErr.MANIFEST_INVALID).AddDetail(map[string]string{"mediaType": mediaType})
zcommon.WriteJSON(response, http.StatusUnsupportedMediaType, apiErr.NewErrorList(err))
return
}
@@ -683,25 +686,31 @@ func (rh *RouteHandler) UpdateManifest(response http.ResponseWriter, request *ht
digest, subjectDigest, err := imgStore.PutImageManifest(name, reference, mediaType, body)
if err != nil {
details := zerr.GetDetails(err)
if errors.Is(err, zerr.ErrRepoNotFound) { //nolint:gocritic // errorslint conflicts with gocritic:IfElseChain
zcommon.WriteJSON(response, http.StatusNotFound,
apiErr.NewErrorList(apiErr.NewError(apiErr.NAME_UNKNOWN, map[string]string{"name": name})))
details["name"] = name
e := apiErr.NewError(apiErr.NAME_UNKNOWN).AddDetail(details)
zcommon.WriteJSON(response, http.StatusNotFound, apiErr.NewErrorList(e))
} else if errors.Is(err, zerr.ErrManifestNotFound) {
zcommon.WriteJSON(response, http.StatusNotFound,
apiErr.NewErrorList(apiErr.NewError(apiErr.MANIFEST_UNKNOWN, map[string]string{"reference": reference})))
details["reference"] = reference
e := apiErr.NewError(apiErr.MANIFEST_UNKNOWN).AddDetail(details)
zcommon.WriteJSON(response, http.StatusNotFound, apiErr.NewErrorList(e))
} else if errors.Is(err, zerr.ErrBadManifest) {
zcommon.WriteJSON(response, http.StatusBadRequest,
apiErr.NewErrorList(apiErr.NewError(apiErr.MANIFEST_INVALID, map[string]string{"reference": reference})))
details["reference"] = reference
e := apiErr.NewError(apiErr.MANIFEST_INVALID).AddDetail(details)
zcommon.WriteJSON(response, http.StatusBadRequest, apiErr.NewErrorList(e))
} else if errors.Is(err, zerr.ErrBlobNotFound) {
zcommon.WriteJSON(response, http.StatusBadRequest,
apiErr.NewErrorList(apiErr.NewError(apiErr.BLOB_UNKNOWN, map[string]string{"blob": digest.String()})))
details["blob"] = digest.String()
e := apiErr.NewError(apiErr.BLOB_UNKNOWN).AddDetail(details)
zcommon.WriteJSON(response, http.StatusBadRequest, apiErr.NewErrorList(e))
} else if errors.Is(err, zerr.ErrRepoBadVersion) {
zcommon.WriteJSON(response, http.StatusInternalServerError,
apiErr.NewErrorList(apiErr.NewError(apiErr.INVALID_INDEX, map[string]string{"name": name})))
details["name"] = name
e := apiErr.NewError(apiErr.INVALID_INDEX).AddDetail(details)
zcommon.WriteJSON(response, http.StatusInternalServerError, apiErr.NewErrorList(e))
} else if errors.Is(err, zerr.ErrImageLintAnnotations) {
zcommon.WriteJSON(response, http.StatusBadRequest,
apiErr.NewErrorList(apiErr.NewError(
apiErr.MANIFEST_INVALID, map[string]string{"reference": reference}).WithMessage(err.Error())))
details["reference"] = reference
e := apiErr.NewError(apiErr.MANIFEST_INVALID).AddDetail(details)
zcommon.WriteJSON(response, http.StatusBadRequest, apiErr.NewErrorList(e))
} else {
// could be syscall.EMFILE (Err:0x18 too many opened files), etc
rh.c.Log.Error().Err(err).Msg("unexpected error: performing cleanup")
@@ -782,15 +791,19 @@ func (rh *RouteHandler) DeleteManifest(response http.ResponseWriter, request *ht
manifestBlob, manifestDigest, mediaType, err := imgStore.GetImageManifest(name, reference)
if err != nil {
details := zerr.GetDetails(err)
if errors.Is(err, zerr.ErrRepoNotFound) { //nolint:gocritic // errorslint conflicts with gocritic:IfElseChain
zcommon.WriteJSON(response, http.StatusBadRequest,
apiErr.NewErrorList(apiErr.NewError(apiErr.NAME_UNKNOWN, map[string]string{"name": name})))
details["name"] = name
e := apiErr.NewError(apiErr.NAME_UNKNOWN).AddDetail(details)
zcommon.WriteJSON(response, http.StatusBadRequest, apiErr.NewErrorList(e))
} else if errors.Is(err, zerr.ErrManifestNotFound) {
zcommon.WriteJSON(response, http.StatusNotFound,
apiErr.NewErrorList(apiErr.NewError(apiErr.MANIFEST_UNKNOWN, map[string]string{"reference": reference})))
details["reference"] = reference
e := apiErr.NewError(apiErr.MANIFEST_UNKNOWN).AddDetail(details)
zcommon.WriteJSON(response, http.StatusNotFound, apiErr.NewErrorList(e))
} else if errors.Is(err, zerr.ErrBadManifest) {
zcommon.WriteJSON(response, http.StatusBadRequest,
apiErr.NewErrorList(apiErr.NewError(apiErr.UNSUPPORTED, map[string]string{"reference": reference})))
details["reference"] = reference
e := apiErr.NewError(apiErr.UNSUPPORTED).AddDetail(details)
zcommon.WriteJSON(response, http.StatusBadRequest, apiErr.NewErrorList(e))
} else {
rh.c.Log.Error().Err(err).Msg("unexpected error")
response.WriteHeader(http.StatusInternalServerError)
@@ -800,19 +813,24 @@ func (rh *RouteHandler) DeleteManifest(response http.ResponseWriter, request *ht
}
err = imgStore.DeleteImageManifest(name, reference, detectCollision)
if err != nil {
if err != nil { //nolint: dupl
details := zerr.GetDetails(err)
if errors.Is(err, zerr.ErrRepoNotFound) { //nolint:gocritic // errorslint conflicts with gocritic:IfElseChain
zcommon.WriteJSON(response, http.StatusBadRequest,
apiErr.NewErrorList(apiErr.NewError(apiErr.NAME_UNKNOWN, map[string]string{"name": name})))
details["name"] = name
e := apiErr.NewError(apiErr.NAME_UNKNOWN).AddDetail(details)
zcommon.WriteJSON(response, http.StatusBadRequest, apiErr.NewErrorList(e))
} else if errors.Is(err, zerr.ErrManifestNotFound) {
zcommon.WriteJSON(response, http.StatusNotFound,
apiErr.NewErrorList(apiErr.NewError(apiErr.MANIFEST_UNKNOWN, map[string]string{"reference": reference})))
details["reference"] = reference
e := apiErr.NewError(apiErr.MANIFEST_UNKNOWN).AddDetail(details)
zcommon.WriteJSON(response, http.StatusNotFound, apiErr.NewErrorList(e))
} else if errors.Is(err, zerr.ErrManifestConflict) {
zcommon.WriteJSON(response, http.StatusConflict,
apiErr.NewErrorList(apiErr.NewError(apiErr.MANIFEST_INVALID, map[string]string{"reference": reference})))
details["reference"] = reference
e := apiErr.NewError(apiErr.MANIFEST_INVALID).AddDetail(details)
zcommon.WriteJSON(response, http.StatusConflict, apiErr.NewErrorList(e))
} else if errors.Is(err, zerr.ErrBadManifest) {
zcommon.WriteJSON(response, http.StatusBadRequest,
apiErr.NewErrorList(apiErr.NewError(apiErr.UNSUPPORTED, map[string]string{"reference": reference})))
details["reference"] = reference
e := apiErr.NewError(apiErr.UNSUPPORTED).AddDetail(details)
zcommon.WriteJSON(response, http.StatusBadRequest, apiErr.NewErrorList(e))
} else {
rh.c.Log.Error().Err(err).Msg("unexpected error")
response.WriteHeader(http.StatusInternalServerError)
@@ -868,17 +886,19 @@ func (rh *RouteHandler) CheckBlob(response http.ResponseWriter, request *http.Re
ok, blen, err := imgStore.CheckBlob(name, digest)
if err != nil {
details := zerr.GetDetails(err)
if errors.Is(err, zerr.ErrBadBlobDigest) { //nolint:gocritic // errorslint conflicts with gocritic:IfElseChain
zcommon.WriteJSON(response,
http.StatusBadRequest,
apiErr.NewErrorList(apiErr.NewError(apiErr.DIGEST_INVALID, map[string]string{"digest": digest.String()})))
details["digest"] = digest.String()
e := apiErr.NewError(apiErr.DIGEST_INVALID).AddDetail(details)
zcommon.WriteJSON(response, http.StatusBadRequest, apiErr.NewErrorList(e))
} else if errors.Is(err, zerr.ErrRepoNotFound) {
zcommon.WriteJSON(response, http.StatusNotFound,
apiErr.NewErrorList(apiErr.NewError(apiErr.NAME_UNKNOWN, map[string]string{"name": name})))
details["name"] = name
e := apiErr.NewError(apiErr.NAME_UNKNOWN).AddDetail(details)
zcommon.WriteJSON(response, http.StatusNotFound, apiErr.NewErrorList(e))
} else if errors.Is(err, zerr.ErrBlobNotFound) {
zcommon.WriteJSON(response, http.StatusNotFound,
apiErr.NewErrorList(apiErr.NewError(apiErr.BLOB_UNKNOWN,
map[string]string{"digest": digest.String()})))
details["digest"] = digest.String()
e := apiErr.NewError(apiErr.BLOB_UNKNOWN).AddDetail(details)
zcommon.WriteJSON(response, http.StatusNotFound, apiErr.NewErrorList(e))
} else {
rh.c.Log.Error().Err(err).Msg("unexpected error")
response.WriteHeader(http.StatusInternalServerError)
@@ -888,8 +908,8 @@ func (rh *RouteHandler) CheckBlob(response http.ResponseWriter, request *http.Re
}
if !ok {
zcommon.WriteJSON(response, http.StatusNotFound, apiErr.NewErrorList(apiErr.NewError(apiErr.BLOB_UNKNOWN,
map[string]string{"digest": digest.String()})))
e := apiErr.NewError(apiErr.BLOB_UNKNOWN).AddDetail(map[string]string{"digest": digest.String()})
zcommon.WriteJSON(response, http.StatusNotFound, apiErr.NewErrorList(e))
return
}
@@ -1018,18 +1038,19 @@ func (rh *RouteHandler) GetBlob(response http.ResponseWriter, request *http.Requ
}
if err != nil {
details := zerr.GetDetails(err)
if errors.Is(err, zerr.ErrBadBlobDigest) { //nolint:gocritic // errorslint conflicts with gocritic:IfElseChain
zcommon.WriteJSON(response,
http.StatusBadRequest,
apiErr.NewErrorList(apiErr.NewError(apiErr.DIGEST_INVALID, map[string]string{"digest": digest.String()})))
details["digest"] = digest.String()
e := apiErr.NewError(apiErr.DIGEST_INVALID).AddDetail(details)
zcommon.WriteJSON(response, http.StatusBadRequest, apiErr.NewErrorList(e))
} else if errors.Is(err, zerr.ErrRepoNotFound) {
zcommon.WriteJSON(response,
http.StatusNotFound,
apiErr.NewErrorList(apiErr.NewError(apiErr.NAME_UNKNOWN, map[string]string{"name": name})))
details["name"] = name
e := apiErr.NewError(apiErr.NAME_UNKNOWN).AddDetail(details)
zcommon.WriteJSON(response, http.StatusNotFound, apiErr.NewErrorList(e))
} else if errors.Is(err, zerr.ErrBlobNotFound) {
zcommon.WriteJSON(response,
http.StatusNotFound,
apiErr.NewErrorList(apiErr.NewError(apiErr.BLOB_UNKNOWN, map[string]string{"digest": digest.String()})))
details["digest"] = digest.String()
e := apiErr.NewError(apiErr.BLOB_UNKNOWN).AddDetail(details)
zcommon.WriteJSON(response, http.StatusNotFound, apiErr.NewErrorList(e))
} else {
rh.c.Log.Error().Err(err).Msg("unexpected error")
response.WriteHeader(http.StatusInternalServerError)
@@ -1087,22 +1108,23 @@ func (rh *RouteHandler) DeleteBlob(response http.ResponseWriter, request *http.R
err = imgStore.DeleteBlob(name, digest)
if err != nil {
details := zerr.GetDetails(err)
if errors.Is(err, zerr.ErrBadBlobDigest) { //nolint:gocritic // errorslint conflicts with gocritic:IfElseChain
zcommon.WriteJSON(response,
http.StatusBadRequest,
apiErr.NewErrorList(apiErr.NewError(apiErr.DIGEST_INVALID, map[string]string{"digest": digest.String()})))
details["digest"] = digest.String()
e := apiErr.NewError(apiErr.DIGEST_INVALID).AddDetail(details)
zcommon.WriteJSON(response, http.StatusBadRequest, apiErr.NewErrorList(e))
} else if errors.Is(err, zerr.ErrRepoNotFound) {
zcommon.WriteJSON(response,
http.StatusNotFound,
apiErr.NewErrorList(apiErr.NewError(apiErr.NAME_UNKNOWN, map[string]string{"name": name})))
details["name"] = name
e := apiErr.NewError(apiErr.NAME_UNKNOWN).AddDetail(map[string]string{"name": name})
zcommon.WriteJSON(response, http.StatusNotFound, apiErr.NewErrorList(e))
} else if errors.Is(err, zerr.ErrBlobNotFound) {
zcommon.WriteJSON(response,
http.StatusNotFound,
apiErr.NewErrorList(apiErr.NewError(apiErr.BLOB_UNKNOWN, map[string]string{".String()": digest.String()})))
details["digest"] = digest.String()
e := apiErr.NewError(apiErr.BLOB_UNKNOWN).AddDetail(details)
zcommon.WriteJSON(response, http.StatusNotFound, apiErr.NewErrorList(e))
} else if errors.Is(err, zerr.ErrBlobReferenced) {
zcommon.WriteJSON(response,
http.StatusMethodNotAllowed,
apiErr.NewErrorList(apiErr.NewError(apiErr.DENIED, map[string]string{".String()": digest.String()})))
details["digest"] = digest.String()
e := apiErr.NewError(apiErr.DENIED).AddDetail(details)
zcommon.WriteJSON(response, http.StatusMethodNotAllowed, apiErr.NewErrorList(e))
} else {
rh.c.Log.Error().Err(err).Msg("unexpected error")
response.WriteHeader(http.StatusInternalServerError)
@@ -1154,9 +1176,11 @@ func (rh *RouteHandler) CreateBlobUpload(response http.ResponseWriter, request *
if err != nil {
upload, err := imgStore.NewBlobUpload(name)
if err != nil {
details := zerr.GetDetails(err)
if errors.Is(err, zerr.ErrRepoNotFound) {
zcommon.WriteJSON(response, http.StatusNotFound,
apiErr.NewErrorList(apiErr.NewError(apiErr.NAME_UNKNOWN, map[string]string{"name": name})))
details["name"] = name
e := apiErr.NewError(apiErr.NAME_UNKNOWN).AddDetail(details)
zcommon.WriteJSON(response, http.StatusNotFound, apiErr.NewErrorList(e))
} else {
rh.c.Log.Error().Err(err).Msg("unexpected error")
response.WriteHeader(http.StatusInternalServerError)
@@ -1211,8 +1235,15 @@ func (rh *RouteHandler) CreateBlobUpload(response http.ResponseWriter, request *
contentLength, err := strconv.ParseInt(request.Header.Get("Content-Length"), 10, 64)
if err != nil || contentLength <= 0 {
rh.c.Log.Warn().Str("actual", request.Header.Get("Content-Length")).Msg("invalid content length")
zcommon.WriteJSON(response, http.StatusBadRequest,
apiErr.NewErrorList(apiErr.NewError(apiErr.BLOB_UPLOAD_INVALID, map[string]string{"digest": digest.String()})))
details := map[string]string{"digest": digest.String()}
if err != nil {
details["conversion error"] = err.Error()
} else {
details["Content-Length"] = request.Header.Get("Content-Length")
}
e := apiErr.NewError(apiErr.BLOB_UPLOAD_INVALID).AddDetail(details)
zcommon.WriteJSON(response, http.StatusBadRequest, apiErr.NewErrorList(e))
return
}
@@ -1241,9 +1272,11 @@ func (rh *RouteHandler) CreateBlobUpload(response http.ResponseWriter, request *
upload, err := imgStore.NewBlobUpload(name)
if err != nil {
details := zerr.GetDetails(err)
if errors.Is(err, zerr.ErrRepoNotFound) {
zcommon.WriteJSON(response, http.StatusNotFound,
apiErr.NewErrorList(apiErr.NewError(apiErr.NAME_UNKNOWN, map[string]string{"name": name})))
details["name"] = name
e := apiErr.NewError(apiErr.NAME_UNKNOWN).AddDetail(details)
zcommon.WriteJSON(response, http.StatusNotFound, apiErr.NewErrorList(e))
} else {
rh.c.Log.Error().Err(err).Msg("unexpected error")
response.WriteHeader(http.StatusInternalServerError)
@@ -1291,18 +1324,20 @@ func (rh *RouteHandler) GetBlobUpload(response http.ResponseWriter, request *htt
size, err := imgStore.GetBlobUpload(name, sessionID)
if err != nil {
if errors.Is(err, zerr.ErrBadUploadRange) { //nolint:gocritic // errorslint conflicts with gocritic:IfElseChain
zcommon.WriteJSON(response, http.StatusBadRequest,
apiErr.NewErrorList(apiErr.NewError(apiErr.BLOB_UPLOAD_INVALID, map[string]string{"session_id": sessionID})))
} else if errors.Is(err, zerr.ErrBadBlobDigest) {
zcommon.WriteJSON(response, http.StatusBadRequest,
apiErr.NewErrorList(apiErr.NewError(apiErr.BLOB_UPLOAD_INVALID, map[string]string{"session_id": sessionID})))
details := zerr.GetDetails(err)
//nolint:gocritic // errorslint conflicts with gocritic:IfElseChain
if errors.Is(err, zerr.ErrBadUploadRange) || errors.Is(err, zerr.ErrBadBlobDigest) {
details["session_id"] = sessionID
e := apiErr.NewError(apiErr.BLOB_UPLOAD_INVALID).AddDetail(details)
zcommon.WriteJSON(response, http.StatusBadRequest, apiErr.NewErrorList(e))
} else if errors.Is(err, zerr.ErrRepoNotFound) {
zcommon.WriteJSON(response, http.StatusNotFound,
apiErr.NewErrorList(apiErr.NewError(apiErr.NAME_UNKNOWN, map[string]string{"name": name})))
details["name"] = name
e := apiErr.NewError(apiErr.NAME_UNKNOWN).AddDetail(details)
zcommon.WriteJSON(response, http.StatusNotFound, apiErr.NewErrorList(e))
} else if errors.Is(err, zerr.ErrUploadNotFound) {
zcommon.WriteJSON(response, http.StatusNotFound,
apiErr.NewErrorList(apiErr.NewError(apiErr.BLOB_UPLOAD_UNKNOWN, map[string]string{"session_id": sessionID})))
details["session_id"] = sessionID
e := apiErr.NewError(apiErr.BLOB_UPLOAD_UNKNOWN).AddDetail(details)
zcommon.WriteJSON(response, http.StatusNotFound, apiErr.NewErrorList(e))
} else {
rh.c.Log.Error().Err(err).Msg("unexpected error")
response.WriteHeader(http.StatusInternalServerError)
@@ -1380,16 +1415,20 @@ func (rh *RouteHandler) PatchBlobUpload(response http.ResponseWriter, request *h
clen, err = imgStore.PutBlobChunk(name, sessionID, from, to, request.Body)
}
if err != nil {
if err != nil { //nolint: dupl
details := zerr.GetDetails(err)
if errors.Is(err, zerr.ErrBadUploadRange) { //nolint:gocritic // errorslint conflicts with gocritic:IfElseChain
zcommon.WriteJSON(response, http.StatusRequestedRangeNotSatisfiable,
apiErr.NewErrorList(apiErr.NewError(apiErr.BLOB_UPLOAD_INVALID, map[string]string{"session_id": sessionID})))
details["session_id"] = sessionID
e := apiErr.NewError(apiErr.BLOB_UPLOAD_INVALID).AddDetail(details)
zcommon.WriteJSON(response, http.StatusRequestedRangeNotSatisfiable, apiErr.NewErrorList(e))
} else if errors.Is(err, zerr.ErrRepoNotFound) {
zcommon.WriteJSON(response, http.StatusNotFound,
apiErr.NewErrorList(apiErr.NewError(apiErr.NAME_UNKNOWN, map[string]string{"name": name})))
details["name"] = name
e := apiErr.NewError(apiErr.NAME_UNKNOWN).AddDetail(details)
zcommon.WriteJSON(response, http.StatusNotFound, apiErr.NewErrorList(e))
} else if errors.Is(err, zerr.ErrUploadNotFound) {
zcommon.WriteJSON(response, http.StatusNotFound,
apiErr.NewErrorList(apiErr.NewError(apiErr.BLOB_UPLOAD_UNKNOWN, map[string]string{"session_id": sessionID})))
details["session_id"] = sessionID
e := apiErr.NewError(apiErr.BLOB_UPLOAD_UNKNOWN).AddDetail(details)
zcommon.WriteJSON(response, http.StatusNotFound, apiErr.NewErrorList(e))
} else {
// could be io.ErrUnexpectedEOF, syscall.EMFILE (Err:0x18 too many opened files), etc
rh.c.Log.Error().Err(err).Msg("unexpected error: removing .uploads/ files")
@@ -1500,16 +1539,20 @@ func (rh *RouteHandler) UpdateBlobUpload(response http.ResponseWriter, request *
}
_, err = imgStore.PutBlobChunk(name, sessionID, from, to, request.Body)
if err != nil {
if err != nil { //nolint:dupl
details := zerr.GetDetails(err)
if errors.Is(err, zerr.ErrBadUploadRange) { //nolint:gocritic // errorslint conflicts with gocritic:IfElseChain
zcommon.WriteJSON(response, http.StatusBadRequest,
apiErr.NewErrorList(apiErr.NewError(apiErr.BLOB_UPLOAD_INVALID, map[string]string{"session_id": sessionID})))
details["session_id"] = sessionID
e := apiErr.NewError(apiErr.BLOB_UPLOAD_INVALID).AddDetail(details)
zcommon.WriteJSON(response, http.StatusBadRequest, apiErr.NewErrorList(e))
} else if errors.Is(err, zerr.ErrRepoNotFound) {
zcommon.WriteJSON(response, http.StatusNotFound,
apiErr.NewErrorList(apiErr.NewError(apiErr.NAME_UNKNOWN, map[string]string{"name": name})))
details["name"] = name
e := apiErr.NewError(apiErr.NAME_UNKNOWN).AddDetail(details)
zcommon.WriteJSON(response, http.StatusNotFound, apiErr.NewErrorList(e))
} else if errors.Is(err, zerr.ErrUploadNotFound) {
zcommon.WriteJSON(response, http.StatusNotFound,
apiErr.NewErrorList(apiErr.NewError(apiErr.BLOB_UPLOAD_UNKNOWN, map[string]string{"session_id": sessionID})))
details["session_id"] = sessionID
e := apiErr.NewError(apiErr.BLOB_UPLOAD_UNKNOWN).AddDetail(details)
zcommon.WriteJSON(response, http.StatusNotFound, apiErr.NewErrorList(e))
} else {
// could be io.ErrUnexpectedEOF, syscall.EMFILE (Err:0x18 too many opened files), etc
rh.c.Log.Error().Err(err).Msg("unexpected error: removing .uploads/ files")
@@ -1528,18 +1571,23 @@ func (rh *RouteHandler) UpdateBlobUpload(response http.ResponseWriter, request *
finish:
// blob chunks already transferred, just finish
if err := imgStore.FinishBlobUpload(name, sessionID, request.Body, digest); err != nil {
details := zerr.GetDetails(err)
if errors.Is(err, zerr.ErrBadBlobDigest) { //nolint:gocritic // errorslint conflicts with gocritic:IfElseChain
zcommon.WriteJSON(response, http.StatusBadRequest,
apiErr.NewErrorList(apiErr.NewError(apiErr.DIGEST_INVALID, map[string]string{"digest": digest.String()})))
details["digest"] = digest.String()
e := apiErr.NewError(apiErr.DIGEST_INVALID).AddDetail(details)
zcommon.WriteJSON(response, http.StatusBadRequest, apiErr.NewErrorList(e))
} else if errors.Is(err, zerr.ErrBadUploadRange) {
zcommon.WriteJSON(response, http.StatusBadRequest,
apiErr.NewErrorList(apiErr.NewError(apiErr.BLOB_UPLOAD_INVALID, map[string]string{"session_id": sessionID})))
details["session_id"] = sessionID
e := apiErr.NewError(apiErr.BLOB_UPLOAD_INVALID).AddDetail(details)
zcommon.WriteJSON(response, http.StatusBadRequest, apiErr.NewErrorList(e))
} else if errors.Is(err, zerr.ErrRepoNotFound) {
zcommon.WriteJSON(response, http.StatusNotFound,
apiErr.NewErrorList(apiErr.NewError(apiErr.NAME_UNKNOWN, map[string]string{"name": name})))
details["name"] = name
e := apiErr.NewError(apiErr.NAME_UNKNOWN).AddDetail(details)
zcommon.WriteJSON(response, http.StatusNotFound, apiErr.NewErrorList(e))
} else if errors.Is(err, zerr.ErrUploadNotFound) {
zcommon.WriteJSON(response, http.StatusNotFound,
apiErr.NewErrorList(apiErr.NewError(apiErr.BLOB_UPLOAD_UNKNOWN, map[string]string{"session_id": sessionID})))
details["session_id"] = sessionID
e := apiErr.NewError(apiErr.BLOB_UPLOAD_UNKNOWN).AddDetail(details)
zcommon.WriteJSON(response, http.StatusNotFound, apiErr.NewErrorList(e))
} else {
// could be io.ErrUnexpectedEOF, syscall.EMFILE (Err:0x18 too many opened files), etc
rh.c.Log.Error().Err(err).Msg("unexpected error: removing .uploads/ files")
@@ -1591,12 +1639,15 @@ func (rh *RouteHandler) DeleteBlobUpload(response http.ResponseWriter, request *
}
if err := imgStore.DeleteBlobUpload(name, sessionID); err != nil {
details := zerr.GetDetails(err)
if errors.Is(err, zerr.ErrRepoNotFound) { //nolint:gocritic // errorslint conflicts with gocritic:IfElseChain
zcommon.WriteJSON(response, http.StatusNotFound,
apiErr.NewErrorList(apiErr.NewError(apiErr.NAME_UNKNOWN, map[string]string{"name": name})))
details["name"] = name
e := apiErr.NewError(apiErr.NAME_UNKNOWN).AddDetail(details)
zcommon.WriteJSON(response, http.StatusNotFound, apiErr.NewErrorList(e))
} else if errors.Is(err, zerr.ErrUploadNotFound) {
zcommon.WriteJSON(response, http.StatusNotFound,
apiErr.NewErrorList(apiErr.NewError(apiErr.BLOB_UPLOAD_UNKNOWN, map[string]string{"session_id": sessionID})))
details["session_id"] = sessionID
e := apiErr.NewError(apiErr.BLOB_UPLOAD_UNKNOWN).AddDetail(details)
zcommon.WriteJSON(response, http.StatusNotFound, apiErr.NewErrorList(e))
} else {
rh.c.Log.Error().Err(err).Msg("unexpected error")
response.WriteHeader(http.StatusInternalServerError)