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>master
							parent
							
								
									d5441ca506
								
							
						
					
					
						commit
						9e211edc9d
					
				|  | @ -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) { | ||||
|  |  | |||
|  | @ -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) | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue