forked from TrueCloudLab/distribution
Merge pull request #893 from aaronlehmann/functional-opts
Functional options for NewRegistryWithDriver
This commit is contained in:
commit
0189e8c218
8 changed files with 145 additions and 46 deletions
|
@ -118,13 +118,18 @@ func NewApp(ctx context.Context, configuration configuration.Configuration) *App
|
||||||
app.configureRedis(&configuration)
|
app.configureRedis(&configuration)
|
||||||
app.configureLogHook(&configuration)
|
app.configureLogHook(&configuration)
|
||||||
|
|
||||||
|
options := []storage.RegistryOption{}
|
||||||
|
|
||||||
|
if app.isCache {
|
||||||
|
options = append(options, storage.DisableDigestResumption)
|
||||||
|
}
|
||||||
|
|
||||||
// configure deletion
|
// configure deletion
|
||||||
var deleteEnabled bool
|
|
||||||
if d, ok := configuration.Storage["delete"]; ok {
|
if d, ok := configuration.Storage["delete"]; ok {
|
||||||
e, ok := d["enabled"]
|
e, ok := d["enabled"]
|
||||||
if ok {
|
if ok {
|
||||||
if deleteEnabled, ok = e.(bool); !ok {
|
if deleteEnabled, ok := e.(bool); ok && deleteEnabled {
|
||||||
deleteEnabled = false
|
options = append(options, storage.EnableDelete)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -139,10 +144,11 @@ func NewApp(ctx context.Context, configuration configuration.Configuration) *App
|
||||||
default:
|
default:
|
||||||
panic(fmt.Sprintf("invalid type for redirect config: %#v", redirectConfig))
|
panic(fmt.Sprintf("invalid type for redirect config: %#v", redirectConfig))
|
||||||
}
|
}
|
||||||
|
}
|
||||||
if redirectDisabled {
|
if redirectDisabled {
|
||||||
ctxu.GetLogger(app).Infof("backend redirection disabled")
|
ctxu.GetLogger(app).Infof("backend redirection disabled")
|
||||||
}
|
} else {
|
||||||
|
options = append(options, storage.EnableRedirect)
|
||||||
}
|
}
|
||||||
|
|
||||||
// configure storage caches
|
// configure storage caches
|
||||||
|
@ -158,10 +164,20 @@ func NewApp(ctx context.Context, configuration configuration.Configuration) *App
|
||||||
if app.redis == nil {
|
if app.redis == nil {
|
||||||
panic("redis configuration required to use for layerinfo cache")
|
panic("redis configuration required to use for layerinfo cache")
|
||||||
}
|
}
|
||||||
app.registry = storage.NewRegistryWithDriver(app, app.driver, rediscache.NewRedisBlobDescriptorCacheProvider(app.redis), deleteEnabled, !redirectDisabled, app.isCache)
|
cacheProvider := rediscache.NewRedisBlobDescriptorCacheProvider(app.redis)
|
||||||
|
localOptions := append(options, storage.BlobDescriptorCacheProvider(cacheProvider))
|
||||||
|
app.registry, err = storage.NewRegistry(app, app.driver, localOptions...)
|
||||||
|
if err != nil {
|
||||||
|
panic("could not create registry: " + err.Error())
|
||||||
|
}
|
||||||
ctxu.GetLogger(app).Infof("using redis blob descriptor cache")
|
ctxu.GetLogger(app).Infof("using redis blob descriptor cache")
|
||||||
case "inmemory":
|
case "inmemory":
|
||||||
app.registry = storage.NewRegistryWithDriver(app, app.driver, memorycache.NewInMemoryBlobDescriptorCacheProvider(), deleteEnabled, !redirectDisabled, app.isCache)
|
cacheProvider := memorycache.NewInMemoryBlobDescriptorCacheProvider()
|
||||||
|
localOptions := append(options, storage.BlobDescriptorCacheProvider(cacheProvider))
|
||||||
|
app.registry, err = storage.NewRegistry(app, app.driver, localOptions...)
|
||||||
|
if err != nil {
|
||||||
|
panic("could not create registry: " + err.Error())
|
||||||
|
}
|
||||||
ctxu.GetLogger(app).Infof("using inmemory blob descriptor cache")
|
ctxu.GetLogger(app).Infof("using inmemory blob descriptor cache")
|
||||||
default:
|
default:
|
||||||
if v != "" {
|
if v != "" {
|
||||||
|
@ -172,7 +188,10 @@ func NewApp(ctx context.Context, configuration configuration.Configuration) *App
|
||||||
|
|
||||||
if app.registry == nil {
|
if app.registry == nil {
|
||||||
// configure the registry if no cache section is available.
|
// configure the registry if no cache section is available.
|
||||||
app.registry = storage.NewRegistryWithDriver(app.Context, app.driver, nil, deleteEnabled, !redirectDisabled, app.isCache)
|
app.registry, err = storage.NewRegistry(app.Context, app.driver, options...)
|
||||||
|
if err != nil {
|
||||||
|
panic("could not create registry: " + err.Error())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
app.registry, err = applyRegistryMiddleware(app.Context, app.registry, configuration.Middleware["registry"])
|
app.registry, err = applyRegistryMiddleware(app.Context, app.registry, configuration.Middleware["registry"])
|
||||||
|
|
|
@ -26,12 +26,16 @@ import (
|
||||||
func TestAppDispatcher(t *testing.T) {
|
func TestAppDispatcher(t *testing.T) {
|
||||||
driver := inmemory.New()
|
driver := inmemory.New()
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
registry, err := storage.NewRegistry(ctx, driver, storage.BlobDescriptorCacheProvider(memorycache.NewInMemoryBlobDescriptorCacheProvider()), storage.EnableDelete, storage.EnableRedirect)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("error creating registry: %v", err)
|
||||||
|
}
|
||||||
app := &App{
|
app := &App{
|
||||||
Config: configuration.Configuration{},
|
Config: configuration.Configuration{},
|
||||||
Context: ctx,
|
Context: ctx,
|
||||||
router: v2.Router(),
|
router: v2.Router(),
|
||||||
driver: driver,
|
driver: driver,
|
||||||
registry: storage.NewRegistryWithDriver(ctx, driver, memorycache.NewInMemoryBlobDescriptorCacheProvider(), true, true, false),
|
registry: registry,
|
||||||
}
|
}
|
||||||
server := httptest.NewServer(app)
|
server := httptest.NewServer(app)
|
||||||
router := v2.Router()
|
router := v2.Router()
|
||||||
|
|
|
@ -80,13 +80,19 @@ func (te testEnv) RemoteStats() *map[string]int {
|
||||||
func makeTestEnv(t *testing.T, name string) testEnv {
|
func makeTestEnv(t *testing.T, name string) testEnv {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
localRegistry := storage.NewRegistryWithDriver(ctx, inmemory.New(), memory.NewInMemoryBlobDescriptorCacheProvider(), false, true, true)
|
localRegistry, err := storage.NewRegistry(ctx, inmemory.New(), storage.BlobDescriptorCacheProvider(memory.NewInMemoryBlobDescriptorCacheProvider()), storage.EnableRedirect, storage.DisableDigestResumption)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("error creating registry: %v", err)
|
||||||
|
}
|
||||||
localRepo, err := localRegistry.Repository(ctx, name)
|
localRepo, err := localRegistry.Repository(ctx, name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unexpected error getting repo: %v", err)
|
t.Fatalf("unexpected error getting repo: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
truthRegistry := storage.NewRegistryWithDriver(ctx, inmemory.New(), memory.NewInMemoryBlobDescriptorCacheProvider(), false, false, false)
|
truthRegistry, err := storage.NewRegistry(ctx, inmemory.New(), storage.BlobDescriptorCacheProvider(memory.NewInMemoryBlobDescriptorCacheProvider()))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("error creating registry: %v", err)
|
||||||
|
}
|
||||||
truthRepo, err := truthRegistry.Repository(ctx, name)
|
truthRepo, err := truthRegistry.Repository(ctx, name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unexpected error getting repo: %v", err)
|
t.Fatalf("unexpected error getting repo: %v", err)
|
||||||
|
|
|
@ -73,7 +73,10 @@ func (sm statsManifest) Tags() ([]string, error) {
|
||||||
|
|
||||||
func newManifestStoreTestEnv(t *testing.T, name, tag string) *manifestStoreTestEnv {
|
func newManifestStoreTestEnv(t *testing.T, name, tag string) *manifestStoreTestEnv {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
truthRegistry := storage.NewRegistryWithDriver(ctx, inmemory.New(), memory.NewInMemoryBlobDescriptorCacheProvider(), false, false, false)
|
truthRegistry, err := storage.NewRegistry(ctx, inmemory.New(), storage.BlobDescriptorCacheProvider(memory.NewInMemoryBlobDescriptorCacheProvider()))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("error creating registry: %v", err)
|
||||||
|
}
|
||||||
truthRepo, err := truthRegistry.Repository(ctx, name)
|
truthRepo, err := truthRegistry.Repository(ctx, name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unexpected error getting repo: %v", err)
|
t.Fatalf("unexpected error getting repo: %v", err)
|
||||||
|
@ -92,7 +95,10 @@ func newManifestStoreTestEnv(t *testing.T, name, tag string) *manifestStoreTestE
|
||||||
t.Fatalf(err.Error())
|
t.Fatalf(err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
localRegistry := storage.NewRegistryWithDriver(ctx, inmemory.New(), memory.NewInMemoryBlobDescriptorCacheProvider(), false, true, true)
|
localRegistry, err := storage.NewRegistry(ctx, inmemory.New(), storage.BlobDescriptorCacheProvider(memory.NewInMemoryBlobDescriptorCacheProvider()), storage.EnableRedirect, storage.DisableDigestResumption)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("error creating registry: %v", err)
|
||||||
|
}
|
||||||
localRepo, err := localRegistry.Repository(ctx, name)
|
localRepo, err := localRegistry.Repository(ctx, name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unexpected error getting repo: %v", err)
|
t.Fatalf("unexpected error getting repo: %v", err)
|
||||||
|
|
|
@ -33,7 +33,10 @@ func TestSimpleBlobUpload(t *testing.T) {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
imageName := "foo/bar"
|
imageName := "foo/bar"
|
||||||
driver := inmemory.New()
|
driver := inmemory.New()
|
||||||
registry := NewRegistryWithDriver(ctx, driver, memory.NewInMemoryBlobDescriptorCacheProvider(), true, true, false)
|
registry, err := NewRegistry(ctx, driver, BlobDescriptorCacheProvider(memory.NewInMemoryBlobDescriptorCacheProvider()), EnableDelete, EnableRedirect)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("error creating registry: %v", err)
|
||||||
|
}
|
||||||
repository, err := registry.Repository(ctx, imageName)
|
repository, err := registry.Repository(ctx, imageName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unexpected error getting repo: %v", err)
|
t.Fatalf("unexpected error getting repo: %v", err)
|
||||||
|
@ -193,7 +196,10 @@ func TestSimpleBlobUpload(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reuse state to test delete with a delete-disabled registry
|
// Reuse state to test delete with a delete-disabled registry
|
||||||
registry = NewRegistryWithDriver(ctx, driver, memory.NewInMemoryBlobDescriptorCacheProvider(), false, true, false)
|
registry, err = NewRegistry(ctx, driver, BlobDescriptorCacheProvider(memory.NewInMemoryBlobDescriptorCacheProvider()), EnableRedirect)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("error creating registry: %v", err)
|
||||||
|
}
|
||||||
repository, err = registry.Repository(ctx, imageName)
|
repository, err = registry.Repository(ctx, imageName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unexpected error getting repo: %v", err)
|
t.Fatalf("unexpected error getting repo: %v", err)
|
||||||
|
@ -212,7 +218,10 @@ func TestSimpleBlobRead(t *testing.T) {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
imageName := "foo/bar"
|
imageName := "foo/bar"
|
||||||
driver := inmemory.New()
|
driver := inmemory.New()
|
||||||
registry := NewRegistryWithDriver(ctx, driver, memory.NewInMemoryBlobDescriptorCacheProvider(), true, true, false)
|
registry, err := NewRegistry(ctx, driver, BlobDescriptorCacheProvider(memory.NewInMemoryBlobDescriptorCacheProvider()), EnableDelete, EnableRedirect)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("error creating registry: %v", err)
|
||||||
|
}
|
||||||
repository, err := registry.Repository(ctx, imageName)
|
repository, err := registry.Repository(ctx, imageName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unexpected error getting repo: %v", err)
|
t.Fatalf("unexpected error getting repo: %v", err)
|
||||||
|
@ -316,7 +325,10 @@ func TestLayerUploadZeroLength(t *testing.T) {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
imageName := "foo/bar"
|
imageName := "foo/bar"
|
||||||
driver := inmemory.New()
|
driver := inmemory.New()
|
||||||
registry := NewRegistryWithDriver(ctx, driver, memory.NewInMemoryBlobDescriptorCacheProvider(), true, true, false)
|
registry, err := NewRegistry(ctx, driver, BlobDescriptorCacheProvider(memory.NewInMemoryBlobDescriptorCacheProvider()), EnableDelete, EnableRedirect)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("error creating registry: %v", err)
|
||||||
|
}
|
||||||
repository, err := registry.Repository(ctx, imageName)
|
repository, err := registry.Repository(ctx, imageName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unexpected error getting repo: %v", err)
|
t.Fatalf("unexpected error getting repo: %v", err)
|
||||||
|
|
|
@ -22,7 +22,10 @@ func setupFS(t *testing.T) *setupEnv {
|
||||||
d := inmemory.New()
|
d := inmemory.New()
|
||||||
c := []byte("")
|
c := []byte("")
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
registry := NewRegistryWithDriver(ctx, d, memory.NewInMemoryBlobDescriptorCacheProvider(), false, true, false)
|
registry, err := NewRegistry(ctx, d, BlobDescriptorCacheProvider(memory.NewInMemoryBlobDescriptorCacheProvider()), EnableRedirect)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("error creating registry: %v", err)
|
||||||
|
}
|
||||||
rootpath, _ := pathFor(repositoriesRootPathSpec{})
|
rootpath, _ := pathFor(repositoriesRootPathSpec{})
|
||||||
|
|
||||||
repos := []string{
|
repos := []string{
|
||||||
|
|
|
@ -29,7 +29,10 @@ type manifestStoreTestEnv struct {
|
||||||
func newManifestStoreTestEnv(t *testing.T, name, tag string) *manifestStoreTestEnv {
|
func newManifestStoreTestEnv(t *testing.T, name, tag string) *manifestStoreTestEnv {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
driver := inmemory.New()
|
driver := inmemory.New()
|
||||||
registry := NewRegistryWithDriver(ctx, driver, memory.NewInMemoryBlobDescriptorCacheProvider(), true, true, false)
|
registry, err := NewRegistry(ctx, driver, BlobDescriptorCacheProvider(memory.NewInMemoryBlobDescriptorCacheProvider()), EnableDelete, EnableRedirect)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("error creating registry: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
repo, err := registry.Repository(ctx, name)
|
repo, err := registry.Repository(ctx, name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -348,7 +351,10 @@ func TestManifestStorage(t *testing.T) {
|
||||||
t.Errorf("Deleted manifest get returned non-nil")
|
t.Errorf("Deleted manifest get returned non-nil")
|
||||||
}
|
}
|
||||||
|
|
||||||
r := NewRegistryWithDriver(ctx, env.driver, memory.NewInMemoryBlobDescriptorCacheProvider(), false, true, false)
|
r, err := NewRegistry(ctx, env.driver, BlobDescriptorCacheProvider(memory.NewInMemoryBlobDescriptorCacheProvider()), EnableRedirect)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("error creating registry: %v", err)
|
||||||
|
}
|
||||||
repo, err := r.Repository(ctx, env.name)
|
repo, err := r.Repository(ctx, env.name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unexpected error getting repo: %v", err)
|
t.Fatalf("unexpected error getting repo: %v", err)
|
||||||
|
|
|
@ -12,28 +12,65 @@ import (
|
||||||
// package. All instances should descend from this object.
|
// package. All instances should descend from this object.
|
||||||
type registry struct {
|
type registry struct {
|
||||||
blobStore *blobStore
|
blobStore *blobStore
|
||||||
blobServer distribution.BlobServer
|
blobServer *blobServer
|
||||||
statter distribution.BlobStatter // global statter service.
|
statter *blobStatter // global statter service.
|
||||||
blobDescriptorCacheProvider cache.BlobDescriptorCacheProvider
|
blobDescriptorCacheProvider cache.BlobDescriptorCacheProvider
|
||||||
deleteEnabled bool
|
deleteEnabled bool
|
||||||
resumableDigestEnabled bool
|
resumableDigestEnabled bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewRegistryWithDriver creates a new registry instance from the provided
|
// RegistryOption is the type used for functional options for NewRegistry.
|
||||||
// driver. The resulting registry may be shared by multiple goroutines but is
|
type RegistryOption func(*registry) error
|
||||||
// cheap to allocate. If redirect is true, the backend blob server will
|
|
||||||
// attempt to use (StorageDriver).URLFor to serve all blobs.
|
|
||||||
//
|
|
||||||
// TODO(stevvooe): This function signature is getting very out of hand. Move to
|
|
||||||
// functional options for instance configuration.
|
|
||||||
func NewRegistryWithDriver(ctx context.Context, driver storagedriver.StorageDriver, blobDescriptorCacheProvider cache.BlobDescriptorCacheProvider, deleteEnabled bool, redirect bool, isCache bool) distribution.Namespace {
|
|
||||||
// create global statter, with cache.
|
|
||||||
var statter distribution.BlobDescriptorService = &blobStatter{
|
|
||||||
driver: driver,
|
|
||||||
}
|
|
||||||
|
|
||||||
if blobDescriptorCacheProvider != nil {
|
// EnableRedirect is a functional option for NewRegistry. It causes the backend
|
||||||
statter = cache.NewCachedBlobStatter(blobDescriptorCacheProvider, statter)
|
// blob server to attempt using (StorageDriver).URLFor to serve all blobs.
|
||||||
|
func EnableRedirect(registry *registry) error {
|
||||||
|
registry.blobServer.redirect = true
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// EnableDelete is a functional option for NewRegistry. It enables deletion on
|
||||||
|
// the registry.
|
||||||
|
func EnableDelete(registry *registry) error {
|
||||||
|
registry.deleteEnabled = true
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DisableDigestResumption is a functional option for NewRegistry. It should be
|
||||||
|
// used if the registry is acting as a caching proxy.
|
||||||
|
func DisableDigestResumption(registry *registry) error {
|
||||||
|
registry.resumableDigestEnabled = false
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// BlobDescriptorCacheProvider returns a functional option for
|
||||||
|
// NewRegistry. It creates a cached blob statter for use by the
|
||||||
|
// registry.
|
||||||
|
func BlobDescriptorCacheProvider(blobDescriptorCacheProvider cache.BlobDescriptorCacheProvider) RegistryOption {
|
||||||
|
// TODO(aaronl): The duplication of statter across several objects is
|
||||||
|
// ugly, and prevents us from using interface types in the registry
|
||||||
|
// struct. Ideally, blobStore and blobServer should be lazily
|
||||||
|
// initialized, and use the current value of
|
||||||
|
// blobDescriptorCacheProvider.
|
||||||
|
return func(registry *registry) error {
|
||||||
|
if blobDescriptorCacheProvider != nil {
|
||||||
|
statter := cache.NewCachedBlobStatter(blobDescriptorCacheProvider, registry.statter)
|
||||||
|
registry.blobStore.statter = statter
|
||||||
|
registry.blobServer.statter = statter
|
||||||
|
registry.blobDescriptorCacheProvider = blobDescriptorCacheProvider
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewRegistry creates a new registry instance from the provided driver. The
|
||||||
|
// resulting registry may be shared by multiple goroutines but is cheap to
|
||||||
|
// allocate. If the Redirect option is specified, the backend blob server will
|
||||||
|
// attempt to use (StorageDriver).URLFor to serve all blobs.
|
||||||
|
func NewRegistry(ctx context.Context, driver storagedriver.StorageDriver, options ...RegistryOption) (distribution.Namespace, error) {
|
||||||
|
// create global statter
|
||||||
|
statter := &blobStatter{
|
||||||
|
driver: driver,
|
||||||
}
|
}
|
||||||
|
|
||||||
bs := &blobStore{
|
bs := &blobStore{
|
||||||
|
@ -41,18 +78,24 @@ func NewRegistryWithDriver(ctx context.Context, driver storagedriver.StorageDriv
|
||||||
statter: statter,
|
statter: statter,
|
||||||
}
|
}
|
||||||
|
|
||||||
return ®istry{
|
registry := ®istry{
|
||||||
blobStore: bs,
|
blobStore: bs,
|
||||||
blobServer: &blobServer{
|
blobServer: &blobServer{
|
||||||
driver: driver,
|
driver: driver,
|
||||||
statter: statter,
|
statter: statter,
|
||||||
pathFn: bs.path,
|
pathFn: bs.path,
|
||||||
redirect: redirect,
|
|
||||||
},
|
},
|
||||||
blobDescriptorCacheProvider: blobDescriptorCacheProvider,
|
statter: statter,
|
||||||
deleteEnabled: deleteEnabled,
|
resumableDigestEnabled: true,
|
||||||
resumableDigestEnabled: !isCache,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for _, option := range options {
|
||||||
|
if err := option(registry); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return registry, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Scope returns the namespace scope for a registry. The registry
|
// Scope returns the namespace scope for a registry. The registry
|
||||||
|
|
Loading…
Reference in a new issue