1package storage
2
3import (
4	"context"
5	"regexp"
6
7	"github.com/docker/distribution"
8	"github.com/docker/distribution/reference"
9	"github.com/docker/distribution/registry/storage/cache"
10	storagedriver "github.com/docker/distribution/registry/storage/driver"
11	"github.com/docker/libtrust"
12)
13
14// registry is the top-level implementation of Registry for use in the storage
15// package. All instances should descend from this object.
16type registry struct {
17	blobStore                    *blobStore
18	blobServer                   *blobServer
19	statter                      *blobStatter // global statter service.
20	blobDescriptorCacheProvider  cache.BlobDescriptorCacheProvider
21	deleteEnabled                bool
22	schema1Enabled               bool
23	resumableDigestEnabled       bool
24	schema1SigningKey            libtrust.PrivateKey
25	blobDescriptorServiceFactory distribution.BlobDescriptorServiceFactory
26	manifestURLs                 manifestURLs
27	driver                       storagedriver.StorageDriver
28}
29
30// manifestURLs holds regular expressions for controlling manifest URL whitelisting
31type manifestURLs struct {
32	allow *regexp.Regexp
33	deny  *regexp.Regexp
34}
35
36// RegistryOption is the type used for functional options for NewRegistry.
37type RegistryOption func(*registry) error
38
39// EnableRedirect is a functional option for NewRegistry. It causes the backend
40// blob server to attempt using (StorageDriver).URLFor to serve all blobs.
41func EnableRedirect(registry *registry) error {
42	registry.blobServer.redirect = true
43	return nil
44}
45
46// EnableDelete is a functional option for NewRegistry. It enables deletion on
47// the registry.
48func EnableDelete(registry *registry) error {
49	registry.deleteEnabled = true
50	return nil
51}
52
53// EnableSchema1 is a functional option for NewRegistry. It enables pushing of
54// schema1 manifests.
55func EnableSchema1(registry *registry) error {
56	registry.schema1Enabled = true
57	return nil
58}
59
60// DisableDigestResumption is a functional option for NewRegistry. It should be
61// used if the registry is acting as a caching proxy.
62func DisableDigestResumption(registry *registry) error {
63	registry.resumableDigestEnabled = false
64	return nil
65}
66
67// ManifestURLsAllowRegexp is a functional option for NewRegistry.
68func ManifestURLsAllowRegexp(r *regexp.Regexp) RegistryOption {
69	return func(registry *registry) error {
70		registry.manifestURLs.allow = r
71		return nil
72	}
73}
74
75// ManifestURLsDenyRegexp is a functional option for NewRegistry.
76func ManifestURLsDenyRegexp(r *regexp.Regexp) RegistryOption {
77	return func(registry *registry) error {
78		registry.manifestURLs.deny = r
79		return nil
80	}
81}
82
83// Schema1SigningKey returns a functional option for NewRegistry. It sets the
84// key for signing  all schema1 manifests.
85func Schema1SigningKey(key libtrust.PrivateKey) RegistryOption {
86	return func(registry *registry) error {
87		registry.schema1SigningKey = key
88		return nil
89	}
90}
91
92// BlobDescriptorServiceFactory returns a functional option for NewRegistry. It sets the
93// factory to create BlobDescriptorServiceFactory middleware.
94func BlobDescriptorServiceFactory(factory distribution.BlobDescriptorServiceFactory) RegistryOption {
95	return func(registry *registry) error {
96		registry.blobDescriptorServiceFactory = factory
97		return nil
98	}
99}
100
101// BlobDescriptorCacheProvider returns a functional option for
102// NewRegistry. It creates a cached blob statter for use by the
103// registry.
104func BlobDescriptorCacheProvider(blobDescriptorCacheProvider cache.BlobDescriptorCacheProvider) RegistryOption {
105	// TODO(aaronl): The duplication of statter across several objects is
106	// ugly, and prevents us from using interface types in the registry
107	// struct. Ideally, blobStore and blobServer should be lazily
108	// initialized, and use the current value of
109	// blobDescriptorCacheProvider.
110	return func(registry *registry) error {
111		if blobDescriptorCacheProvider != nil {
112			statter := cache.NewCachedBlobStatter(blobDescriptorCacheProvider, registry.statter)
113			registry.blobStore.statter = statter
114			registry.blobServer.statter = statter
115			registry.blobDescriptorCacheProvider = blobDescriptorCacheProvider
116		}
117		return nil
118	}
119}
120
121// NewRegistry creates a new registry instance from the provided driver. The
122// resulting registry may be shared by multiple goroutines but is cheap to
123// allocate. If the Redirect option is specified, the backend blob server will
124// attempt to use (StorageDriver).URLFor to serve all blobs.
125func NewRegistry(ctx context.Context, driver storagedriver.StorageDriver, options ...RegistryOption) (distribution.Namespace, error) {
126	// create global statter
127	statter := &blobStatter{
128		driver: driver,
129	}
130
131	bs := &blobStore{
132		driver:  driver,
133		statter: statter,
134	}
135
136	registry := &registry{
137		blobStore: bs,
138		blobServer: &blobServer{
139			driver:  driver,
140			statter: statter,
141			pathFn:  bs.path,
142		},
143		statter:                statter,
144		resumableDigestEnabled: true,
145		driver:                 driver,
146	}
147
148	for _, option := range options {
149		if err := option(registry); err != nil {
150			return nil, err
151		}
152	}
153
154	return registry, nil
155}
156
157// Scope returns the namespace scope for a registry. The registry
158// will only serve repositories contained within this scope.
159func (reg *registry) Scope() distribution.Scope {
160	return distribution.GlobalScope
161}
162
163// Repository returns an instance of the repository tied to the registry.
164// Instances should not be shared between goroutines but are cheap to
165// allocate. In general, they should be request scoped.
166func (reg *registry) Repository(ctx context.Context, canonicalName reference.Named) (distribution.Repository, error) {
167	var descriptorCache distribution.BlobDescriptorService
168	if reg.blobDescriptorCacheProvider != nil {
169		var err error
170		descriptorCache, err = reg.blobDescriptorCacheProvider.RepositoryScoped(canonicalName.Name())
171		if err != nil {
172			return nil, err
173		}
174	}
175
176	return &repository{
177		ctx:             ctx,
178		registry:        reg,
179		name:            canonicalName,
180		descriptorCache: descriptorCache,
181	}, nil
182}
183
184func (reg *registry) Blobs() distribution.BlobEnumerator {
185	return reg.blobStore
186}
187
188func (reg *registry) BlobStatter() distribution.BlobStatter {
189	return reg.statter
190}
191
192// repository provides name-scoped access to various services.
193type repository struct {
194	*registry
195	ctx             context.Context
196	name            reference.Named
197	descriptorCache distribution.BlobDescriptorService
198}
199
200// Name returns the name of the repository.
201func (repo *repository) Named() reference.Named {
202	return repo.name
203}
204
205func (repo *repository) Tags(ctx context.Context) distribution.TagService {
206	tags := &tagStore{
207		repository: repo,
208		blobStore:  repo.registry.blobStore,
209	}
210
211	return tags
212}
213
214// Manifests returns an instance of ManifestService. Instantiation is cheap and
215// may be context sensitive in the future. The instance should be used similar
216// to a request local.
217func (repo *repository) Manifests(ctx context.Context, options ...distribution.ManifestServiceOption) (distribution.ManifestService, error) {
218	manifestLinkPathFns := []linkPathFunc{
219		// NOTE(stevvooe): Need to search through multiple locations since
220		// 2.1.0 unintentionally linked into  _layers.
221		manifestRevisionLinkPath,
222		blobLinkPath,
223	}
224
225	manifestDirectoryPathSpec := manifestRevisionsPathSpec{name: repo.name.Name()}
226
227	var statter distribution.BlobDescriptorService = &linkedBlobStatter{
228		blobStore:   repo.blobStore,
229		repository:  repo,
230		linkPathFns: manifestLinkPathFns,
231	}
232
233	if repo.registry.blobDescriptorServiceFactory != nil {
234		statter = repo.registry.blobDescriptorServiceFactory.BlobAccessController(statter)
235	}
236
237	blobStore := &linkedBlobStore{
238		ctx:                  ctx,
239		blobStore:            repo.blobStore,
240		repository:           repo,
241		deleteEnabled:        repo.registry.deleteEnabled,
242		blobAccessController: statter,
243
244		// TODO(stevvooe): linkPath limits this blob store to only
245		// manifests. This instance cannot be used for blob checks.
246		linkPathFns:           manifestLinkPathFns,
247		linkDirectoryPathSpec: manifestDirectoryPathSpec,
248	}
249
250	var v1Handler ManifestHandler
251	if repo.schema1Enabled {
252		v1Handler = &signedManifestHandler{
253			ctx:               ctx,
254			schema1SigningKey: repo.schema1SigningKey,
255			repository:        repo,
256			blobStore:         blobStore,
257		}
258	} else {
259		v1Handler = &v1UnsupportedHandler{
260			innerHandler: &signedManifestHandler{
261				ctx:               ctx,
262				schema1SigningKey: repo.schema1SigningKey,
263				repository:        repo,
264				blobStore:         blobStore,
265			},
266		}
267	}
268
269	ms := &manifestStore{
270		ctx:            ctx,
271		repository:     repo,
272		blobStore:      blobStore,
273		schema1Handler: v1Handler,
274		schema2Handler: &schema2ManifestHandler{
275			ctx:          ctx,
276			repository:   repo,
277			blobStore:    blobStore,
278			manifestURLs: repo.registry.manifestURLs,
279		},
280		manifestListHandler: &manifestListHandler{
281			ctx:        ctx,
282			repository: repo,
283			blobStore:  blobStore,
284		},
285		ocischemaHandler: &ocischemaManifestHandler{
286			ctx:          ctx,
287			repository:   repo,
288			blobStore:    blobStore,
289			manifestURLs: repo.registry.manifestURLs,
290		},
291	}
292
293	// Apply options
294	for _, option := range options {
295		err := option.Apply(ms)
296		if err != nil {
297			return nil, err
298		}
299	}
300
301	return ms, nil
302}
303
304// Blobs returns an instance of the BlobStore. Instantiation is cheap and
305// may be context sensitive in the future. The instance should be used similar
306// to a request local.
307func (repo *repository) Blobs(ctx context.Context) distribution.BlobStore {
308	var statter distribution.BlobDescriptorService = &linkedBlobStatter{
309		blobStore:   repo.blobStore,
310		repository:  repo,
311		linkPathFns: []linkPathFunc{blobLinkPath},
312	}
313
314	if repo.descriptorCache != nil {
315		statter = cache.NewCachedBlobStatter(repo.descriptorCache, statter)
316	}
317
318	if repo.registry.blobDescriptorServiceFactory != nil {
319		statter = repo.registry.blobDescriptorServiceFactory.BlobAccessController(statter)
320	}
321
322	return &linkedBlobStore{
323		registry:             repo.registry,
324		blobStore:            repo.blobStore,
325		blobServer:           repo.blobServer,
326		blobAccessController: statter,
327		repository:           repo,
328		ctx:                  ctx,
329
330		// TODO(stevvooe): linkPath limits this blob store to only layers.
331		// This instance cannot be used for manifest checks.
332		linkPathFns:            []linkPathFunc{blobLinkPath},
333		deleteEnabled:          repo.registry.deleteEnabled,
334		resumableDigestEnabled: repo.resumableDigestEnabled,
335	}
336}
337