forked from TrueCloudLab/distribution
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:
parent
5f7f871d8f
commit
a1f9f71e67
2 changed files with 97 additions and 16 deletions
|
@ -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) {
|
||||||
|
|
|
@ -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)
|
||||||
|
|
Loading…
Reference in a new issue