Merge pull request #3143 from eyJhb/pagination

OCI: Add pagination on `/v2/<name>/tags/list`
This commit is contained in:
João Pereira
2021-05-22 15:05:18 +01:00
committed by GitHub
5 changed files with 75 additions and 0 deletions

View File

@@ -490,6 +490,18 @@ var routeDescriptors = []RouteDescriptor{
},
},
Failures: []ResponseDescriptor{
{
Name: "Invalid pagination number",
Description: "The received parameter n was invalid in some way, as described by the error code. The client should resolve the issue and retry the request.",
StatusCode: http.StatusBadRequest,
Body: BodyDescriptor{
ContentType: "application/json",
Format: errorsBody,
},
ErrorCodes: []errcode.ErrorCode{
ErrorCodePaginationNumberInvalid,
},
},
unauthorizedResponseDescriptor,
repositoryNotFoundResponseDescriptor,
deniedResponseDescriptor,

View File

@@ -144,4 +144,14 @@ var (
longer proceed.`,
HTTPStatusCode: http.StatusNotFound,
})
// ErrorCodePaginationNumberInvalid is returned when the `n` parameter is
// not an integer, or `n` is negative.
ErrorCodePaginationNumberInvalid = errcode.Register(errGroup, errcode.ErrorDescriptor{
Value: "PAGINATION_NUMBER_INVALID",
Message: "invalid number of results requested",
Description: `Returned when the "n" parameter (number of results
to return) is not an integer, or "n" is negative.`,
HTTPStatusCode: http.StatusBadRequest,
})
)

View File

@@ -3,6 +3,8 @@ package handlers
import (
"encoding/json"
"net/http"
"sort"
"strconv"
"github.com/distribution/distribution/v3"
"github.com/distribution/distribution/v3/registry/api/errcode"
@@ -49,6 +51,51 @@ func (th *tagsHandler) GetTags(w http.ResponseWriter, r *http.Request) {
return
}
// do pagination if requested
q := r.URL.Query()
// get entries after latest, if any specified
if lastEntry := q.Get("last"); lastEntry != "" {
lastEntryIndex := sort.SearchStrings(tags, lastEntry)
// as`sort.SearchStrings` can return len(tags), if the
// specified `lastEntry` is not found, we need to
// ensure it does not panic when slicing.
if lastEntryIndex == len(tags) {
tags = []string{}
} else {
tags = tags[lastEntryIndex+1:]
}
}
// if no error, means that the user requested `n` entries
if n := q.Get("n"); n != "" {
maxEntries, err := strconv.Atoi(n)
if err != nil || maxEntries < 0 {
th.Errors = append(th.Errors, v2.ErrorCodePaginationNumberInvalid.WithDetail(map[string]string{"n": n}))
return
}
// if there is requested more than or
// equal to the amount of tags we have,
// then set the request to equal `len(tags)`.
// the reason for the `=`, is so the else
// clause will only activate if there
// are tags left the user needs.
if maxEntries >= len(tags) {
maxEntries = len(tags)
} else if maxEntries > 0 {
// defined in `catalog.go`
urlStr, err := createLinkEntry(r.URL.String(), maxEntries, tags[maxEntries-1])
if err != nil {
th.Errors = append(th.Errors, errcode.ErrorCodeUnknown.WithDetail(err))
return
}
w.Header().Set("Link", urlStr)
}
tags = tags[:maxEntries]
}
w.Header().Set("Content-Type", "application/json")
enc := json.NewEncoder(w)

View File

@@ -3,6 +3,7 @@ package storage
import (
"context"
"path"
"sort"
"github.com/distribution/distribution/v3"
storagedriver "github.com/distribution/distribution/v3/registry/storage/driver"
@@ -47,6 +48,10 @@ func (ts *tagStore) All(ctx context.Context) ([]string, error) {
tags = append(tags, filename)
}
// there is no guarantee for the order,
// therefore sort before return.
sort.Strings(tags)
return tags, nil
}