160 lines
		
	
	
		
			4.1 KiB
		
	
	
	
		
			Go
		
	
	
			
		
		
	
	
			160 lines
		
	
	
		
			4.1 KiB
		
	
	
	
		
			Go
		
	
	
| package storage
 | |
| 
 | |
| import (
 | |
| 	"fmt"
 | |
| 
 | |
| 	"github.com/docker/distribution/context"
 | |
| 	"github.com/docker/distribution/digest"
 | |
| 	storagedriver "github.com/docker/distribution/registry/storage/driver"
 | |
| )
 | |
| 
 | |
| // TODO(stevvooe): Currently, the blobStore implementation used by the
 | |
| // manifest store. The layer store should be refactored to better leverage the
 | |
| // blobStore, reducing duplicated code.
 | |
| 
 | |
| // blobStore implements a generalized blob store over a driver, supporting the
 | |
| // read side and link management. This object is intentionally a leaky
 | |
| // abstraction, providing utility methods that support creating and traversing
 | |
| // backend links.
 | |
| type blobStore struct {
 | |
| 	driver storagedriver.StorageDriver
 | |
| 	pm     *pathMapper
 | |
| 	ctx    context.Context
 | |
| }
 | |
| 
 | |
| // exists reports whether or not the path exists. If the driver returns error
 | |
| // other than storagedriver.PathNotFound, an error may be returned.
 | |
| func (bs *blobStore) exists(dgst digest.Digest) (bool, error) {
 | |
| 	path, err := bs.path(dgst)
 | |
| 
 | |
| 	if err != nil {
 | |
| 		return false, err
 | |
| 	}
 | |
| 
 | |
| 	ok, err := exists(bs.ctx, bs.driver, path)
 | |
| 	if err != nil {
 | |
| 		return false, err
 | |
| 	}
 | |
| 
 | |
| 	return ok, nil
 | |
| }
 | |
| 
 | |
| // get retrieves the blob by digest, returning it a byte slice. This should
 | |
| // only be used for small objects.
 | |
| func (bs *blobStore) get(dgst digest.Digest) ([]byte, error) {
 | |
| 	bp, err := bs.path(dgst)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	return bs.driver.GetContent(bs.ctx, bp)
 | |
| }
 | |
| 
 | |
| // link links the path to the provided digest by writing the digest into the
 | |
| // target file.
 | |
| func (bs *blobStore) link(path string, dgst digest.Digest) error {
 | |
| 	if exists, err := bs.exists(dgst); err != nil {
 | |
| 		return err
 | |
| 	} else if !exists {
 | |
| 		return fmt.Errorf("cannot link non-existent blob")
 | |
| 	}
 | |
| 
 | |
| 	// The contents of the "link" file are the exact string contents of the
 | |
| 	// digest, which is specified in that package.
 | |
| 	return bs.driver.PutContent(bs.ctx, path, []byte(dgst))
 | |
| }
 | |
| 
 | |
| // linked reads the link at path and returns the content.
 | |
| func (bs *blobStore) linked(path string) ([]byte, error) {
 | |
| 	linked, err := bs.readlink(path)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	return bs.get(linked)
 | |
| }
 | |
| 
 | |
| // readlink returns the linked digest at path.
 | |
| func (bs *blobStore) readlink(path string) (digest.Digest, error) {
 | |
| 	content, err := bs.driver.GetContent(bs.ctx, path)
 | |
| 	if err != nil {
 | |
| 		return "", err
 | |
| 	}
 | |
| 
 | |
| 	linked, err := digest.ParseDigest(string(content))
 | |
| 	if err != nil {
 | |
| 		return "", err
 | |
| 	}
 | |
| 
 | |
| 	if exists, err := bs.exists(linked); err != nil {
 | |
| 		return "", err
 | |
| 	} else if !exists {
 | |
| 		return "", fmt.Errorf("link %q invalid: blob %s does not exist", path, linked)
 | |
| 	}
 | |
| 
 | |
| 	return linked, nil
 | |
| }
 | |
| 
 | |
| // resolve reads the digest link at path and returns the blob store link.
 | |
| func (bs *blobStore) resolve(path string) (string, error) {
 | |
| 	dgst, err := bs.readlink(path)
 | |
| 	if err != nil {
 | |
| 		return "", err
 | |
| 	}
 | |
| 
 | |
| 	return bs.path(dgst)
 | |
| }
 | |
| 
 | |
| // put stores the content p in the blob store, calculating the digest. If the
 | |
| // content is already present, only the digest will be returned. This should
 | |
| // only be used for small objects, such as manifests.
 | |
| func (bs *blobStore) put(p []byte) (digest.Digest, error) {
 | |
| 	dgst, err := digest.FromBytes(p)
 | |
| 	if err != nil {
 | |
| 		context.GetLogger(bs.ctx).Errorf("error digesting content: %v, %s", err, string(p))
 | |
| 		return "", err
 | |
| 	}
 | |
| 
 | |
| 	bp, err := bs.path(dgst)
 | |
| 	if err != nil {
 | |
| 		return "", err
 | |
| 	}
 | |
| 
 | |
| 	// If the content already exists, just return the digest.
 | |
| 	if exists, err := bs.exists(dgst); err != nil {
 | |
| 		return "", err
 | |
| 	} else if exists {
 | |
| 		return dgst, nil
 | |
| 	}
 | |
| 
 | |
| 	return dgst, bs.driver.PutContent(bs.ctx, bp, p)
 | |
| }
 | |
| 
 | |
| // path returns the canonical path for the blob identified by digest. The blob
 | |
| // may or may not exist.
 | |
| func (bs *blobStore) path(dgst digest.Digest) (string, error) {
 | |
| 	bp, err := bs.pm.path(blobDataPathSpec{
 | |
| 		digest: dgst,
 | |
| 	})
 | |
| 
 | |
| 	if err != nil {
 | |
| 		return "", err
 | |
| 	}
 | |
| 
 | |
| 	return bp, nil
 | |
| }
 | |
| 
 | |
| // exists provides a utility method to test whether or not a path exists
 | |
| func exists(ctx context.Context, driver storagedriver.StorageDriver, path string) (bool, error) {
 | |
| 	if _, err := driver.Stat(ctx, path); err != nil {
 | |
| 		switch err := err.(type) {
 | |
| 		case storagedriver.PathNotFoundError:
 | |
| 			return false, nil
 | |
| 		default:
 | |
| 			return false, err
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return true, nil
 | |
| }
 |