Unify the method/style to handle error in handler/middleware

This commit provides a "SendError" method to unify the way to handle error in handlers/middlewares

Signed-off-by: Wenkai Yin <yinw@vmware.com>
This commit is contained in:
Wenkai Yin 2020-01-26 10:36:25 +08:00
parent 8a74fcb074
commit af4dd142bc
19 changed files with 143 additions and 171 deletions

View File

@ -317,7 +317,7 @@ responses:
description: The ID of the corresponding request for the response description: The ID of the corresponding request for the response
type: string type: string
schema: schema:
$ref: '#/definitions/Error' $ref: '#/definitions/Errors'
'401': '401':
description: Unauthorized description: Unauthorized
headers: headers:
@ -325,7 +325,7 @@ responses:
description: The ID of the corresponding request for the response description: The ID of the corresponding request for the response
type: string type: string
schema: schema:
$ref: '#/definitions/Error' $ref: '#/definitions/Errors'
'403': '403':
description: Forbidden description: Forbidden
headers: headers:
@ -333,7 +333,7 @@ responses:
description: The ID of the corresponding request for the response description: The ID of the corresponding request for the response
type: string type: string
schema: schema:
$ref: '#/definitions/Error' $ref: '#/definitions/Errors'
'404': '404':
description: Not found description: Not found
headers: headers:
@ -341,7 +341,7 @@ responses:
description: The ID of the corresponding request for the response description: The ID of the corresponding request for the response
type: string type: string
schema: schema:
$ref: '#/definitions/Error' $ref: '#/definitions/Errors'
'409': '409':
description: Conflict description: Conflict
headers: headers:
@ -349,7 +349,7 @@ responses:
description: The ID of the corresponding request for the response description: The ID of the corresponding request for the response
type: string type: string
schema: schema:
$ref: '#/definitions/Error' $ref: '#/definitions/Errors'
'500': '500':
description: Internal server error description: Internal server error
headers: headers:
@ -357,8 +357,13 @@ responses:
description: The ID of the corresponding request for the response description: The ID of the corresponding request for the response
type: string type: string
schema: schema:
$ref: '#/definitions/Error' $ref: '#/definitions/Errors'
definitions: definitions:
Errors:
description: The error array that describe the errors got during the handling of request
type: array
items:
$ref: '#/definitions/Error'
Error: Error:
description: a model for all the error response coming from harbor description: a model for all the error response coming from harbor
type: object type: object
@ -369,11 +374,6 @@ definitions:
message: message:
type: string type: string
description: The error message description: The error message
x-go-type:
import:
package: "github.com/goharbor/harbor/src/internal/error"
alias: "ierrors"
type: "Error"
Artifact: Artifact:
type: object type: object
properties: properties:

View File

