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 }