package server import ( "fmt" "net/http" "github.com/docker/distribution" "github.com/docker/distribution/context" "github.com/docker/distribution/digest" "github.com/docker/distribution/reference" ) // errorBlobStore wraps a distribution.BlobStore for a particular repo. // before delegating, it ensures auth completed and there were no errors relevant to the repo. type errorBlobStore struct { store distribution.BlobStore repo *repository } var _ distribution.BlobStore = &errorBlobStore{} func (r *errorBlobStore) Stat(ctx context.Context, dgst digest.Digest) (distribution.Descriptor, error) { if err := r.repo.checkPendingErrors(ctx); err != nil { return distribution.Descriptor{}, err } return r.store.Stat(WithRepository(ctx, r.repo), dgst) } func (r *errorBlobStore) Get(ctx context.Context, dgst digest.Digest) ([]byte, error) { if err := r.repo.checkPendingErrors(ctx); err != nil { return nil, err } return r.store.Get(WithRepository(ctx, r.repo), dgst) } func (r *errorBlobStore) Open(ctx context.Context, dgst digest.Digest) (distribution.ReadSeekCloser, error) { if err := r.repo.checkPendingErrors(ctx); err != nil { return nil, err } return r.store.Open(WithRepository(ctx, r.repo), dgst) } func (r *errorBlobStore) Put(ctx context.Context, mediaType string, p []byte) (distribution.Descriptor, error) { if err := r.repo.checkPendingErrors(ctx); err != nil { return distribution.Descriptor{}, err } return r.store.Put(WithRepository(ctx, r.repo), mediaType, p) } func (r *errorBlobStore) Create(ctx context.Context, options ...distribution.BlobCreateOption) (distribution.BlobWriter, error) { if err := r.repo.checkPendingErrors(ctx); err != nil { return nil, err } ctx = WithRepository(ctx, r.repo) opts, err := effectiveCreateOptions(options) if err != nil { return nil, err } err = checkPendingCrossMountErrors(ctx, opts) if err != nil { context.GetLogger(ctx).Infof("disabling cross-repo mount because of an error: %v", err) options = append(options, guardCreateOptions{DisableCrossMount: true}) } else if !opts.Mount.ShouldMount { options = append(options, guardCreateOptions{DisableCrossMount: true}) } else { context.GetLogger(ctx).Debugf("attempting cross-repo mount") options = append(options, statCrossMountCreateOptions{ ctx: ctx, destRepo: r.repo, }) } return r.store.Create(ctx, options...) } func (r *errorBlobStore) Resume(ctx context.Context, id string) (distribution.BlobWriter, error) { if err := r.repo.checkPendingErrors(ctx); err != nil { return nil, err } return r.store.Resume(WithRepository(ctx, r.repo), id) } func (r *errorBlobStore) ServeBlob(ctx context.Context, w http.ResponseWriter, req *http.Request, dgst digest.Digest) error { if err := r.repo.checkPendingErrors(ctx); err != nil { return err } return r.store.ServeBlob(WithRepository(ctx, r.repo), w, req, dgst) } func (r *errorBlobStore) Delete(ctx context.Context, dgst digest.Digest) error { if err := r.repo.checkPendingErrors(ctx); err != nil { return err } return r.store.Delete(WithRepository(ctx, r.repo), dgst) } // checkPendingCrossMountErrors returns true if a cross-repo mount has been requested with given create // options. If requested and there are pending authorization errors for source repository, the error will be // returned. Cross-repo mount must not be allowed in case of error. func checkPendingCrossMountErrors(ctx context.Context, opts *distribution.CreateOptions) error { if !opts.Mount.ShouldMount { return nil } namespace, name, err := getNamespaceName(opts.Mount.From.Name()) if err != nil { return err } return checkPendingErrors(context.GetLogger(ctx), ctx, namespace, name) } // guardCreateOptions ensures the expected options type is passed, and optionally disables cross mounting type guardCreateOptions struct { DisableCrossMount bool } var _ distribution.BlobCreateOption = guardCreateOptions{} func (f guardCreateOptions) Apply(v interface{}) error { opts, ok := v.(*distribution.CreateOptions) if !ok { return fmt.Errorf("Unexpected create options: %#v", v) } if f.DisableCrossMount { opts.Mount.ShouldMount = false } return nil } // statCrossMountCreateOptions ensures the expected options type is passed, and optionally pre-fills the cross-mount stat info type statCrossMountCreateOptions struct { ctx context.Context destRepo *repository } var _ distribution.BlobCreateOption = statCrossMountCreateOptions{} func (f statCrossMountCreateOptions) Apply(v interface{}) error { opts, ok := v.(*distribution.CreateOptions) if !ok { return fmt.Errorf("Unexpected create options: %#v", v) } if !opts.Mount.ShouldMount { return nil } desc, err := statSourceRepository(f.ctx, f.destRepo, opts.Mount.From, opts.Mount.From.Digest()) if err != nil { context.GetLogger(f.ctx).Infof("cannot mount blob %s from repository %s: %v - disabling cross-repo mount", opts.Mount.From.Digest().String(), opts.Mount.From.Name()) opts.Mount.ShouldMount = false return nil } opts.Mount.Stat = &desc return nil } func statSourceRepository( ctx context.Context, destRepo *repository, sourceRepoName reference.Named, dgst digest.Digest, ) (desc distribution.Descriptor, err error) { upstreamRepo, err := dockerRegistry.Repository(ctx, sourceRepoName) if err != nil { return distribution.Descriptor{}, err } namespace, name, err := getNamespaceName(sourceRepoName.Name()) if err != nil { return distribution.Descriptor{}, err } repo := *destRepo repo.namespace = namespace repo.name = name repo.Repository = upstreamRepo return repo.Blobs(ctx).Stat(ctx, dgst) }