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  }