@ -262,6 +262,5 @@ func (b *BaseAPI) SendStatusServiceUnavailableError(err error) {
// ] // ]
// } // }
func (b *BaseAPI) SendError(err error) { func (b *BaseAPI) SendError(err error) {
statusCode, payload := serror.APIError(err) serror.SendError(b.Ctx.ResponseWriter, err)
b.RenderError(statusCode, payload)
} }

View File

@ -16,11 +16,14 @@ package error
import ( import (
"errors" "errors"
"net/http" "fmt"
openapi "github.com/go-openapi/errors"
"github.com/go-openapi/runtime" "github.com/go-openapi/runtime"
"github.com/go-openapi/runtime/middleware" "github.com/go-openapi/runtime/middleware"
"github.com/goharbor/harbor/src/common/utils/log"
ierror "github.com/goharbor/harbor/src/internal/error" ierror "github.com/goharbor/harbor/src/internal/error"
"net/http"
"strings"
) )
var ( var (
@ -28,25 +31,57 @@ var (
ierror.BadRequestCode: http.StatusBadRequest, ierror.BadRequestCode: http.StatusBadRequest,
ierror.UnAuthorizedCode: http.StatusUnauthorized, ierror.UnAuthorizedCode: http.StatusUnauthorized,
ierror.ForbiddenCode: http.StatusForbidden, ierror.ForbiddenCode: http.StatusForbidden,
ierror.DENIED: http.StatusForbidden,
ierror.NotFoundCode: http.StatusNotFound, ierror.NotFoundCode: http.StatusNotFound,
ierror.ConflictCode: http.StatusConflict, ierror.ConflictCode: http.StatusConflict,
ierror.PreconditionCode: http.StatusPreconditionFailed, ierror.PreconditionCode: http.StatusPreconditionFailed,
ierror.ViolateForeignKeyConstraintCode: http.StatusPreconditionFailed, ierror.ViolateForeignKeyConstraintCode: http.StatusPreconditionFailed,
ierror.PROJECTPOLICYVIOLATION: http.StatusPreconditionFailed,
ierror.GeneralCode: http.StatusInternalServerError, ierror.GeneralCode: http.StatusInternalServerError,
} }
) )
// APIError generates the HTTP status code and error payload based on the input err // TODO use "SendError" instead in the v1 APIs?
func APIError(err error) (int, string) {
return getHTTPStatusCode(ierror.ErrCode(err)), ierror.NewErrs(err).Error() // SendError tries to parse the HTTP status code from the specified error, envelops it into
// an error array as the error payload and returns the code and payload to the response.
// And the error is logged as well
func SendError(w http.ResponseWriter, err error) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
statusCode, errPayload := apiError(err)
// the error detail is logged only, and will not be sent to the client to avoid leaking server information
if statusCode >= http.StatusInternalServerError {
log.Error(errPayload)
err = ierror.New(nil).WithCode(ierror.GeneralCode).WithMessage("internal server error")
errPayload = ierror.NewErrs(err).Error()
} else {
// only log the error whose status code < 500 when debugging to avoid log flooding
log.Debug(errPayload)
}
w.WriteHeader(statusCode)
fmt.Fprintln(w, errPayload)
} }
func getHTTPStatusCode(errCode string) int { // generates the HTTP status code based on the specified error,
statusCode, ok := codeMap[errCode] // envelops the error into an error array as the payload and return them
if !ok { func apiError(err error) (statusCode int, errPayload string) {
statusCode = http.StatusInternalServerError code := 0
var openAPIErr openapi.Error
if errors.As(err, &openAPIErr) {
// Before executing operation handler, go-swagger will bind a parameters object to a request and validate the request,
// it will return directly when bind and validate failed.
// The response format of the default ServeError implementation does not match the internal error response format.
// So we needed to convert the format to the internal error response format.
code = int(openAPIErr.Code())
errCode := strings.Replace(strings.ToUpper(http.StatusText(code)), " ", "_", -1)
err = ierror.New(nil).WithCode(errCode).WithMessage(openAPIErr.Error())
} else {
code = codeMap[ierror.ErrCode(err)]
} }
return statusCode if code == 0 {
code = http.StatusInternalServerError
}
return code, ierror.NewErrs(err).Error()
} }
var _ middleware.Responder = &ErrResponder{} var _ middleware.Responder = &ErrResponder{}
@ -58,20 +93,7 @@ type ErrResponder struct {
// WriteResponse ... // WriteResponse ...
func (r *ErrResponder) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { func (r *ErrResponder) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) {
code := ierror.ErrCode(r.err) SendError(rw, r.err)
rw.WriteHeader(getHTTPStatusCode(code))
var e *ierror.Error
if !errors.As(r.err, &e) {
e = &ierror.Error{
Code: code,
Message: r.err.Error(),
}
}
if err := producer.Produce(rw, e); err != nil {
panic(err) // let the recovery middleware deal with this
}
} }
// NewErrResponder returns responder for err // NewErrResponder returns responder for err

View File

@ -16,38 +16,51 @@ package error
import ( import (
"errors" "errors"
openapi "github.com/go-openapi/errors"
ierror "github.com/goharbor/harbor/src/internal/error" ierror "github.com/goharbor/harbor/src/internal/error"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"net/http" "net/http"
"net/http/httptest"
"testing" "testing"
) )
func TestGetHTTPStatusCode(t *testing.T) { func TestSendError(t *testing.T) {
// pre-defined error code // internal server error
errCode := ierror.NotFoundCode rw := httptest.NewRecorder()
statusCode := getHTTPStatusCode(errCode) err := ierror.New(nil).WithCode(ierror.GeneralCode).WithMessage("unknown")
assert.Equal(t, http.StatusNotFound, statusCode) SendError(rw, err)
assert.Equal(t, http.StatusInternalServerError, rw.Code)
assert.Equal(t, `{"errors":[{"code":"UNKNOWN","message":"internal server error"}]}`+"\n", rw.Body.String())
// not-defined error code // not internal server error
errCode = "NOT_DEFINED_ERROR_CODE" rw = httptest.NewRecorder()
statusCode = getHTTPStatusCode(errCode) err = ierror.New(nil).WithCode(ierror.NotFoundCode).WithMessage("object not found")
assert.Equal(t, http.StatusInternalServerError, statusCode) SendError(rw, err)
assert.Equal(t, http.StatusNotFound, rw.Code)
assert.Equal(t, `{"errors":[{"code":"NOT_FOUND","message":"object not found"}]}`+"\n", rw.Body.String())
} }
func TestAPIError(t *testing.T) { func TestAPIError(t *testing.T) {
var err error
// open API error: github.com/go-openapi/errors.Error
err = openapi.New(400, "bad request")
statusCode, payload := apiError(err)
assert.Equal(t, http.StatusBadRequest, statusCode)
assert.Equal(t, `{"errors":[{"code":"BAD_REQUEST","message":"bad request"}]}`, payload)
// ierror.Error // ierror.Error
err := &ierror.Error{ err = &ierror.Error{
Cause: nil, Cause: nil,
Code: ierror.NotFoundCode, Code: ierror.NotFoundCode,
Message: "resource not found", Message: "resource not found",
} }
statusCode, payload := APIError(err) statusCode, payload = apiError(err)
assert.Equal(t, http.StatusNotFound, statusCode) assert.Equal(t, http.StatusNotFound, statusCode)
assert.Equal(t, `{"errors":[{"code":"NOT_FOUND","message":"resource not found"}]}`, payload) assert.Equal(t, `{"errors":[{"code":"NOT_FOUND","message":"resource not found"}]}`, payload)
// common error // common error
e := errors.New("customized error") e := errors.New("customized error")
statusCode, payload = APIError(e) statusCode, payload = apiError(e)
assert.Equal(t, http.StatusInternalServerError, statusCode) assert.Equal(t, http.StatusInternalServerError, statusCode)
assert.Equal(t, `{"errors":[{"code":"UNKNOWN","message":"customized error"}]}`, payload) assert.Equal(t, `{"errors":[{"code":"UNKNOWN","message":"customized error"}]}`, payload)
} }

View File

@ -17,16 +17,15 @@ package artifactinfo
import ( import (
"context" "context"
"fmt" "fmt"
"github.com/goharbor/harbor/src/common/utils/log"
ierror "github.com/goharbor/harbor/src/internal/error"
serror "github.com/goharbor/harbor/src/server/error"
"github.com/goharbor/harbor/src/server/middleware"
"github.com/opencontainers/go-digest"
"net/http" "net/http"
"net/url" "net/url"
"regexp" "regexp"
"strings" "strings"
"github.com/goharbor/harbor/src/common/utils/log"
ierror "github.com/goharbor/harbor/src/internal/error"
"github.com/goharbor/harbor/src/server/middleware"
reg_err "github.com/goharbor/harbor/src/server/registry/error"
"github.com/opencontainers/go-digest"
) )
const ( const (
@ -58,7 +57,7 @@ func Middleware() func(http.Handler) http.Handler {
repo := m[middleware.RepositorySubexp] repo := m[middleware.RepositorySubexp]
pn, err := projectNameFromRepo(repo) pn, err := projectNameFromRepo(repo)
if err != nil { if err != nil {
reg_err.Handle(rw, req, ierror.BadRequestError(err)) serror.SendError(rw, ierror.BadRequestError(err))
return return
} }
art := &middleware.ArtifactInfo{ art := &middleware.ArtifactInfo{
@ -77,7 +76,7 @@ func Middleware() func(http.Handler) http.Handler {
// it's not clear in OCI spec how to handle invalid from parm // it's not clear in OCI spec how to handle invalid from parm
bmp, err := projectNameFromRepo(bmr) bmp, err := projectNameFromRepo(bmr)
if err != nil { if err != nil {
reg_err.Handle(rw, req, ierror.BadRequestError(err)) serror.SendError(rw, ierror.BadRequestError(err))
return return
} }
art.BlobMountDigest = m[blobMountDigest] art.BlobMountDigest = m[blobMountDigest]

View File

@ -6,6 +6,7 @@ import (
"github.com/goharbor/harbor/src/core/config" "github.com/goharbor/harbor/src/core/config"
"github.com/goharbor/harbor/src/core/middlewares/util" "github.com/goharbor/harbor/src/core/middlewares/util"
internal_errors "github.com/goharbor/harbor/src/internal/error" internal_errors "github.com/goharbor/harbor/src/internal/error"
serror "github.com/goharbor/harbor/src/server/error"
"github.com/goharbor/harbor/src/server/middleware" "github.com/goharbor/harbor/src/server/middleware"
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
@ -28,16 +29,12 @@ func Middleware() func(http.Handler) http.Handler {
if rec.Result().StatusCode == http.StatusOK { if rec.Result().StatusCode == http.StatusOK {
match, err := matchNotaryDigest(mf) match, err := matchNotaryDigest(mf)
if err != nil { if err != nil {
pkgE := internal_errors.New(nil).WithCode(internal_errors.PROJECTPOLICYVIOLATION).WithMessage("Failed in communication with Notary please check the log") serror.SendError(rw, err)
msg := internal_errors.NewErrs(pkgE).Error()
http.Error(rw, msg, http.StatusInternalServerError)
return return
} }
if !match { if !match {
log.Debugf("digest mismatch, failing the response.")
pkgE := internal_errors.New(nil).WithCode(internal_errors.PROJECTPOLICYVIOLATION).WithMessage("The image is not signed in Notary.") pkgE := internal_errors.New(nil).WithCode(internal_errors.PROJECTPOLICYVIOLATION).WithMessage("The image is not signed in Notary.")
msg := internal_errors.NewErrs(pkgE).Error() serror.SendError(rw, pkgE)
http.Error(rw, msg, http.StatusPreconditionFailed)
return return
} }
} }

View File

@ -6,6 +6,7 @@ import (
"github.com/goharbor/harbor/src/api/artifact" "github.com/goharbor/harbor/src/api/artifact"
common_util "github.com/goharbor/harbor/src/common/utils" common_util "github.com/goharbor/harbor/src/common/utils"
internal_errors "github.com/goharbor/harbor/src/internal/error" internal_errors "github.com/goharbor/harbor/src/internal/error"
serror "github.com/goharbor/harbor/src/server/error"
"github.com/goharbor/harbor/src/server/middleware" "github.com/goharbor/harbor/src/server/middleware"
"net/http" "net/http"
) )
@ -18,13 +19,12 @@ func MiddlewareDelete() func(http.Handler) http.Handler {
var e *ErrImmutable var e *ErrImmutable
if errors.As(err, &e) { if errors.As(err, &e) {
pkgE := internal_errors.New(e).WithCode(internal_errors.PreconditionCode) pkgE := internal_errors.New(e).WithCode(internal_errors.PreconditionCode)
msg := internal_errors.NewErrs(pkgE).Error() serror.SendError(rw, pkgE)
http.Error(rw, msg, http.StatusPreconditionFailed)
return return
} }
pkgE := internal_errors.New(fmt.Errorf("error occurred when to handle request in immutable handler: %v", err)).WithCode(internal_errors.GeneralCode) pkgE := internal_errors.New(fmt.Errorf("error occurred when to handle request in immutable handler: %v", err)).WithCode(internal_errors.GeneralCode)
msg := internal_errors.NewErrs(pkgE).Error() serror.SendError(rw, pkgE)
http.Error(rw, msg, http.StatusInternalServerError) return
} }
next.ServeHTTP(rw, req) next.ServeHTTP(rw, req)
}) })

View File

@ -6,6 +6,7 @@ import (
"github.com/goharbor/harbor/src/api/artifact" "github.com/goharbor/harbor/src/api/artifact"
common_util "github.com/goharbor/harbor/src/common/utils" common_util "github.com/goharbor/harbor/src/common/utils"
internal_errors "github.com/goharbor/harbor/src/internal/error" internal_errors "github.com/goharbor/harbor/src/internal/error"
serror "github.com/goharbor/harbor/src/server/error"
"github.com/goharbor/harbor/src/server/middleware" "github.com/goharbor/harbor/src/server/middleware"
"net/http" "net/http"
) )
@ -18,13 +19,12 @@ func MiddlewarePush() func(http.Handler) http.Handler {
var e *ErrImmutable var e *ErrImmutable
if errors.As(err, &e) { if errors.As(err, &e) {
pkgE := internal_errors.New(e).WithCode(internal_errors.PreconditionCode) pkgE := internal_errors.New(e).WithCode(internal_errors.PreconditionCode)
msg := internal_errors.NewErrs(pkgE).Error() serror.SendError(rw, pkgE)
http.Error(rw, msg, http.StatusPreconditionFailed)
return return
} }
pkgE := internal_errors.New(fmt.Errorf("error occurred when to handle request in immutable handler: %v", err)).WithCode(internal_errors.GeneralCode) pkgE := internal_errors.New(fmt.Errorf("error occurred when to handle request in immutable handler: %v", err)).WithCode(internal_errors.GeneralCode)
msg := internal_errors.NewErrs(pkgE).Error() serror.SendError(rw, pkgE)
http.Error(rw, msg, http.StatusInternalServerError) return
} }
next.ServeHTTP(rw, req) next.ServeHTTP(rw, req)
}) })

