8efb9ca329
Add a generic Manifest interface to represent manifests in the registry and remove references to schema specific manifests. Add a ManifestBuilder to construct Manifest objects. Concrete manifest builders will exist for each manifest type and implementations will contain manifest specific data used to build a manifest. Remove Signatures() from Repository interface. Signatures are relevant only to schema1 manifests. Move access to the signature store inside the schema1 manifestStore. Add some API tests to verify signature roundtripping. schema1 ------- Change the way data is stored in schema1.Manifest to enable Payload() to be used to return complete Manifest JSON from the HTTP handler without knowledge of the schema1 protocol. tags ---- Move tag functionality to a seperate TagService and update ManifestService to use the new interfaces. Implement a driver based tagService to be backward compatible with the current tag service. Add a proxyTagService to enable the registry to get a digest for remote manifests from a tag. manifest store -------------- Remove revision store and move all signing functionality into the signed manifeststore. manifest registration --------------------- Add a mechanism to register manifest media types and to allow different manifest types to be Unmarshalled correctly. client ------ Add ManifestServiceOptions to client functions to allow tags to be passed into Put and Get for building correct registry URLs. Change functional arguments to be an interface type to allow passing data without mutating shared state. Signed-off-by: Richard Scothern <richard.scothern@gmail.com> Signed-off-by: Richard Scothern <richard.scothern@docker.com>
232 lines
7.1 KiB
Go
232 lines
7.1 KiB
Go
package storage
|
|
|
|
import (
|
|
"github.com/docker/distribution"
|
|
"github.com/docker/distribution/context"
|
|
"github.com/docker/distribution/reference"
|
|
"github.com/docker/distribution/registry/storage/cache"
|
|
storagedriver "github.com/docker/distribution/registry/storage/driver"
|
|
)
|
|
|
|
// registry is the top-level implementation of Registry for use in the storage
|
|
// package. All instances should descend from this object.
|
|
type registry struct {
|
|
blobStore *blobStore
|
|
blobServer *blobServer
|
|
statter *blobStatter // global statter service.
|
|
blobDescriptorCacheProvider cache.BlobDescriptorCacheProvider
|
|
deleteEnabled bool
|
|
resumableDigestEnabled bool
|
|
}
|
|
|
|
// RegistryOption is the type used for functional options for NewRegistry.
|
|
type RegistryOption func(*registry) error
|
|
|
|
// EnableRedirect is a functional option for NewRegistry. It causes the backend
|
|
// 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{
|
|
driver: driver,
|
|
statter: statter,
|
|
}
|
|
|
|
registry := ®istry{
|
|
blobStore: bs,
|
|
blobServer: &blobServer{
|
|
driver: driver,
|
|
statter: statter,
|
|
pathFn: bs.path,
|
|
},
|
|
statter: statter,
|
|
resumableDigestEnabled: true,
|
|
}
|
|
|
|
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
|
|
// will only serve repositories contained within this scope.
|
|
func (reg *registry) Scope() distribution.Scope {
|
|
return distribution.GlobalScope
|
|
}
|
|
|
|
// 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.
|
|
func (reg *registry) Repository(ctx context.Context, canonicalName string) (distribution.Repository, error) {
|
|
if _, err := reference.ParseNamed(canonicalName); err != nil {
|
|
return nil, distribution.ErrRepositoryNameInvalid{
|
|
Name: canonicalName,
|
|
Reason: err,
|
|
}
|
|
}
|
|
|
|
var descriptorCache distribution.BlobDescriptorService
|
|
if reg.blobDescriptorCacheProvider != nil {
|
|
var err error
|
|
descriptorCache, err = reg.blobDescriptorCacheProvider.RepositoryScoped(canonicalName)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
return &repository{
|
|
ctx: ctx,
|
|
registry: reg,
|
|
name: canonicalName,
|
|
descriptorCache: descriptorCache,
|
|
}, nil
|
|
}
|
|
|
|
// repository provides name-scoped access to various services.
|
|
type repository struct {
|
|
*registry
|
|
ctx context.Context
|
|
name string
|
|
descriptorCache distribution.BlobDescriptorService
|
|
}
|
|
|
|
// Name returns the name of the repository.
|
|
func (repo *repository) Name() string {
|
|
return repo.name
|
|
}
|
|
|
|
func (repo *repository) Tags(ctx context.Context) distribution.TagService {
|
|
tags := &tagStore{
|
|
repository: repo,
|
|
blobStore: repo.registry.blobStore,
|
|
}
|
|
|
|
return tags
|
|
}
|
|
|
|
// Manifests returns an instance of ManifestService. Instantiation is cheap and
|
|
// may be context sensitive in the future. The instance should be used similar
|
|
// to a request local.
|
|
func (repo *repository) Manifests(ctx context.Context, options ...distribution.ManifestServiceOption) (distribution.ManifestService, error) {
|
|
manifestLinkPathFns := []linkPathFunc{
|
|
// NOTE(stevvooe): Need to search through multiple locations since
|
|
// 2.1.0 unintentionally linked into _layers.
|
|
manifestRevisionLinkPath,
|
|
blobLinkPath,
|
|
}
|
|
|
|
ms := &manifestStore{
|
|
ctx: ctx,
|
|
repository: repo,
|
|
blobStore: &linkedBlobStore{
|
|
ctx: ctx,
|
|
blobStore: repo.blobStore,
|
|
repository: repo,
|
|
deleteEnabled: repo.registry.deleteEnabled,
|
|
blobAccessController: &linkedBlobStatter{
|
|
blobStore: repo.blobStore,
|
|
repository: repo,
|
|
linkPathFns: manifestLinkPathFns,
|
|
},
|
|
|
|
// TODO(stevvooe): linkPath limits this blob store to only
|
|
// manifests. This instance cannot be used for blob checks.
|
|
linkPathFns: manifestLinkPathFns,
|
|
},
|
|
signatures: &signatureStore{
|
|
ctx: ctx,
|
|
repository: repo,
|
|
blobStore: repo.blobStore,
|
|
},
|
|
}
|
|
|
|
// Apply options
|
|
for _, option := range options {
|
|
err := option.Apply(ms)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
return ms, nil
|
|
}
|
|
|
|
// Blobs returns an instance of the BlobStore. Instantiation is cheap and
|
|
// may be context sensitive in the future. The instance should be used similar
|
|
// to a request local.
|
|
func (repo *repository) Blobs(ctx context.Context) distribution.BlobStore {
|
|
var statter distribution.BlobDescriptorService = &linkedBlobStatter{
|
|
blobStore: repo.blobStore,
|
|
repository: repo,
|
|
linkPathFns: []linkPathFunc{blobLinkPath},
|
|
}
|
|
|
|
if repo.descriptorCache != nil {
|
|
statter = cache.NewCachedBlobStatter(repo.descriptorCache, statter)
|
|
}
|
|
|
|
return &linkedBlobStore{
|
|
blobStore: repo.blobStore,
|
|
blobServer: repo.blobServer,
|
|
blobAccessController: statter,
|
|
repository: repo,
|
|
ctx: ctx,
|
|
|
|
// TODO(stevvooe): linkPath limits this blob store to only layers.
|
|
// This instance cannot be used for manifest checks.
|
|
linkPathFns: []linkPathFunc{blobLinkPath},
|
|
deleteEnabled: repo.registry.deleteEnabled,
|
|
resumableDigestEnabled: repo.resumableDigestEnabled,
|
|
}
|
|
}
|