package storage import ( "fmt" ctxu "github.com/docker/distribution/context" "github.com/docker/distribution/digest" "github.com/docker/distribution/storagedriver" "golang.org/x/net/context" ) // 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 { *registry 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.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(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(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(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 { ctxu.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(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 func exists(driver storagedriver.StorageDriver, path string) (bool, error) { if _, err := driver.Stat(path); err != nil { switch err := err.(type) { case storagedriver.PathNotFoundError: return false, nil default: return false, err } } return true, nil }