Changes the client Tags All() method to follow links

This returns all tags even when the registry forces pagination.

Signed-off-by: Brian Bland <brian.t.bland@gmail.com>
This commit is contained in:
Brian Bland 2016-06-28 14:44:51 -07:00
parent 5f7f871d8f
commit a1f9f71e67
2 changed files with 97 additions and 16 deletions

View File

@ -10,6 +10,7 @@ import (
"net/http" "net/http"
"net/url" "net/url"
"strconv" "strconv"
"strings"
"time" "time"
"github.com/docker/distribution" "github.com/docker/distribution"
@ -213,28 +214,35 @@ func (t *tags) All(ctx context.Context) ([]string, error) {
return tags, err return tags, err
} }
resp, err := t.client.Get(u) for {
if err != nil { resp, err := t.client.Get(u)
return tags, err
}
defer resp.Body.Close()
if SuccessStatus(resp.StatusCode) {
b, err := ioutil.ReadAll(resp.Body)
if err != nil { if err != nil {
return tags, err return tags, err
} }
defer resp.Body.Close()
tagsResponse := struct { if SuccessStatus(resp.StatusCode) {
Tags []string `json:"tags"` b, err := ioutil.ReadAll(resp.Body)
}{} if err != nil {
if err := json.Unmarshal(b, &tagsResponse); err != nil { return tags, err
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) { func descriptorFromResponse(response *http.Response) (distribution.Descriptor, error) {

View File

@ -3,6 +3,7 @@ package client
import ( import (
"bytes" "bytes"
"crypto/rand" "crypto/rand"
"encoding/json"
"fmt" "fmt"
"io" "io"
"log" "log"
@ -949,6 +950,78 @@ func TestManifestTags(t *testing.T) {
// TODO(dmcgowan): Check for error cases // 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) { func TestManifestUnauthorized(t *testing.T) {
repo, _ := reference.ParseNamed("test.example.com/repo") repo, _ := reference.ParseNamed("test.example.com/repo")
_, dgst, _ := newRandomSchemaV1Manifest(repo, "latest", 6) _, dgst, _ := newRandomSchemaV1Manifest(repo, "latest", 6)