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