View File

@ -5,8 +5,8 @@ import (
"github.com/goharbor/harbor/src/common/utils" "github.com/goharbor/harbor/src/common/utils"
ierror "github.com/goharbor/harbor/src/internal/error" ierror "github.com/goharbor/harbor/src/internal/error"
project2 "github.com/goharbor/harbor/src/pkg/project" project2 "github.com/goharbor/harbor/src/pkg/project"
serror "github.com/goharbor/harbor/src/server/error"
"github.com/goharbor/harbor/src/server/middleware" "github.com/goharbor/harbor/src/server/middleware"
reg_err "github.com/goharbor/harbor/src/server/registry/error"
"github.com/opencontainers/go-digest" "github.com/opencontainers/go-digest"
"net/http" "net/http"
"regexp" "regexp"
@ -23,7 +23,7 @@ func Middleware() func(http.Handler) http.Handler {
return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
mf, err := parseManifestInfoFromPath(req) mf, err := parseManifestInfoFromPath(req)
if err != nil { if err != nil {
reg_err.Handle(rw, req, err) serror.SendError(rw, err)
return return
} }
*req = *(req.WithContext(middleware.NewManifestInfoContext(req.Context(), mf))) *req = *(req.WithContext(middleware.NewManifestInfoContext(req.Context(), mf)))

View File

@ -15,9 +15,9 @@
package readonly package readonly
import ( import (
serror "github.com/goharbor/harbor/src/server/error"
"net/http" "net/http"
"github.com/goharbor/harbor/src/common/utils/log"
"github.com/goharbor/harbor/src/core/config" "github.com/goharbor/harbor/src/core/config"
internal_errors "github.com/goharbor/harbor/src/internal/error" internal_errors "github.com/goharbor/harbor/src/internal/error"
"github.com/goharbor/harbor/src/server/middleware" "github.com/goharbor/harbor/src/server/middleware"
@ -67,9 +67,8 @@ func MiddlewareWithConfig(config Config, skippers ...middleware.Skipper) func(ht
return middleware.New(func(w http.ResponseWriter, r *http.Request, next http.Handler) { return middleware.New(func(w http.ResponseWriter, r *http.Request, next http.Handler) {
if config.ReadOnly(r) { if config.ReadOnly(r) {
log.Warningf("The request is prohibited in readonly mode, url is: %s", r.URL.Path)
pkgE := internal_errors.New(nil).WithCode(internal_errors.DENIED).WithMessage("The system is in read only mode. Any modification is prohibited.") pkgE := internal_errors.New(nil).WithCode(internal_errors.DENIED).WithMessage("The system is in read only mode. Any modification is prohibited.")
http.Error(w, internal_errors.NewErrs(pkgE).Error(), http.StatusForbidden) serror.SendError(w, pkgE)
return return
} }

View File

@ -7,8 +7,8 @@ import (
"github.com/goharbor/harbor/src/common/utils/log" "github.com/goharbor/harbor/src/common/utils/log"
pkg_token "github.com/goharbor/harbor/src/pkg/token" pkg_token "github.com/goharbor/harbor/src/pkg/token"
"github.com/goharbor/harbor/src/pkg/token/claims/registry" "github.com/goharbor/harbor/src/pkg/token/claims/registry"
serror "github.com/goharbor/harbor/src/server/error"
"github.com/goharbor/harbor/src/server/middleware" "github.com/goharbor/harbor/src/server/middleware"
reg_err "github.com/goharbor/harbor/src/server/registry/error"
"net/http" "net/http"
"strings" "strings"
) )
@ -19,7 +19,7 @@ func Middleware() func(http.Handler) http.Handler {
return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
err := parseToken(req) err := parseToken(req)
if err != nil { if err != nil {
reg_err.Handle(rw, req, err) serror.SendError(rw, err)
return return
} }
next.ServeHTTP(rw, req) next.ServeHTTP(rw, req)

View File

@ -18,6 +18,7 @@ import (
"context" "context"
"errors" "errors"
"fmt" "fmt"
serror "github.com/goharbor/harbor/src/server/error"
"net/http" "net/http"
"github.com/goharbor/harbor/src/common/utils/log" "github.com/goharbor/harbor/src/common/utils/log"
@ -77,7 +78,7 @@ func Middleware(skippers ...middleware.Skipper) func(http.Handler) http.Handler
} }
if err := orm.WithTransaction(h)(r.Context()); err != nil && err != errNonSuccess { if err := orm.WithTransaction(h)(r.Context()); err != nil && err != errNonSuccess {
log.Errorf("deal with %s request in transaction failed: %v", r.URL.Path, err) err = fmt.Errorf("deal with %s request in transaction failed: %v", r.URL.Path, err)
// begin, commit or rollback transaction db error happened, // begin, commit or rollback transaction db error happened,
// reset the response and set status code to 500 // reset the response and set status code to 500
@ -85,7 +86,7 @@ func Middleware(skippers ...middleware.Skipper) func(http.Handler) http.Handler
log.Errorf("reset the response failed: %v", err) log.Errorf("reset the response failed: %v", err)
return return
} }
res.WriteHeader(http.StatusInternalServerError) serror.SendError(res, err)
} }
}, skippers...) }, skippers...)
} }

