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 := ®istry{ 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