github.com/docker/cnab-to-oci@v0.3.0-beta4/remotes/mount.go (about)

     1  package remotes
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"io"
     7  	"strings"
     8  
     9  	"github.com/containerd/containerd/content"
    10  	"github.com/containerd/containerd/errdefs"
    11  	"github.com/containerd/containerd/images"
    12  	"github.com/containerd/containerd/remotes"
    13  	"github.com/docker/distribution/reference"
    14  	ocischemav1 "github.com/opencontainers/image-spec/specs-go/v1"
    15  	"github.com/pkg/errors"
    16  )
    17  
    18  const (
    19  	// labelDistributionSource describes the source blob comes from.
    20  	// This label comes from containerd: https://github.com/containerd/containerd/blob/master/remotes/docker/handler.go#L35
    21  	labelDistributionSource = "containerd.io/distribution.source"
    22  )
    23  
    24  func newDescriptorCopier(ctx context.Context, resolver remotes.Resolver,
    25  	sourceFetcher remotes.Fetcher, targetRepo string,
    26  	eventNotifier eventNotifier, originalSource reference.Named) (*descriptorCopier, error) {
    27  	destPusher, err := resolver.Pusher(ctx, targetRepo)
    28  	if err != nil {
    29  		return nil, err
    30  	}
    31  	return &descriptorCopier{
    32  		sourceFetcher:  sourceFetcher,
    33  		targetPusher:   destPusher,
    34  		eventNotifier:  eventNotifier,
    35  		resolver:       resolver,
    36  		originalSource: originalSource,
    37  	}, nil
    38  }
    39  
    40  type descriptorCopier struct {
    41  	sourceFetcher  remotes.Fetcher
    42  	targetPusher   remotes.Pusher
    43  	eventNotifier  eventNotifier
    44  	resolver       remotes.Resolver
    45  	originalSource reference.Named
    46  }
    47  
    48  func (h *descriptorCopier) Handle(ctx context.Context, desc *descriptorProgress) (retErr error) {
    49  	ctx, cancel := context.WithCancel(ctx)
    50  	defer cancel()
    51  	if len(desc.URLs) > 0 {
    52  		desc.markDone()
    53  		desc.setAction("Skip (foreign layer)")
    54  		return nil
    55  	}
    56  	desc.setAction("Copy")
    57  	h.eventNotifier.reportProgress(nil)
    58  	defer func() {
    59  		if retErr != nil {
    60  			desc.setError(retErr)
    61  		}
    62  		h.eventNotifier.reportProgress(retErr)
    63  	}()
    64  	writer, err := pushWithAnnotation(ctx, h.targetPusher, h.originalSource, desc.Descriptor)
    65  	if errors.Cause(err) == errdefs.ErrAlreadyExists {
    66  		desc.markDone()
    67  		if strings.Contains(err.Error(), "mounted") {
    68  			desc.setAction("Mounted")
    69  		}
    70  		return nil
    71  	}
    72  	if err != nil {
    73  		return err
    74  	}
    75  	defer writer.Close()
    76  	reader, err := h.sourceFetcher.Fetch(ctx, desc.Descriptor)
    77  	if err != nil {
    78  		return err
    79  	}
    80  	defer reader.Close()
    81  	err = content.Copy(ctx, writer, reader, desc.Size, desc.Digest)
    82  	if errors.Cause(err) == errdefs.ErrAlreadyExists {
    83  		err = nil
    84  	}
    85  	if err == nil {
    86  		desc.markDone()
    87  	}
    88  	return err
    89  }
    90  
    91  func pushWithAnnotation(ctx context.Context, pusher remotes.Pusher, ref reference.Named, desc ocischemav1.Descriptor) (content.Writer, error) {
    92  	// Add the distribution source annotation to help containerd
    93  	// mount instead of push when possible.
    94  	repo := fmt.Sprintf("%s.%s", labelDistributionSource, reference.Domain(ref))
    95  	desc.Annotations = map[string]string{
    96  		repo: reference.FamiliarName(ref),
    97  	}
    98  	return pusher.Push(ctx, desc)
    99  }
   100  
   101  func isManifest(mediaType string) bool {
   102  	return mediaType == images.MediaTypeDockerSchema1Manifest ||
   103  		mediaType == images.MediaTypeDockerSchema2Manifest ||
   104  		mediaType == images.MediaTypeDockerSchema2ManifestList ||
   105  		mediaType == ocischemav1.MediaTypeImageIndex ||
   106  		mediaType == ocischemav1.MediaTypeImageManifest
   107  }
   108  
   109  type imageContentProvider struct {
   110  	fetcher remotes.Fetcher
   111  }
   112  
   113  func (p *imageContentProvider) ReaderAt(ctx context.Context, desc ocischemav1.Descriptor) (content.ReaderAt, error) {
   114  	rc, err := p.fetcher.Fetch(ctx, desc)
   115  	if err != nil {
   116  		return nil, err
   117  	}
   118  	return &remoteReaderAt{ReadCloser: rc, currentOffset: 0, size: desc.Size}, nil
   119  }
   120  
   121  type remoteReaderAt struct {
   122  	io.ReadCloser
   123  	currentOffset int64
   124  	size          int64
   125  }
   126  
   127  func (r *remoteReaderAt) Size() int64 {
   128  	return r.size
   129  }
   130  
   131  func (r *remoteReaderAt) ReadAt(p []byte, off int64) (int, error) {
   132  	if off != r.currentOffset {
   133  		return 0, fmt.Errorf("at the moment this reader only supports offset at %d, requested offset was %d", r.currentOffset, off)
   134  	}
   135  	n, err := r.Read(p)
   136  	r.currentOffset += int64(n)
   137  	if err == io.EOF && n == len(p) {
   138  		return n, nil
   139  	}
   140  	if err != nil || n == len(p) {
   141  		return n, err
   142  	}
   143  	n2, err := r.ReadAt(p[n:], r.currentOffset)
   144  	n += n2
   145  	return n, err
   146  }
   147  
   148  type descriptorContentHandler struct {
   149  	descriptorCopier *descriptorCopier
   150  	targetRepo       string
   151  }
   152  
   153  func (h *descriptorContentHandler) createCopyTask(ctx context.Context, descProgress *descriptorProgress) (func(ctx context.Context) error, error) {
   154  	copyOrMountWorkItem := func(ctx context.Context) error {
   155  		return h.descriptorCopier.Handle(ctx, descProgress)
   156  	}
   157  	if !isManifest(descProgress.MediaType) {
   158  		return copyOrMountWorkItem, nil
   159  	}
   160  	_, _, err := h.descriptorCopier.resolver.Resolve(ctx, fmt.Sprintf("%s@%s", h.targetRepo, descProgress.Digest))
   161  	if err == nil {
   162  		descProgress.setAction("Skip (already present)")
   163  		descProgress.markDone()
   164  		return nil, errdefs.ErrAlreadyExists
   165  	}
   166  	return copyOrMountWorkItem, nil
   167  }
   168  
   169  type manifestWalker struct {
   170  	getChildren    images.HandlerFunc
   171  	eventNotifier  eventNotifier
   172  	scheduler      scheduler
   173  	progress       *progress
   174  	contentHandler *descriptorContentHandler
   175  }
   176  
   177  func newManifestWalker(
   178  	eventNotifier eventNotifier,
   179  	scheduler scheduler,
   180  	progress *progress,
   181  	descriptorContentHandler *descriptorContentHandler) *manifestWalker {
   182  	sourceFetcher := descriptorContentHandler.descriptorCopier.sourceFetcher
   183  	return &manifestWalker{
   184  		eventNotifier:  eventNotifier,
   185  		getChildren:    images.ChildrenHandler(&imageContentProvider{sourceFetcher}),
   186  		scheduler:      scheduler,
   187  		progress:       progress,
   188  		contentHandler: descriptorContentHandler,
   189  	}
   190  }
   191  
   192  func (w *manifestWalker) walk(ctx context.Context, desc ocischemav1.Descriptor, parent *descriptorProgress) promise {
   193  	select {
   194  	case <-ctx.Done():
   195  		return newPromise(w.scheduler, ctx)
   196  	default:
   197  	}
   198  	descProgress := &descriptorProgress{
   199  		Descriptor: desc,
   200  	}
   201  	if parent != nil {
   202  		parent.addChild(descProgress)
   203  	} else {
   204  		w.progress.addRoot(descProgress)
   205  	}
   206  	copyOrMountWorkItem, err := w.contentHandler.createCopyTask(ctx, descProgress)
   207  	if errors.Cause(err) == errdefs.ErrAlreadyExists {
   208  		w.eventNotifier.reportProgress(nil)
   209  		return newPromise(w.scheduler, doneDependency{})
   210  	}
   211  	if err != nil {
   212  		w.eventNotifier.reportProgress(err)
   213  		return newPromise(w.scheduler, failedDependency{err: err})
   214  	}
   215  	childrenPromise := scheduleAndUnwrap(w.scheduler, func(ctx context.Context) (dependency, error) {
   216  		var deps []dependency
   217  		children, err := w.getChildren.Handle(ctx, desc)
   218  		if err != nil {
   219  			return nil, err
   220  		}
   221  		for _, c := range children {
   222  			dep := w.walk(ctx, c, descProgress)
   223  			deps = append(deps, dep)
   224  		}
   225  		return newPromise(w.scheduler, whenAll(deps)), nil
   226  	})
   227  
   228  	return childrenPromise.then(copyOrMountWorkItem)
   229  }
   230  
   231  type eventNotifier func(eventType FixupEventType, message string, err error)
   232  
   233  func (n eventNotifier) reportProgress(err error) {
   234  	n(FixupEventTypeProgress, "", err)
   235  }