github.com/npaton/distribution@v2.3.1-rc.0+incompatible/registry/storage/linkedblobstore.go (about) 1 package storage 2 3 import ( 4 "fmt" 5 "net/http" 6 "time" 7 8 "github.com/docker/distribution" 9 "github.com/docker/distribution/context" 10 "github.com/docker/distribution/digest" 11 "github.com/docker/distribution/reference" 12 "github.com/docker/distribution/registry/storage/driver" 13 "github.com/docker/distribution/uuid" 14 ) 15 16 // linkPathFunc describes a function that can resolve a link based on the 17 // repository name and digest. 18 type linkPathFunc func(name string, dgst digest.Digest) (string, error) 19 20 // linkedBlobStore provides a full BlobService that namespaces the blobs to a 21 // given repository. Effectively, it manages the links in a given repository 22 // that grant access to the global blob store. 23 type linkedBlobStore struct { 24 *blobStore 25 registry *registry 26 blobServer distribution.BlobServer 27 blobAccessController distribution.BlobDescriptorService 28 repository distribution.Repository 29 ctx context.Context // only to be used where context can't come through method args 30 deleteEnabled bool 31 resumableDigestEnabled bool 32 33 // linkPathFns specifies one or more path functions allowing one to 34 // control the repository blob link set to which the blob store 35 // dispatches. This is required because manifest and layer blobs have not 36 // yet been fully merged. At some point, this functionality should be 37 // removed an the blob links folder should be merged. The first entry is 38 // treated as the "canonical" link location and will be used for writes. 39 linkPathFns []linkPathFunc 40 } 41 42 var _ distribution.BlobStore = &linkedBlobStore{} 43 44 func (lbs *linkedBlobStore) Stat(ctx context.Context, dgst digest.Digest) (distribution.Descriptor, error) { 45 return lbs.blobAccessController.Stat(ctx, dgst) 46 } 47 48 func (lbs *linkedBlobStore) Get(ctx context.Context, dgst digest.Digest) ([]byte, error) { 49 canonical, err := lbs.Stat(ctx, dgst) // access check 50 if err != nil { 51 return nil, err 52 } 53 54 return lbs.blobStore.Get(ctx, canonical.Digest) 55 } 56 57 func (lbs *linkedBlobStore) Open(ctx context.Context, dgst digest.Digest) (distribution.ReadSeekCloser, error) { 58 canonical, err := lbs.Stat(ctx, dgst) // access check 59 if err != nil { 60 return nil, err 61 } 62 63 return lbs.blobStore.Open(ctx, canonical.Digest) 64 } 65 66 func (lbs *linkedBlobStore) ServeBlob(ctx context.Context, w http.ResponseWriter, r *http.Request, dgst digest.Digest) error { 67 canonical, err := lbs.Stat(ctx, dgst) // access check 68 if err != nil { 69 return err 70 } 71 72 if canonical.MediaType != "" { 73 // Set the repository local content type. 74 w.Header().Set("Content-Type", canonical.MediaType) 75 } 76 77 return lbs.blobServer.ServeBlob(ctx, w, r, canonical.Digest) 78 } 79 80 func (lbs *linkedBlobStore) Put(ctx context.Context, mediaType string, p []byte) (distribution.Descriptor, error) { 81 dgst := digest.FromBytes(p) 82 // Place the data in the blob store first. 83 desc, err := lbs.blobStore.Put(ctx, mediaType, p) 84 if err != nil { 85 context.GetLogger(ctx).Errorf("error putting into main store: %v", err) 86 return distribution.Descriptor{}, err 87 } 88 89 if err := lbs.blobAccessController.SetDescriptor(ctx, dgst, desc); err != nil { 90 return distribution.Descriptor{}, err 91 } 92 93 // TODO(stevvooe): Write out mediatype if incoming differs from what is 94 // returned by Put above. Note that we should allow updates for a given 95 // repository. 96 97 return desc, lbs.linkBlob(ctx, desc) 98 } 99 100 // createOptions is a collection of blob creation modifiers relevant to general 101 // blob storage intended to be configured by the BlobCreateOption.Apply method. 102 type createOptions struct { 103 Mount struct { 104 ShouldMount bool 105 From reference.Canonical 106 } 107 } 108 109 type optionFunc func(interface{}) error 110 111 func (f optionFunc) Apply(v interface{}) error { 112 return f(v) 113 } 114 115 // WithMountFrom returns a BlobCreateOption which designates that the blob should be 116 // mounted from the given canonical reference. 117 func WithMountFrom(ref reference.Canonical) distribution.BlobCreateOption { 118 return optionFunc(func(v interface{}) error { 119 opts, ok := v.(*createOptions) 120 if !ok { 121 return fmt.Errorf("unexpected options type: %T", v) 122 } 123 124 opts.Mount.ShouldMount = true 125 opts.Mount.From = ref 126 127 return nil 128 }) 129 } 130 131 // Writer begins a blob write session, returning a handle. 132 func (lbs *linkedBlobStore) Create(ctx context.Context, options ...distribution.BlobCreateOption) (distribution.BlobWriter, error) { 133 context.GetLogger(ctx).Debug("(*linkedBlobStore).Writer") 134 135 var opts createOptions 136 137 for _, option := range options { 138 err := option.Apply(&opts) 139 if err != nil { 140 return nil, err 141 } 142 } 143 144 if opts.Mount.ShouldMount { 145 desc, err := lbs.mount(ctx, opts.Mount.From, opts.Mount.From.Digest()) 146 if err == nil { 147 // Mount successful, no need to initiate an upload session 148 return nil, distribution.ErrBlobMounted{From: opts.Mount.From, Descriptor: desc} 149 } 150 } 151 152 uuid := uuid.Generate().String() 153 startedAt := time.Now().UTC() 154 155 path, err := pathFor(uploadDataPathSpec{ 156 name: lbs.repository.Name().Name(), 157 id: uuid, 158 }) 159 160 if err != nil { 161 return nil, err 162 } 163 164 startedAtPath, err := pathFor(uploadStartedAtPathSpec{ 165 name: lbs.repository.Name().Name(), 166 id: uuid, 167 }) 168 169 if err != nil { 170 return nil, err 171 } 172 173 // Write a startedat file for this upload 174 if err := lbs.blobStore.driver.PutContent(ctx, startedAtPath, []byte(startedAt.Format(time.RFC3339))); err != nil { 175 return nil, err 176 } 177 178 return lbs.newBlobUpload(ctx, uuid, path, startedAt) 179 } 180 181 func (lbs *linkedBlobStore) Resume(ctx context.Context, id string) (distribution.BlobWriter, error) { 182 context.GetLogger(ctx).Debug("(*linkedBlobStore).Resume") 183 184 startedAtPath, err := pathFor(uploadStartedAtPathSpec{ 185 name: lbs.repository.Name().Name(), 186 id: id, 187 }) 188 189 if err != nil { 190 return nil, err 191 } 192 193 startedAtBytes, err := lbs.blobStore.driver.GetContent(ctx, startedAtPath) 194 if err != nil { 195 switch err := err.(type) { 196 case driver.PathNotFoundError: 197 return nil, distribution.ErrBlobUploadUnknown 198 default: 199 return nil, err 200 } 201 } 202 203 startedAt, err := time.Parse(time.RFC3339, string(startedAtBytes)) 204 if err != nil { 205 return nil, err 206 } 207 208 path, err := pathFor(uploadDataPathSpec{ 209 name: lbs.repository.Name().Name(), 210 id: id, 211 }) 212 213 if err != nil { 214 return nil, err 215 } 216 217 return lbs.newBlobUpload(ctx, id, path, startedAt) 218 } 219 220 func (lbs *linkedBlobStore) Delete(ctx context.Context, dgst digest.Digest) error { 221 if !lbs.deleteEnabled { 222 return distribution.ErrUnsupported 223 } 224 225 // Ensure the blob is available for deletion 226 _, err := lbs.blobAccessController.Stat(ctx, dgst) 227 if err != nil { 228 return err 229 } 230 231 err = lbs.blobAccessController.Clear(ctx, dgst) 232 if err != nil { 233 return err 234 } 235 236 return nil 237 } 238 239 func (lbs *linkedBlobStore) mount(ctx context.Context, sourceRepo reference.Named, dgst digest.Digest) (distribution.Descriptor, error) { 240 repo, err := lbs.registry.Repository(ctx, sourceRepo) 241 if err != nil { 242 return distribution.Descriptor{}, err 243 } 244 stat, err := repo.Blobs(ctx).Stat(ctx, dgst) 245 if err != nil { 246 return distribution.Descriptor{}, err 247 } 248 249 desc := distribution.Descriptor{ 250 Size: stat.Size, 251 252 // NOTE(stevvooe): The central blob store firewalls media types from 253 // other users. The caller should look this up and override the value 254 // for the specific repository. 255 MediaType: "application/octet-stream", 256 Digest: dgst, 257 } 258 return desc, lbs.linkBlob(ctx, desc) 259 } 260 261 // newBlobUpload allocates a new upload controller with the given state. 262 func (lbs *linkedBlobStore) newBlobUpload(ctx context.Context, uuid, path string, startedAt time.Time) (distribution.BlobWriter, error) { 263 fw, err := newFileWriter(ctx, lbs.driver, path) 264 if err != nil { 265 return nil, err 266 } 267 268 bw := &blobWriter{ 269 blobStore: lbs, 270 id: uuid, 271 startedAt: startedAt, 272 digester: digest.Canonical.New(), 273 bufferedFileWriter: *fw, 274 resumableDigestEnabled: lbs.resumableDigestEnabled, 275 } 276 277 return bw, nil 278 } 279 280 // linkBlob links a valid, written blob into the registry under the named 281 // repository for the upload controller. 282 func (lbs *linkedBlobStore) linkBlob(ctx context.Context, canonical distribution.Descriptor, aliases ...digest.Digest) error { 283 dgsts := append([]digest.Digest{canonical.Digest}, aliases...) 284 285 // TODO(stevvooe): Need to write out mediatype for only canonical hash 286 // since we don't care about the aliases. They are generally unused except 287 // for tarsum but those versions don't care about mediatype. 288 289 // Don't make duplicate links. 290 seenDigests := make(map[digest.Digest]struct{}, len(dgsts)) 291 292 // only use the first link 293 linkPathFn := lbs.linkPathFns[0] 294 295 for _, dgst := range dgsts { 296 if _, seen := seenDigests[dgst]; seen { 297 continue 298 } 299 seenDigests[dgst] = struct{}{} 300 301 blobLinkPath, err := linkPathFn(lbs.repository.Name().Name(), dgst) 302 if err != nil { 303 return err 304 } 305 306 if err := lbs.blobStore.link(ctx, blobLinkPath, canonical.Digest); err != nil { 307 return err 308 } 309 } 310 311 return nil 312 } 313 314 type linkedBlobStatter struct { 315 *blobStore 316 repository distribution.Repository 317 318 // linkPathFns specifies one or more path functions allowing one to 319 // control the repository blob link set to which the blob store 320 // dispatches. This is required because manifest and layer blobs have not 321 // yet been fully merged. At some point, this functionality should be 322 // removed an the blob links folder should be merged. The first entry is 323 // treated as the "canonical" link location and will be used for writes. 324 linkPathFns []linkPathFunc 325 } 326 327 var _ distribution.BlobDescriptorService = &linkedBlobStatter{} 328 329 func (lbs *linkedBlobStatter) Stat(ctx context.Context, dgst digest.Digest) (distribution.Descriptor, error) { 330 var ( 331 resolveErr error 332 target digest.Digest 333 ) 334 335 // try the many link path functions until we get success or an error that 336 // is not PathNotFoundError. 337 for _, linkPathFn := range lbs.linkPathFns { 338 var err error 339 target, err = lbs.resolveWithLinkFunc(ctx, dgst, linkPathFn) 340 341 if err == nil { 342 break // success! 343 } 344 345 switch err := err.(type) { 346 case driver.PathNotFoundError: 347 resolveErr = distribution.ErrBlobUnknown // move to the next linkPathFn, saving the error 348 default: 349 return distribution.Descriptor{}, err 350 } 351 } 352 353 if resolveErr != nil { 354 return distribution.Descriptor{}, resolveErr 355 } 356 357 if target != dgst { 358 // Track when we are doing cross-digest domain lookups. ie, sha512 to sha256. 359 context.GetLogger(ctx).Warnf("looking up blob with canonical target: %v -> %v", dgst, target) 360 } 361 362 // TODO(stevvooe): Look up repository local mediatype and replace that on 363 // the returned descriptor. 364 365 return lbs.blobStore.statter.Stat(ctx, target) 366 } 367 368 func (lbs *linkedBlobStatter) Clear(ctx context.Context, dgst digest.Digest) (err error) { 369 // clear any possible existence of a link described in linkPathFns 370 for _, linkPathFn := range lbs.linkPathFns { 371 blobLinkPath, err := linkPathFn(lbs.repository.Name().Name(), dgst) 372 if err != nil { 373 return err 374 } 375 376 err = lbs.blobStore.driver.Delete(ctx, blobLinkPath) 377 if err != nil { 378 switch err := err.(type) { 379 case driver.PathNotFoundError: 380 continue // just ignore this error and continue 381 default: 382 return err 383 } 384 } 385 } 386 387 return nil 388 } 389 390 // resolveTargetWithFunc allows us to read a link to a resource with different 391 // linkPathFuncs to let us try a few different paths before returning not 392 // found. 393 func (lbs *linkedBlobStatter) resolveWithLinkFunc(ctx context.Context, dgst digest.Digest, linkPathFn linkPathFunc) (digest.Digest, error) { 394 blobLinkPath, err := linkPathFn(lbs.repository.Name().Name(), dgst) 395 if err != nil { 396 return "", err 397 } 398 399 return lbs.blobStore.readlink(ctx, blobLinkPath) 400 } 401 402 func (lbs *linkedBlobStatter) SetDescriptor(ctx context.Context, dgst digest.Digest, desc distribution.Descriptor) error { 403 // The canonical descriptor for a blob is set at the commit phase of upload 404 return nil 405 } 406 407 // blobLinkPath provides the path to the blob link, also known as layers. 408 func blobLinkPath(name string, dgst digest.Digest) (string, error) { 409 return pathFor(layerLinkPathSpec{name: name, digest: dgst}) 410 } 411 412 // manifestRevisionLinkPath provides the path to the manifest revision link. 413 func manifestRevisionLinkPath(name string, dgst digest.Digest) (string, error) { 414 return pathFor(manifestRevisionLinkPathSpec{name: name, revision: dgst}) 415 }