Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactoring packages error processing #27979

Closed
wants to merge 8 commits into from
31 changes: 31 additions & 0 deletions modules/util/error.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ package util
import (
"errors"
"fmt"
"io"
"net/http"
)

// Common Errors forming the base of our error system
Expand All @@ -17,8 +19,27 @@ var (
ErrPermissionDenied = errors.New("permission denied")
ErrAlreadyExist = errors.New("resource already exists")
ErrNotExist = errors.New("resource does not exist")
ErrPayloadTooLarge = errors.New("payload too large")
ErrLimitExceeded = errors.New("limit exceeded")
)

// Automatically get HTTP status from error.
func StatusFromError(err error) int {
switch {
case errors.Is(err, ErrNotExist):
return http.StatusNotFound
case errors.Is(err, ErrAlreadyExist) || errors.Is(err, ErrLimitExceeded):
return http.StatusConflict
case errors.Is(err, ErrPayloadTooLarge):
return http.StatusRequestEntityTooLarge
case errors.Is(err, ErrInvalidArgument) || errors.Is(err, io.EOF):
return http.StatusBadRequest
case errors.Is(err, ErrPermissionDenied):
return http.StatusForbidden
}
return http.StatusInternalServerError
}

// SilentWrap provides a simple wrapper for a wrapped error where the wrapped error message plays no part in the error message
// Especially useful for "untyped" errors created with "errors.New(…)" that can be classified as 'invalid argument', 'permission denied', 'exists already', or 'does not exist'
type SilentWrap struct {
Expand Down Expand Up @@ -63,3 +84,13 @@ func NewAlreadyExistErrorf(message string, args ...any) error {
func NewNotExistErrorf(message string, args ...any) error {
return NewSilentWrapErrorf(ErrNotExist, message, args...)
}

// NewPayloadTooLargeErrorf returns an error that formats as the given text but unwraps as an ErrPayloadTooLarge
func NewPayloadTooLargeErrorf(message string, args ...any) error {
return NewSilentWrapErrorf(ErrPayloadTooLarge, message, args...)
}

// NewLimitExceededErrorf returns an error that formats as the given text but unwraps as an ErrLimitExceeded
func NewLimitExceededErrorf(message string, args ...any) error {
return NewSilentWrapErrorf(ErrLimitExceeded, message, args...)
}
69 changes: 23 additions & 46 deletions routers/api/packages/alpine/alpine.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import (
"crypto/x509"
"encoding/hex"
"encoding/pem"
"errors"
"fmt"
"io"
"net/http"
Expand All @@ -24,7 +23,8 @@ import (
alpine_service "code.gitea.io/gitea/services/packages/alpine"
)

func apiError(ctx *context.Context, status int, obj any) {
func apiError(ctx *context.Context, obj any, statuses ...int) {
status := helper.FormResponseCode(obj, statuses...)
helper.LogAndProcessError(ctx, status, obj, func(message string) {
ctx.PlainText(status, message)
})
Expand All @@ -33,25 +33,25 @@ func apiError(ctx *context.Context, status int, obj any) {
func GetRepositoryKey(ctx *context.Context) {
_, pub, err := alpine_service.GetOrCreateKeyPair(ctx, ctx.Package.Owner.ID)
if err != nil {
apiError(ctx, http.StatusInternalServerError, err)
apiError(ctx, err)
return
}

pubPem, _ := pem.Decode([]byte(pub))
if pubPem == nil {
apiError(ctx, http.StatusInternalServerError, "failed to decode private key pem")
apiError(ctx, "failed to decode private key pem")
return
}

pubKey, err := x509.ParsePKIXPublicKey(pubPem.Bytes)
if err != nil {
apiError(ctx, http.StatusInternalServerError, err)
apiError(ctx, err)
return
}

fingerprint, err := util.CreatePublicKeyFingerprint(pubKey)
if err != nil {
apiError(ctx, http.StatusInternalServerError, err)
apiError(ctx, err)
return
}

Expand All @@ -64,7 +64,7 @@ func GetRepositoryKey(ctx *context.Context) {
func GetRepositoryFile(ctx *context.Context) {
pv, err := alpine_service.GetOrCreateRepositoryVersion(ctx, ctx.Package.Owner.ID)
if err != nil {
apiError(ctx, http.StatusInternalServerError, err)
apiError(ctx, err)
return
}

Expand All @@ -77,11 +77,7 @@ func GetRepositoryFile(ctx *context.Context) {
},
)
if err != nil {
if errors.Is(err, util.ErrNotExist) {
apiError(ctx, http.StatusNotFound, err)
} else {
apiError(ctx, http.StatusInternalServerError, err)
}
apiError(ctx, err)
return
}

Expand All @@ -92,13 +88,13 @@ func UploadPackageFile(ctx *context.Context) {
branch := strings.TrimSpace(ctx.Params("branch"))
repository := strings.TrimSpace(ctx.Params("repository"))
if branch == "" || repository == "" {
apiError(ctx, http.StatusBadRequest, "invalid branch or repository")
apiError(ctx, "invalid branch or repository", http.StatusBadRequest)
return
}

upload, close, err := ctx.UploadStream()
if err != nil {
apiError(ctx, http.StatusInternalServerError, err)
apiError(ctx, err)
return
}
if close {
Expand All @@ -107,29 +103,25 @@ func UploadPackageFile(ctx *context.Context) {

buf, err := packages_module.CreateHashedBufferFromReader(upload)
if err != nil {
apiError(ctx, http.StatusInternalServerError, err)
apiError(ctx, err)
return
}
defer buf.Close()

pck, err := alpine_module.ParsePackage(buf)
if err != nil {
if errors.Is(err, util.ErrInvalidArgument) || err == io.EOF {
apiError(ctx, http.StatusBadRequest, err)
} else {
apiError(ctx, http.StatusInternalServerError, err)
}
apiError(ctx, err)
return
}

if _, err := buf.Seek(0, io.SeekStart); err != nil {
apiError(ctx, http.StatusInternalServerError, err)
apiError(ctx, err)
return
}

fileMetadataRaw, err := json.Marshal(pck.FileMetadata)
if err != nil {
apiError(ctx, http.StatusInternalServerError, err)
apiError(ctx, err)
return
}

Expand Down Expand Up @@ -162,19 +154,12 @@ func UploadPackageFile(ctx *context.Context) {
},
)
if err != nil {
switch err {
case packages_model.ErrDuplicatePackageVersion, packages_model.ErrDuplicatePackageFile:
apiError(ctx, http.StatusConflict, err)
case packages_service.ErrQuotaTotalCount, packages_service.ErrQuotaTypeSize, packages_service.ErrQuotaTotalSize:
apiError(ctx, http.StatusForbidden, err)
default:
apiError(ctx, http.StatusInternalServerError, err)
}
apiError(ctx, err)
return
}

if err := alpine_service.BuildSpecificRepositoryFiles(ctx, ctx.Package.Owner.ID, branch, repository, pck.FileMetadata.Architecture); err != nil {
apiError(ctx, http.StatusInternalServerError, err)
apiError(ctx, err)
return
}

Expand All @@ -189,21 +174,17 @@ func DownloadPackageFile(ctx *context.Context) {
CompositeKey: fmt.Sprintf("%s|%s|%s", ctx.Params("branch"), ctx.Params("repository"), ctx.Params("architecture")),
})
if err != nil {
apiError(ctx, http.StatusInternalServerError, err)
apiError(ctx, err)
return
}
if len(pfs) != 1 {
apiError(ctx, http.StatusNotFound, nil)
apiError(ctx, nil, http.StatusNotFound)
return
}

s, u, pf, err := packages_service.GetPackageFileStream(ctx, pfs[0])
if err != nil {
if errors.Is(err, util.ErrNotExist) {
apiError(ctx, http.StatusNotFound, err)
} else {
apiError(ctx, http.StatusInternalServerError, err)
}
apiError(ctx, err)
return
}

Expand All @@ -220,25 +201,21 @@ func DeletePackageFile(ctx *context.Context) {
CompositeKey: fmt.Sprintf("%s|%s|%s", branch, repository, architecture),
})
if err != nil {
apiError(ctx, http.StatusInternalServerError, err)
apiError(ctx, err)
return
}
if len(pfs) != 1 {
apiError(ctx, http.StatusNotFound, nil)
apiError(ctx, nil, http.StatusNotFound)
return
}

if err := packages_service.RemovePackageFileAndVersionIfUnreferenced(ctx, ctx.Doer, pfs[0]); err != nil {
if errors.Is(err, util.ErrNotExist) {
apiError(ctx, http.StatusNotFound, err)
} else {
apiError(ctx, http.StatusInternalServerError, err)
}
apiError(ctx, err)
return
}

if err := alpine_service.BuildSpecificRepositoryFiles(ctx, ctx.Package.Owner.ID, branch, repository, architecture); err != nil {
apiError(ctx, http.StatusInternalServerError, err)
apiError(ctx, err)
return
}

Expand Down
Loading