mirror of
https://github.com/distribution/distribution.git
synced 2026-05-01 12:55:30 +00:00
fix(proxy): fix tag list endpoint in proxy mode (#4846)
This commit is contained in:
@@ -345,7 +345,66 @@ func (t *tags) Lookup(ctx context.Context, digest v1.Descriptor) ([]string, erro
|
||||
}
|
||||
|
||||
func (t *tags) List(ctx context.Context, limit int, last string) ([]string, error) {
|
||||
panic("not implemented")
|
||||
if limit < 0 {
|
||||
tags, err := t.All(ctx)
|
||||
if err != nil {
|
||||
return tags, err
|
||||
}
|
||||
// return io.EOF, indicating that there are no more tags to list
|
||||
return tags, io.EOF
|
||||
}
|
||||
v := url.Values{}
|
||||
v.Add("n", strconv.Itoa(limit))
|
||||
if last != "" {
|
||||
v.Add("last", last)
|
||||
}
|
||||
listURLStr, err := t.ub.BuildTagsURL(t.name, v)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
listURL, err := url.Parse(listURLStr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
preAlloc := 1000
|
||||
if limit < preAlloc {
|
||||
preAlloc = limit
|
||||
}
|
||||
tags := make([]string, 0, preAlloc)
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, listURL.String(), nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resp, err := t.client.Do(req)
|
||||
if err != nil {
|
||||
return tags, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if err := HandleHTTPResponseError(resp); err != nil {
|
||||
return tags, err
|
||||
}
|
||||
|
||||
b, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return tags, err
|
||||
}
|
||||
|
||||
tagsResponse := struct {
|
||||
Tags []string `json:"tags"`
|
||||
}{}
|
||||
if err := json.Unmarshal(b, &tagsResponse); err != nil {
|
||||
return tags, err
|
||||
}
|
||||
tags = append(tags, tagsResponse.Tags...)
|
||||
// if there is a Link header, return nil to indicate that there are more tags to list
|
||||
// otherwise return io.EOF to indicate that there are no more tags to list
|
||||
if link := resp.Header.Get("Link"); link != "" {
|
||||
return tags, nil
|
||||
}
|
||||
return tags, io.EOF
|
||||
}
|
||||
|
||||
func (t *tags) Tag(ctx context.Context, tag string, desc distribution.Descriptor) error {
|
||||
|
||||
@@ -1660,6 +1660,88 @@ func TestManifestTagsPaginated(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestManifestTagsListPaginated(t *testing.T) {
|
||||
s := httptest.NewServer(http.NotFoundHandler())
|
||||
defer s.Close()
|
||||
|
||||
repo, _ := reference.WithName("test.example.com/repo/tags/list")
|
||||
tagsList := []string{"tag1", "tag2", "funtag"}
|
||||
var m testutil.RequestResponseMap
|
||||
for i := range 3 {
|
||||
body, err := json.Marshal(map[string]any{
|
||||
"name": "test.example.com/repo/tags/list",
|
||||
"tags": []string{tagsList[i]},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
queryParams := make(map[string][]string)
|
||||
if i > 0 {
|
||||
queryParams["n"] = []string{"1"}
|
||||
queryParams["last"] = []string{tagsList[i-1]}
|
||||
} else {
|
||||
queryParams["n"] = []string{"1"}
|
||||
}
|
||||
|
||||
// Test both relative and absolute links.
|
||||
relativeLink := "/v2/" + repo.Name() + "/tags/list?n=1&last=" + tagsList[i]
|
||||
var link string
|
||||
switch i {
|
||||
case 0:
|
||||
link = relativeLink
|
||||
case len(tagsList) - 1:
|
||||
link = ""
|
||||
default:
|
||||
link = s.URL + relativeLink
|
||||
}
|
||||
|
||||
headers := http.Header(map[string][]string{
|
||||
"Content-Length": {fmt.Sprint(len(body))},
|
||||
"Last-Modified": {time.Now().Add(-1 * time.Second).Format(time.ANSIC)},
|
||||
})
|
||||
if link != "" {
|
||||
headers.Set("Link", fmt.Sprintf(`<%s>; rel="next"`, link))
|
||||
}
|
||||
|
||||
m = append(m, testutil.RequestResponseMapping{
|
||||
Request: testutil.Request{
|
||||
Method: http.MethodGet,
|
||||
Route: "/v2/" + repo.Name() + "/tags/list",
|
||||
QueryParams: queryParams,
|
||||
},
|
||||
Response: testutil.Response{
|
||||
StatusCode: http.StatusOK,
|
||||
Body: body,
|
||||
Headers: headers,
|
||||
},
|
||||
})
|
||||
}
|
||||
s.Config.Handler = testutil.NewHandler(m)
|
||||
|
||||
r, err := NewRepository(repo, s.URL, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
ctx := dcontext.Background()
|
||||
tagService := r.Tags(ctx)
|
||||
|
||||
last := ""
|
||||
for i := range 3 {
|
||||
tags, err := tagService.List(ctx, 1, last)
|
||||
if err != nil && err != io.EOF {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(tags) != 1 {
|
||||
t.Fatalf("Wrong number of tags returned: %d, expected 1", len(tags))
|
||||
}
|
||||
if tags[0] != tagsList[i] {
|
||||
t.Fatalf("Wrong tag returned: %s, expected %s", tags[0], tagsList[i])
|
||||
}
|
||||
last = tags[0]
|
||||
}
|
||||
}
|
||||
|
||||
func TestManifestUnauthorized(t *testing.T) {
|
||||
repo, _ := reference.WithName("test.example.com/repo")
|
||||
_, dgst, _ := newRandomOCIManifest(t, 6)
|
||||
|
||||
@@ -2,6 +2,7 @@ package proxy
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
|
||||
"github.com/distribution/distribution/v3"
|
||||
v1 "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
@@ -67,5 +68,12 @@ func (pt proxyTagService) Lookup(ctx context.Context, digest v1.Descriptor) ([]s
|
||||
}
|
||||
|
||||
func (pt proxyTagService) List(ctx context.Context, limit int, last string) ([]string, error) {
|
||||
return []string{}, distribution.ErrUnsupported
|
||||
err := pt.authChallenger.tryEstablishChallenges(ctx)
|
||||
if err == nil {
|
||||
tags, err := pt.remoteTags.List(ctx, limit, last)
|
||||
if err == nil || err == io.EOF {
|
||||
return tags, err
|
||||
}
|
||||
}
|
||||
return pt.localTags.List(ctx, limit, last)
|
||||
}
|
||||
|
||||
@@ -63,8 +63,22 @@ func (m *mockTagStore) Lookup(ctx context.Context, digest distribution.Descripto
|
||||
}
|
||||
|
||||
func (m *mockTagStore) List(ctx context.Context, limit int, last string) ([]string, error) {
|
||||
panic("not implemented")
|
||||
m.Lock()
|
||||
defer m.Unlock()
|
||||
tags := make([]string, 0, len(m.mapping))
|
||||
for tag := range m.mapping {
|
||||
tags = append(tags, tag)
|
||||
}
|
||||
sort.Strings(tags)
|
||||
result := make([]string, 0, limit)
|
||||
for _, tag := range tags {
|
||||
if tag > last && len(result) < limit {
|
||||
result = append(result, tag)
|
||||
}
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func testProxyTagService(local, remote map[string]distribution.Descriptor) *proxyTagService {
|
||||
if local == nil {
|
||||
local = make(map[string]v1.Descriptor)
|
||||
@@ -179,7 +193,33 @@ func TestGet(t *testing.T) {
|
||||
t.Fatalf("Unexpected tags returned from All() : %v ", all)
|
||||
}
|
||||
|
||||
if proxyTags.authChallenger.(*mockChallenger).count != 4 {
|
||||
t.Fatalf("Expected 4 auth challenge calls, got %#v", proxyTags.authChallenger)
|
||||
list, err := proxyTags.List(ctx, 1, "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if len(list) != 1 {
|
||||
t.Fatalf("Unexpected tag length returned from List() : %d ", len(list))
|
||||
}
|
||||
|
||||
if list[0] != "funtag" {
|
||||
t.Fatalf("Unexpected tags returned from List() : %v ", list)
|
||||
}
|
||||
|
||||
list2, err := proxyTags.List(ctx, 1, "funtag")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if len(list2) != 1 {
|
||||
t.Fatalf("Unexpected tag length returned from List() : %d ", len(list2))
|
||||
}
|
||||
|
||||
if list2[0] != "remote" {
|
||||
t.Fatalf("Unexpected tags returned from List() : %v ", list2)
|
||||
}
|
||||
|
||||
if proxyTags.authChallenger.(*mockChallenger).count != 6 {
|
||||
t.Fatalf("Expected 6 auth challenge calls, got %#v", proxyTags.authChallenger)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user