From 9e211edc9dee82b5ca2deec9f5a996cf1e946e4d Mon Sep 17 00:00:00 2001 From: Brian Bland Date: Tue, 28 Jun 2016 14:44:51 -0700 Subject: [PATCH] Changes the client Tags All() method to follow links This returns all tags even when the registry forces pagination. Signed-off-by: Brian Bland --- docs/client/repository.go | 40 +++++++++++-------- docs/client/repository_test.go | 73 ++++++++++++++++++++++++++++++++++ 2 files changed, 97 insertions(+), 16 deletions(-) diff --git a/docs/client/repository.go b/docs/client/repository.go index 323ab5086..973125561 100644 --- a/docs/client/repository.go +++ b/docs/client/repository.go @@ -10,6 +10,7 @@ import ( "net/http" "net/url" "strconv" + "strings" "time" "github.com/docker/distribution" @@ -213,28 +214,35 @@ func (t *tags) All(ctx context.Context) ([]string, error) { return tags, err } - resp, err := t.client.Get(u) - if err != nil { - return tags, err - } - defer resp.Body.Close() - - if SuccessStatus(resp.StatusCode) { - b, err := ioutil.ReadAll(resp.Body) + for { + resp, err := t.client.Get(u) if err != nil { return tags, err } + defer resp.Body.Close() - tagsResponse := struct { - Tags []string `json:"tags"` - }{} - if err := json.Unmarshal(b, &tagsResponse); err != nil { - return tags, err + if SuccessStatus(resp.StatusCode) { + b, err := ioutil.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 link := resp.Header.Get("Link"); link != "" { + u = strings.Trim(strings.Split(link, ";")[0], "<>") + } else { + return tags, nil + } + } else { + return tags, HandleErrorResponse(resp) } - tags = tagsResponse.Tags - return tags, nil } - return tags, HandleErrorResponse(resp) } func descriptorFromResponse(response *http.Response) (distribution.Descriptor, error) { diff --git a/docs/client/repository_test.go b/docs/client/repository_test.go index 19b6ca2c4..d945596b5 100644 --- a/docs/client/repository_test.go +++ b/docs/client/repository_test.go @@ -3,6 +3,7 @@ package client import ( "bytes" "crypto/rand" + "encoding/json" "fmt" "io" "log" @@ -949,6 +950,78 @@ func TestManifestTags(t *testing.T) { // TODO(dmcgowan): Check for error cases } +func TestManifestTagsPaginated(t *testing.T) { + s := httptest.NewServer(http.NotFoundHandler()) + defer s.Close() + + repo, _ := reference.ParseNamed("test.example.com/repo/tags/list") + tagsList := []string{"tag1", "tag2", "funtag"} + var m testutil.RequestResponseMap + for i := 0; i < 3; i++ { + body, err := json.Marshal(map[string]interface{}{ + "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]} + } + headers := http.Header(map[string][]string{ + "Content-Length": {fmt.Sprint(len(body))}, + "Last-Modified": {time.Now().Add(-1 * time.Second).Format(time.ANSIC)}, + }) + if i < 2 { + headers.Set("Link", "<"+s.URL+"/v2/"+repo.Name()+"/tags/list?n=1&last="+tagsList[i]+`>; rel="next"`) + } + m = append(m, testutil.RequestResponseMapping{ + Request: testutil.Request{ + Method: "GET", + 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(context.Background(), repo, s.URL, nil) + if err != nil { + t.Fatal(err) + } + + ctx := context.Background() + tagService := r.Tags(ctx) + + tags, err := tagService.All(ctx) + if err != nil { + t.Fatal(tags, err) + } + if len(tags) != 3 { + t.Fatalf("Wrong number of tags returned: %d, expected 3", len(tags)) + } + + expected := map[string]struct{}{ + "tag1": {}, + "tag2": {}, + "funtag": {}, + } + for _, t := range tags { + delete(expected, t) + } + if len(expected) != 0 { + t.Fatalf("unexpected tags returned: %v", expected) + } +} + func TestManifestUnauthorized(t *testing.T) { repo, _ := reference.ParseNamed("test.example.com/repo") _, dgst, _ := newRandomSchemaV1Manifest(repo, "latest", 6)