View File

@ -17,6 +17,7 @@ package v2auth
import ( import (
"context" "context"
"fmt" "fmt"
serror "github.com/goharbor/harbor/src/server/error"
"net/http" "net/http"
"sync" "sync"
@ -27,7 +28,6 @@ import (
"github.com/goharbor/harbor/src/core/promgr" "github.com/goharbor/harbor/src/core/promgr"
ierror "github.com/goharbor/harbor/src/internal/error" ierror "github.com/goharbor/harbor/src/internal/error"
"github.com/goharbor/harbor/src/server/middleware" "github.com/goharbor/harbor/src/server/middleware"
reg_err "github.com/goharbor/harbor/src/server/registry/error"
) )
type reqChecker struct { type reqChecker struct {
@ -131,7 +131,7 @@ func Middleware() func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler { return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
if err := checker.check(req); err != nil { if err := checker.check(req); err != nil {
reg_err.Handle(rw, req, ierror.UnauthorizedError(err).WithMessage(err.Error())) serror.SendError(rw, ierror.UnauthorizedError(err).WithMessage(err.Error()))
return return
} }
next.ServeHTTP(rw, req) next.ServeHTTP(rw, req)

View File

@ -8,6 +8,7 @@ import (
"github.com/goharbor/harbor/src/pkg/scan/report" "github.com/goharbor/harbor/src/pkg/scan/report"
v1 "github.com/goharbor/harbor/src/pkg/scan/rest/v1" v1 "github.com/goharbor/harbor/src/pkg/scan/rest/v1"
"github.com/goharbor/harbor/src/pkg/scan/vuln" "github.com/goharbor/harbor/src/pkg/scan/vuln"
serror "github.com/goharbor/harbor/src/server/error"
"github.com/goharbor/harbor/src/server/middleware" "github.com/goharbor/harbor/src/server/middleware"
"github.com/pkg/errors" "github.com/pkg/errors"
"net/http" "net/http"
@ -30,7 +31,8 @@ func Middleware() func(http.Handler) http.Handler {
// Invalid project ID // Invalid project ID
if wl.ProjectID == 0 { if wl.ProjectID == 0 {
err := errors.Errorf("project verification error: project %s", img.ProjectName) err := errors.Errorf("project verification error: project %s", img.ProjectName)
sendError(err, rw) pkgE := internal_errors.New(err).WithCode(internal_errors.PROJECTPOLICYVIOLATION)
serror.SendError(rw, pkgE)
return return
} }
@ -52,7 +54,8 @@ func Middleware() func(http.Handler) http.Handler {
if err != nil { if err != nil {
err = errors.Wrap(err, "middleware: vulnerable handler") err = errors.Wrap(err, "middleware: vulnerable handler")
sendError(err, rw) pkgE := internal_errors.New(err).WithCode(internal_errors.PROJECTPOLICYVIOLATION)
serror.SendError(rw, pkgE)
return return
} }
@ -60,7 +63,8 @@ func Middleware() func(http.Handler) http.Handler {
// No report yet? // No report yet?
if !ok { if !ok {
err = errors.Errorf("no scan report existing for the artifact: %s:%s@%s", img.Repository, img.Tag, img.Digest) err = errors.Errorf("no scan report existing for the artifact: %s:%s@%s", img.Repository, img.Tag, img.Digest)
sendError(err, rw) pkgE := internal_errors.New(err).WithCode(internal_errors.PROJECTPOLICYVIOLATION)
serror.SendError(rw, pkgE)
return return
} }
@ -70,7 +74,8 @@ func Middleware() func(http.Handler) http.Handler {
if summary.Severity.Code() >= projectVulnerableSeverity.Code() { if summary.Severity.Code() >= projectVulnerableSeverity.Code() {
err = errors.Errorf("current image with '%q vulnerable' cannot be pulled due to configured policy in 'Prevent images with vulnerability severity of %q from running.' "+ err = errors.Errorf("current image with '%q vulnerable' cannot be pulled due to configured policy in 'Prevent images with vulnerability severity of %q from running.' "+
"Please contact your project administrator for help'", summary.Severity, projectVulnerableSeverity) "Please contact your project administrator for help'", summary.Severity, projectVulnerableSeverity)
sendError(err, rw) pkgE := internal_errors.New(err).WithCode(internal_errors.PROJECTPOLICYVIOLATION)
serror.SendError(rw, pkgE)
return return
} }
@ -110,10 +115,3 @@ func validate(req *http.Request) (bool, *middleware.ManifestInfo, vuln.Severity,
} }
return true, mf, projectVulnerableSeverity, wl return true, mf, projectVulnerableSeverity, wl
} }
func sendError(err error, rw http.ResponseWriter) {
log.Error(err)
pkgE := internal_errors.New(err).WithCode(internal_errors.PROJECTPOLICYVIOLATION)
msg := internal_errors.NewErrs(pkgE).Error()
http.Error(rw, msg, http.StatusPreconditionFailed)
}

View File

@ -19,7 +19,7 @@ import (
"fmt" "fmt"
"github.com/goharbor/harbor/src/api/repository" "github.com/goharbor/harbor/src/api/repository"
ierror "github.com/goharbor/harbor/src/internal/error" ierror "github.com/goharbor/harbor/src/internal/error"
reg_error "github.com/goharbor/harbor/src/server/registry/error" serror "github.com/goharbor/harbor/src/server/error"
"github.com/goharbor/harbor/src/server/registry/util" "github.com/goharbor/harbor/src/server/registry/util"
"net/http" "net/http"
"sort" "sort"
@ -47,7 +47,7 @@ func (r *repositoryHandler) ServeHTTP(w http.ResponseWriter, req *http.Request)
maxEntries, err = strconv.Atoi(reqQ.Get("n")) maxEntries, err = strconv.Atoi(reqQ.Get("n"))
if err != nil || maxEntries < 0 { if err != nil || maxEntries < 0 {
err := ierror.New(err).WithCode(ierror.BadRequestCode).WithMessage("the N must be a positive int type") err := ierror.New(err).WithCode(ierror.BadRequestCode).WithMessage("the N must be a positive int type")
reg_error.Handle(w, req, err) serror.SendError(w, err)
return return
} }
} }
@ -57,7 +57,7 @@ func (r *repositoryHandler) ServeHTTP(w http.ResponseWriter, req *http.Request)
// ToDo filter out the untagged repos // ToDo filter out the untagged repos
total, repoRecords, err := r.repoCtl.List(req.Context(), nil) total, repoRecords, err := r.repoCtl.List(req.Context(), nil)
if err != nil { if err != nil {
reg_error.Handle(w, req, err) serror.SendError(w, err)
return return
} }
if total <= 0 { if total <= 0 {
@ -81,7 +81,7 @@ func (r *repositoryHandler) ServeHTTP(w http.ResponseWriter, req *http.Request)
lastEntryIndex := util.IndexString(repoNames, lastEntry) lastEntryIndex := util.IndexString(repoNames, lastEntry)
if lastEntryIndex == -1 { if lastEntryIndex == -1 {
err := ierror.New(nil).WithCode(ierror.BadRequestCode).WithMessage(fmt.Sprintf("the last: %s should be a valid repository name.", lastEntry)) err := ierror.New(nil).WithCode(ierror.BadRequestCode).WithMessage(fmt.Sprintf("the last: %s should be a valid repository name.", lastEntry))
reg_error.Handle(w, req, err) serror.SendError(w, err)
return return
} }
resRepos = repoNames[lastEntryIndex+1 : lastEntryIndex+maxEntries] resRepos = repoNames[lastEntryIndex+1 : lastEntryIndex+maxEntries]
@ -94,7 +94,7 @@ func (r *repositoryHandler) ServeHTTP(w http.ResponseWriter, req *http.Request)
if repoNames[len(repoNames)-1] != resRepos[len(resRepos)-1] { if repoNames[len(repoNames)-1] != resRepos[len(resRepos)-1] {
urlStr, err := util.SetLinkHeader(req.URL.String(), maxEntries, resRepos[len(resRepos)-1]) urlStr, err := util.SetLinkHeader(req.URL.String(), maxEntries, resRepos[len(resRepos)-1])
if err != nil { if err != nil {
reg_error.Handle(w, req, err) serror.SendError(w, err)
return return
} }
w.Header().Set("Link", urlStr) w.Header().Set("Link", urlStr)
@ -111,7 +111,7 @@ func (r *repositoryHandler) sendResponse(w http.ResponseWriter, req *http.Reques
if err := enc.Encode(catalogAPIResponse{ if err := enc.Encode(catalogAPIResponse{
Repositories: repositoryNames, Repositories: repositoryNames,
}); err != nil { }); err != nil {
reg_error.Handle(w, req, err) serror.SendError(w, err)
return return
} }
} }

View File

@ -1,29 +0,0 @@
// Copyright Project Harbor Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package error
import (
"github.com/goharbor/harbor/src/common/utils/log"
serror "github.com/goharbor/harbor/src/server/error"
"net/http"
)
// Handle generates the HTTP status code and error payload and writes them to the response
func Handle(w http.ResponseWriter, req *http.Request, err error) {
log.Errorf("failed to handle the request %s %s: %v", req.Method, req.URL, err)
statusCode, payload := serror.APIError(err)
w.WriteHeader(statusCode)
w.Write([]byte(payload))
}

View File

@ -19,7 +19,7 @@ import (
"github.com/goharbor/harbor/src/api/repository" "github.com/goharbor/harbor/src/api/repository"
"github.com/goharbor/harbor/src/common/utils/log" "github.com/goharbor/harbor/src/common/utils/log"
"github.com/goharbor/harbor/src/internal" "github.com/goharbor/harbor/src/internal"
"github.com/goharbor/harbor/src/server/registry/error" serror "github.com/goharbor/harbor/src/server/error"
"github.com/goharbor/harbor/src/server/router" "github.com/goharbor/harbor/src/server/router"
"github.com/opencontainers/go-digest" "github.com/opencontainers/go-digest"
"net/http" "net/http"
@ -32,7 +32,7 @@ func getManifest(w http.ResponseWriter, req *http.Request) {
reference := router.Param(req.Context(), ":reference") reference := router.Param(req.Context(), ":reference")
artifact, err := artifact.Ctl.GetByReference(req.Context(), repository, reference, nil) artifact, err := artifact.Ctl.GetByReference(req.Context(), repository, reference, nil)
if err != nil { if err != nil {
error.Handle(w, req, err) serror.SendError(w, err)
return return
} }
@ -53,11 +53,11 @@ func deleteManifest(w http.ResponseWriter, req *http.Request) {
reference := router.Param(req.Context(), ":reference") reference := router.Param(req.Context(), ":reference")
art, err := artifact.Ctl.GetByReference(req.Context(), repository, reference, nil) art, err := artifact.Ctl.GetByReference(req.Context(), repository, reference, nil)
if err != nil { if err != nil {
error.Handle(w, req, err) serror.SendError(w, err)
return return
} }
if err = artifact.Ctl.Delete(req.Context(), art.ID); err != nil { if err = artifact.Ctl.Delete(req.Context(), art.ID); err != nil {
error.Handle(w, req, err) serror.SendError(w, err)
return return
} }
w.WriteHeader(http.StatusAccepted) w.WriteHeader(http.StatusAccepted)
@ -72,7 +72,7 @@ func putManifest(w http.ResponseWriter, req *http.Request) {
// make sure the repository exist before pushing the manifest // make sure the repository exist before pushing the manifest
_, repositoryID, err := repository.Ctl.Ensure(req.Context(), repo) _, repositoryID, err := repository.Ctl.Ensure(req.Context(), repo)
if err != nil { if err != nil {
error.Handle(w, req, err) serror.SendError(w, err)
return return
} }
@ -100,7 +100,7 @@ func putManifest(w http.ResponseWriter, req *http.Request) {
_, _, err = artifact.Ctl.Ensure(req.Context(), repositoryID, dgt, tags...) _, _, err = artifact.Ctl.Ensure(req.Context(), repositoryID, dgt, tags...)
if err != nil { if err != nil {
error.Handle(w, req, err) serror.SendError(w, err)
return return
} }

View File

@ -21,7 +21,7 @@ import (
"github.com/goharbor/harbor/src/api/repository" "github.com/goharbor/harbor/src/api/repository"
ierror "github.com/goharbor/harbor/src/internal/error" ierror "github.com/goharbor/harbor/src/internal/error"
"github.com/goharbor/harbor/src/pkg/q" "github.com/goharbor/harbor/src/pkg/q"
reg_error "github.com/goharbor/harbor/src/server/registry/error" serror "github.com/goharbor/harbor/src/server/error"
"github.com/goharbor/harbor/src/server/registry/util" "github.com/goharbor/harbor/src/server/registry/util"
"github.com/goharbor/harbor/src/server/router" "github.com/goharbor/harbor/src/server/router"
"net/http" "net/http"
@ -65,7 +65,7 @@ func (t *tagHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
maxEntries, err = strconv.Atoi(reqQ.Get("n")) maxEntries, err = strconv.Atoi(reqQ.Get("n"))
if err != nil || maxEntries < 0 { if err != nil || maxEntries < 0 {
err := ierror.New(err).WithCode(ierror.BadRequestCode).WithMessage("the N must be a positive int type") err := ierror.New(err).WithCode(ierror.BadRequestCode).WithMessage("the N must be a positive int type")
reg_error.Handle(w, req, err) serror.SendError(w, err)
return return
} }
} }
@ -75,7 +75,7 @@ func (t *tagHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
t.repositoryName = router.Param(req.Context(), ":splat") t.repositoryName = router.Param(req.Context(), ":splat")
repository, err := t.repoCtl.GetByName(req.Context(), t.repositoryName) repository, err := t.repoCtl.GetByName(req.Context(), t.repositoryName)
if err != nil { if err != nil {
reg_error.Handle(w, req, err) serror.SendError(w, err)
return return
} }
@ -85,7 +85,7 @@ func (t *tagHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
"RepositoryID": repository.RepositoryID, "RepositoryID": repository.RepositoryID,
}}, nil) }}, nil)
if err != nil { if err != nil {
reg_error.Handle(w, req, err) serror.SendError(w, err)
return return
} }
if total == 0 { if total == 0 {
@ -110,7 +110,7 @@ func (t *tagHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
lastEntryIndex := util.IndexString(tagNames, lastEntry) lastEntryIndex := util.IndexString(tagNames, lastEntry)
if lastEntryIndex == -1 { if lastEntryIndex == -1 {
err := ierror.New(nil).WithCode(ierror.BadRequestCode).WithMessage(fmt.Sprintf("the last: %s should be a valid tag name.", lastEntry)) err := ierror.New(nil).WithCode(ierror.BadRequestCode).WithMessage(fmt.Sprintf("the last: %s should be a valid tag name.", lastEntry))
reg_error.Handle(w, req, err) serror.SendError(w, err)
return return
} }
resTags = tagNames[lastEntryIndex+1 : lastEntryIndex+maxEntries] resTags = tagNames[lastEntryIndex+1 : lastEntryIndex+maxEntries]
@ -123,7 +123,7 @@ func (t *tagHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
if tagNames[len(tagNames)-1] != resTags[len(resTags)-1] { if tagNames[len(tagNames)-1] != resTags[len(resTags)-1] {
urlStr, err := util.SetLinkHeader(req.URL.String(), maxEntries, resTags[len(resTags)-1]) urlStr, err := util.SetLinkHeader(req.URL.String(), maxEntries, resTags[len(resTags)-1])
if err != nil { if err != nil {
reg_error.Handle(w, req, err) serror.SendError(w, err)
return return
} }
w.Header().Set("Link", urlStr) w.Header().Set("Link", urlStr)
@ -140,7 +140,7 @@ func (t *tagHandler) sendResponse(w http.ResponseWriter, req *http.Request, tagN
Name: t.repositoryName, Name: t.repositoryName,
Tags: tagNames, Tags: tagNames,
}); err != nil { }); err != nil {
reg_error.Handle(w, req, err) serror.SendError(w, err)
return return
} }
} }

View File

@ -15,16 +15,10 @@
package handler package handler
import ( import (
"encoding/json" serror "github.com/goharbor/harbor/src/server/error"
"fmt" "github.com/goharbor/harbor/src/server/v2.0/restapi"
"log" "log"
"net/http" "net/http"
"net/http/httptest"
"strings"
"github.com/go-openapi/errors"
ierrors "github.com/goharbor/harbor/src/internal/error"
"github.com/goharbor/harbor/src/server/v2.0/restapi"
) )
// New returns http handler for API V2.0 // New returns http handler for API V2.0
@ -42,31 +36,10 @@ func New() http.Handler {
return h return h
} }
type apiError struct {
Code int32 `json:"code"`
Message string `json:"message"`
}
// Before executing operation handler, go-swagger will bind a parameters object to a request and validate the request, // Before executing operation handler, go-swagger will bind a parameters object to a request and validate the request,
// it will return directly when bind and validate failed. // it will return directly when bind and validate failed.
// The response format of the default ServeError implementation does not match the internal error response format. // The response format of the default ServeError implementation does not match the internal error response format.
// So we needed to convert the format to the internal error response format. // So we needed to convert the format to the internal error response format.
func serveError(rw http.ResponseWriter, r *http.Request, err error) { func serveError(rw http.ResponseWriter, r *http.Request, err error) {
w := httptest.NewRecorder() serror.SendError(rw, err)
errors.ServeError(w, r, err)
rw.WriteHeader(w.Code)
for key, values := range w.Header() {
for _, value := range values {
rw.Header().Add(key, value)
}
}
var er apiError
json.Unmarshal(w.Body.Bytes(), &er)
code := strings.Replace(strings.ToUpper(http.StatusText(w.Code)), " ", "_", -1)
e := ierrors.New(fmt.Errorf(er.Message)).WithCode(code)
rw.Write([]byte(e.Error()))
} }