Create Repositories method
This change removes the Catalog Service and replaces it with a more simplistic Repositories() method for obtaining a catalog of all repositories. The Repositories method takes a pre-allocated slice and fills it up to the size of the slice and returns the amount filled. The catalog is returned lexicographically and will start being filled from the last entry passed to Repositories(). If there are no more entries to fill, io.EOF will be returned. Signed-off-by: Patrick Devine <patrick.devine@docker.com> Conflicts: registry/client/repository.go registry/handlers/api_test.go
This commit is contained in:
parent
f3207e76c8
commit
bf62b7ebb7
9 changed files with 246 additions and 137 deletions
|
@ -1,36 +1,38 @@
|
|||
package storage
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"path"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/docker/distribution"
|
||||
"github.com/docker/distribution/context"
|
||||
storageDriver "github.com/docker/distribution/registry/storage/driver"
|
||||
"github.com/docker/distribution/registry/storage/driver"
|
||||
)
|
||||
|
||||
type catalogSvc struct {
|
||||
ctx context.Context
|
||||
driver storageDriver.StorageDriver
|
||||
}
|
||||
|
||||
var _ distribution.CatalogService = &catalogSvc{}
|
||||
|
||||
// Get returns a list, or partial list, of repositories in the registry.
|
||||
// Returns a list, or partial list, of repositories in the registry.
|
||||
// Because it's a quite expensive operation, it should only be used when building up
|
||||
// an initial set of repositories.
|
||||
func (c *catalogSvc) Get(maxEntries int, lastEntry string) ([]string, bool, error) {
|
||||
log.Infof("Retrieving up to %d entries of the catalog starting with '%s'", maxEntries, lastEntry)
|
||||
var repos []string
|
||||
func (reg *registry) Repositories(ctx context.Context, repos []string, last string) (n int, err error) {
|
||||
var foundRepos []string
|
||||
var errVal error
|
||||
|
||||
if len(repos) == 0 {
|
||||
return 0, errors.New("no space in slice")
|
||||
}
|
||||
|
||||
root, err := defaultPathMapper.path(repositoriesRootPathSpec{})
|
||||
if err != nil {
|
||||
return repos, false, err
|
||||
return 0, err
|
||||
}
|
||||
|
||||
Walk(c.ctx, c.driver, root, func(fileInfo storageDriver.FileInfo) error {
|
||||
// Walk each of the directories in our storage. Unfortunately since there's no
|
||||
// guarantee that storage will return files in lexigraphical order, we have
|
||||
// to store everything another slice, sort it and then copy it back to our
|
||||
// passed in slice.
|
||||
|
||||
Walk(ctx, reg.blobStore.driver, root, func(fileInfo driver.FileInfo) error {
|
||||
filePath := fileInfo.Path()
|
||||
|
||||
// lop the base path off
|
||||
|
@ -39,8 +41,8 @@ func (c *catalogSvc) Get(maxEntries int, lastEntry string) ([]string, bool, erro
|
|||
_, file := path.Split(repoPath)
|
||||
if file == "_layers" {
|
||||
repoPath = strings.TrimSuffix(repoPath, "/_layers")
|
||||
if repoPath > lastEntry {
|
||||
repos = append(repos, repoPath)
|
||||
if repoPath > last {
|
||||
foundRepos = append(foundRepos, repoPath)
|
||||
}
|
||||
return ErrSkipDir
|
||||
} else if strings.HasPrefix(file, "_") {
|
||||
|
@ -50,13 +52,14 @@ func (c *catalogSvc) Get(maxEntries int, lastEntry string) ([]string, bool, erro
|
|||
return nil
|
||||
})
|
||||
|
||||
sort.Strings(repos)
|
||||
sort.Strings(foundRepos)
|
||||
n = copy(repos, foundRepos)
|
||||
|
||||
moreEntries := false
|
||||
if len(repos) > maxEntries {
|
||||
moreEntries = true
|
||||
repos = repos[0:maxEntries]
|
||||
// Signal that we have no more entries by setting EOF
|
||||
if len(foundRepos) <= len(repos) {
|
||||
errVal = io.EOF
|
||||
}
|
||||
|
||||
return repos, moreEntries, nil
|
||||
return n, errVal
|
||||
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package storage
|
||||
|
||||
import (
|
||||
"io"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/distribution"
|
||||
|
@ -15,7 +16,6 @@ type setupEnv struct {
|
|||
driver driver.StorageDriver
|
||||
expected []string
|
||||
registry distribution.Namespace
|
||||
catalog distribution.CatalogService
|
||||
}
|
||||
|
||||
func setupFS(t *testing.T) *setupEnv {
|
||||
|
@ -41,8 +41,6 @@ func setupFS(t *testing.T) *setupEnv {
|
|||
}
|
||||
}
|
||||
|
||||
catalog := registry.Catalog(ctx)
|
||||
|
||||
expected := []string{
|
||||
"bar/c",
|
||||
"bar/d",
|
||||
|
@ -56,20 +54,21 @@ func setupFS(t *testing.T) *setupEnv {
|
|||
driver: d,
|
||||
expected: expected,
|
||||
registry: registry,
|
||||
catalog: catalog,
|
||||
}
|
||||
}
|
||||
|
||||
func TestCatalog(t *testing.T) {
|
||||
env := setupFS(t)
|
||||
|
||||
repos, more, _ := env.catalog.Get(100, "")
|
||||
p := make([]string, 50)
|
||||
|
||||
if !testEq(repos, env.expected) {
|
||||
numFilled, err := env.registry.Repositories(env.ctx, p, "")
|
||||
|
||||
if !testEq(p, env.expected, numFilled) {
|
||||
t.Errorf("Expected catalog repos err")
|
||||
}
|
||||
|
||||
if more {
|
||||
if err != io.EOF {
|
||||
t.Errorf("Catalog has more values which we aren't expecting")
|
||||
}
|
||||
}
|
||||
|
@ -78,50 +77,46 @@ func TestCatalogInParts(t *testing.T) {
|
|||
env := setupFS(t)
|
||||
|
||||
chunkLen := 2
|
||||
p := make([]string, chunkLen)
|
||||
|
||||
repos, more, _ := env.catalog.Get(chunkLen, "")
|
||||
if !testEq(repos, env.expected[0:chunkLen]) {
|
||||
numFilled, err := env.registry.Repositories(env.ctx, p, "")
|
||||
if err == io.EOF || numFilled != len(p) {
|
||||
t.Errorf("Expected more values in catalog")
|
||||
}
|
||||
|
||||
if !testEq(p, env.expected[0:chunkLen], numFilled) {
|
||||
t.Errorf("Expected catalog first chunk err")
|
||||
}
|
||||
|
||||
if !more {
|
||||
lastRepo := p[len(p)-1]
|
||||
numFilled, err = env.registry.Repositories(env.ctx, p, lastRepo)
|
||||
|
||||
if err == io.EOF || numFilled != len(p) {
|
||||
t.Errorf("Expected more values in catalog")
|
||||
}
|
||||
|
||||
lastRepo := repos[len(repos)-1]
|
||||
repos, more, _ = env.catalog.Get(chunkLen, lastRepo)
|
||||
|
||||
if !testEq(repos, env.expected[chunkLen:chunkLen*2]) {
|
||||
if !testEq(p, env.expected[chunkLen:chunkLen*2], numFilled) {
|
||||
t.Errorf("Expected catalog second chunk err")
|
||||
}
|
||||
|
||||
if !more {
|
||||
t.Errorf("Expected more values in catalog")
|
||||
}
|
||||
lastRepo = p[len(p)-1]
|
||||
numFilled, err = env.registry.Repositories(env.ctx, p, lastRepo)
|
||||
|
||||
lastRepo = repos[len(repos)-1]
|
||||
repos, more, _ = env.catalog.Get(chunkLen, lastRepo)
|
||||
|
||||
if !testEq(repos, env.expected[chunkLen*2:chunkLen*3-1]) {
|
||||
t.Errorf("Expected catalog third chunk err")
|
||||
}
|
||||
|
||||
if more {
|
||||
if err != io.EOF {
|
||||
t.Errorf("Catalog has more values which we aren't expecting")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func testEq(a, b []string) bool {
|
||||
if len(a) != len(b) {
|
||||
return false
|
||||
if !testEq(p, env.expected[chunkLen*2:chunkLen*3-1], numFilled) {
|
||||
t.Errorf("Expected catalog third chunk err")
|
||||
}
|
||||
|
||||
for count := range a {
|
||||
if a[count] != b[count] {
|
||||
}
|
||||
|
||||
func testEq(a, b []string, size int) bool {
|
||||
for cnt := 0; cnt < size-1; cnt++ {
|
||||
if a[cnt] != b[cnt] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
|
|
@ -55,15 +55,6 @@ func (reg *registry) Scope() distribution.Scope {
|
|||
return distribution.GlobalScope
|
||||
}
|
||||
|
||||
// Catalog returns an instance of the catalog service which can be
|
||||
// used to dump all of the repositories in a registry
|
||||
func (reg *registry) Catalog(ctx context.Context) distribution.CatalogService {
|
||||
return &catalogSvc{
|
||||
ctx: ctx,
|
||||
driver: reg.blobStore.driver,
|
||||
}
|
||||
}
|
||||
|
||||
// Repository returns an instance of the repository tied to the registry.
|
||||
// Instances should not be shared between goroutines but are cheap to
|
||||
// allocate. In general, they should be request scoped.
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue