Catalog for V2 API Implementation

This change adds a basic catalog endpoint to the API, which returns a list,
or partial list, of all of the repositories contained in the registry.  Calls
to this endpoint are somewhat expensive, as every call requires walking a
large part of the registry.

Instead, to maintain a list of repositories, you would first call the catalog
endpoint to get an initial list, and then use the events API to maintain
any future repositories.

Signed-off-by: Patrick Devine <patrick.devine@docker.com>
This commit is contained in:
Patrick Devine 2015-07-13 13:08:13 -07:00
parent 0790a298ed
commit f3207e76c8
11 changed files with 568 additions and 1 deletions

View file

@ -444,3 +444,71 @@ func (bs *blobStatter) Stat(ctx context.Context, dgst digest.Digest) (distributi
return distribution.Descriptor{}, handleErrorResponse(resp)
}
}
// NewCatalog can be used to get a list of repositories
func NewCatalog(ctx context.Context, baseURL string, transport http.RoundTripper) (distribution.CatalogService, error) {
ub, err := v2.NewURLBuilderFromString(baseURL)
if err != nil {
return nil, err
}
client := &http.Client{
Transport: transport,
Timeout: 1 * time.Minute,
}
return &catalog{
client: client,
ub: ub,
context: ctx,
}, nil
}
type catalog struct {
client *http.Client
ub *v2.URLBuilder
context context.Context
}
func (c *catalog) Get(maxEntries int, last string) ([]string, bool, error) {
var repos []string
values := url.Values{}
if maxEntries > 0 {
values.Add("n", strconv.Itoa(maxEntries))
}
if last != "" {
values.Add("last", last)
}
u, err := c.ub.BuildCatalogURL(values)
if err != nil {
return nil, false, err
}
resp, err := c.client.Get(u)
if err != nil {
return nil, false, err
}
defer resp.Body.Close()
switch resp.StatusCode {
case http.StatusOK:
var ctlg struct {
Repositories []string `json:"repositories"`
}
decoder := json.NewDecoder(resp.Body)
if err := decoder.Decode(&ctlg); err != nil {
return nil, false, err
}
repos = ctlg.Repositories
default:
return nil, false, handleErrorResponse(resp)
}
return repos, false, nil
}

View file

@ -8,6 +8,7 @@ import (
"log"
"net/http"
"net/http/httptest"
"strconv"
"strings"
"testing"
"time"
@ -77,6 +78,23 @@ func addTestFetch(repo string, dgst digest.Digest, content []byte, m *testutil.R
})
}
func addTestCatalog(content []byte, m *testutil.RequestResponseMap) {
*m = append(*m, testutil.RequestResponseMapping{
Request: testutil.Request{
Method: "GET",
Route: "/v2/_catalog",
},
Response: testutil.Response{
StatusCode: http.StatusOK,
Body: content,
Headers: http.Header(map[string][]string{
"Content-Length": {strconv.Itoa(len(content))},
"Content-Type": {"application/json; charset=utf-8"},
}),
},
})
}
func TestBlobFetch(t *testing.T) {
d1, b1 := newRandomBlob(1024)
var m testutil.RequestResponseMap
@ -732,3 +750,26 @@ func TestManifestUnauthorized(t *testing.T) {
t.Fatalf("Unexpected message value: %q, expected %q", v2Err.Message, expected)
}
}
func TestCatalog(t *testing.T) {
var m testutil.RequestResponseMap
addTestCatalog([]byte("{\"repositories\":[\"foo\", \"bar\", \"baz\"]}"), &m)
e, c := testServer(m)
defer c()
ctx := context.Background()
ctlg, err := NewCatalog(ctx, e, nil)
if err != nil {
t.Fatal(err)
}
repos, _, err := ctlg.Get(0, "")
if err != nil {
t.Fatal(err)
}
if len(repos) != 3 {
t.Fatalf("Got wrong number of repos")
}
}