270 lines
		
	
	
		
			6.5 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			270 lines
		
	
	
		
			6.5 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| /*
 | |
|    Copyright The containerd Authors.
 | |
| 
 | |
|    Licensed under the Apache License, Version 2.0 (the "License");
 | |
|    you may not use this file except in compliance with the License.
 | |
|    You may obtain a copy of the License at
 | |
| 
 | |
|        http://www.apache.org/licenses/LICENSE-2.0
 | |
| 
 | |
|    Unless required by applicable law or agreed to in writing, software
 | |
|    distributed under the License is distributed on an "AS IS" BASIS,
 | |
|    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | |
|    See the License for the specific language governing permissions and
 | |
|    limitations under the License.
 | |
| */
 | |
| 
 | |
| package local
 | |
| 
 | |
| import (
 | |
| 	"context"
 | |
| 	"fmt"
 | |
| 	"sync"
 | |
| 	"time"
 | |
| 
 | |
| 	"github.com/containerd/containerd/content"
 | |
| 	"github.com/containerd/containerd/errdefs"
 | |
| 	"github.com/containerd/containerd/images"
 | |
| 	"github.com/containerd/containerd/pkg/transfer"
 | |
| 	"github.com/containerd/containerd/platforms"
 | |
| 	"github.com/containerd/containerd/remotes"
 | |
| 	"github.com/opencontainers/go-digest"
 | |
| 	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
 | |
| )
 | |
| 
 | |
| func (ts *localTransferService) push(ctx context.Context, ig transfer.ImageGetter, p transfer.ImagePusher, tops *transfer.Config) error {
 | |
| 	/*
 | |
| 		// TODO: Platform matching
 | |
| 		if pushCtx.PlatformMatcher == nil {
 | |
| 			if len(pushCtx.Platforms) > 0 {
 | |
| 				var ps []ocispec.Platform
 | |
| 				for _, platform := range pushCtx.Platforms {
 | |
| 					p, err := platforms.Parse(platform)
 | |
| 					if err != nil {
 | |
| 						return fmt.Errorf("invalid platform %s: %w", platform, err)
 | |
| 					}
 | |
| 					ps = append(ps, p)
 | |
| 				}
 | |
| 				pushCtx.PlatformMatcher = platforms.Any(ps...)
 | |
| 			} else {
 | |
| 				pushCtx.PlatformMatcher = platforms.All
 | |
| 			}
 | |
| 		}
 | |
| 	*/
 | |
| 	matcher := platforms.All
 | |
| 	// Filter push
 | |
| 
 | |
| 	img, err := ig.Get(ctx, ts.images)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	if tops.Progress != nil {
 | |
| 		tops.Progress(transfer.Progress{
 | |
| 			Event: fmt.Sprintf("Pushing to %s", p),
 | |
| 		})
 | |
| 		tops.Progress(transfer.Progress{
 | |
| 			Event: "pushing content",
 | |
| 			Name:  img.Name,
 | |
| 			//Digest: img.Target.Digest.String(),
 | |
| 		})
 | |
| 	}
 | |
| 
 | |
| 	var pusher remotes.Pusher
 | |
| 	pusher, err = p.Pusher(ctx, img.Target)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	var wrapper func(images.Handler) images.Handler
 | |
| 
 | |
| 	ctx, cancel := context.WithCancel(ctx)
 | |
| 	if tops.Progress != nil {
 | |
| 		progressTracker := NewProgressTracker(img.Name, "uploading") //Pass in first name as root
 | |
| 
 | |
| 		p := newProgressPusher(pusher, progressTracker)
 | |
| 		go progressTracker.HandleProgress(ctx, tops.Progress, p)
 | |
| 		defer progressTracker.Wait()
 | |
| 		wrapper = p.WrapHandler
 | |
| 		pusher = p
 | |
| 	}
 | |
| 	defer cancel()
 | |
| 
 | |
| 	// TODO: Add handler to track parents
 | |
| 	/*
 | |
| 		// TODO: Add handlers
 | |
| 		if len(pushCtx.BaseHandlers) > 0 {
 | |
| 			wrapper = func(h images.Handler) images.Handler {
 | |
| 				h = images.Handlers(append(pushCtx.BaseHandlers, h)...)
 | |
| 				if pushCtx.HandlerWrapper != nil {
 | |
| 					h = pushCtx.HandlerWrapper(h)
 | |
| 				}
 | |
| 				return h
 | |
| 			}
 | |
| 		} else if pushCtx.HandlerWrapper != nil {
 | |
| 			wrapper = pushCtx.HandlerWrapper
 | |
| 		}
 | |
| 	*/
 | |
| 	if err := remotes.PushContent(ctx, pusher, img.Target, ts.content, ts.limiterU, matcher, wrapper); err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	if tops.Progress != nil {
 | |
| 		tops.Progress(transfer.Progress{
 | |
| 			Event: "pushed content",
 | |
| 			Name:  img.Name,
 | |
| 			//Digest: img.Target.Digest.String(),
 | |
| 		})
 | |
| 		tops.Progress(transfer.Progress{
 | |
| 			Event: fmt.Sprintf("Completed push to %s", p),
 | |
| 		})
 | |
| 	}
 | |
| 
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| type progressPusher struct {
 | |
| 	remotes.Pusher
 | |
| 	progress *ProgressTracker
 | |
| 
 | |
| 	status *pushStatus
 | |
| }
 | |
| 
 | |
| type pushStatus struct {
 | |
| 	l        sync.Mutex
 | |
| 	statuses map[string]content.Status
 | |
| 	complete map[digest.Digest]struct{}
 | |
| }
 | |
| 
 | |
| func newProgressPusher(pusher remotes.Pusher, progress *ProgressTracker) *progressPusher {
 | |
| 	return &progressPusher{
 | |
| 		Pusher:   pusher,
 | |
| 		progress: progress,
 | |
| 		status: &pushStatus{
 | |
| 			statuses: map[string]content.Status{},
 | |
| 			complete: map[digest.Digest]struct{}{},
 | |
| 		},
 | |
| 	}
 | |
| 
 | |
| }
 | |
| 
 | |
| func (p *progressPusher) WrapHandler(h images.Handler) images.Handler {
 | |
| 	return images.HandlerFunc(func(ctx context.Context, desc ocispec.Descriptor) (subdescs []ocispec.Descriptor, err error) {
 | |
| 		p.progress.Add(desc)
 | |
| 		subdescs, err = h.Handle(ctx, desc)
 | |
| 		p.progress.AddChildren(desc, subdescs)
 | |
| 		return
 | |
| 	})
 | |
| }
 | |
| 
 | |
| func (p *progressPusher) Push(ctx context.Context, d ocispec.Descriptor) (content.Writer, error) {
 | |
| 	ref := remotes.MakeRefKey(ctx, d)
 | |
| 	p.status.add(ref, d)
 | |
| 	cw, err := p.Pusher.Push(ctx, d)
 | |
| 	if err != nil {
 | |
| 		if errdefs.IsAlreadyExists(err) {
 | |
| 			p.progress.MarkExists(d)
 | |
| 			p.status.markComplete(ref, d)
 | |
| 		}
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	return &progressWriter{
 | |
| 		Writer:   cw,
 | |
| 		ref:      ref,
 | |
| 		desc:     d,
 | |
| 		status:   p.status,
 | |
| 		progress: p.progress,
 | |
| 	}, nil
 | |
| }
 | |
| 
 | |
| func (ps *pushStatus) update(ref string, delta int) {
 | |
| 	ps.l.Lock()
 | |
| 	status, ok := ps.statuses[ref]
 | |
| 	if ok {
 | |
| 		if delta > 0 {
 | |
| 			status.Offset += int64(delta)
 | |
| 		} else if delta < 0 {
 | |
| 			status.Offset = 0
 | |
| 		}
 | |
| 		ps.statuses[ref] = status
 | |
| 	}
 | |
| 	ps.l.Unlock()
 | |
| }
 | |
| 
 | |
| func (ps *pushStatus) add(ref string, d ocispec.Descriptor) {
 | |
| 	status := content.Status{
 | |
| 		Ref:       ref,
 | |
| 		Offset:    0,
 | |
| 		Total:     d.Size,
 | |
| 		StartedAt: time.Now(),
 | |
| 	}
 | |
| 	ps.l.Lock()
 | |
| 	_, ok := ps.statuses[ref]
 | |
| 	_, complete := ps.complete[d.Digest]
 | |
| 	if !ok && !complete {
 | |
| 		ps.statuses[ref] = status
 | |
| 	}
 | |
| 	ps.l.Unlock()
 | |
| }
 | |
| func (ps *pushStatus) markComplete(ref string, d ocispec.Descriptor) {
 | |
| 	ps.l.Lock()
 | |
| 	_, ok := ps.statuses[ref]
 | |
| 	if ok {
 | |
| 		delete(ps.statuses, ref)
 | |
| 	}
 | |
| 	ps.complete[d.Digest] = struct{}{}
 | |
| 	ps.l.Unlock()
 | |
| 
 | |
| }
 | |
| 
 | |
| func (ps *pushStatus) Status(name string) (content.Status, bool) {
 | |
| 	ps.l.Lock()
 | |
| 	status, ok := ps.statuses[name]
 | |
| 	ps.l.Unlock()
 | |
| 	return status, ok
 | |
| }
 | |
| 
 | |
| func (ps *pushStatus) Check(ctx context.Context, dgst digest.Digest) (bool, error) {
 | |
| 	ps.l.Lock()
 | |
| 	_, ok := ps.complete[dgst]
 | |
| 	ps.l.Unlock()
 | |
| 	return ok, nil
 | |
| }
 | |
| 
 | |
| func (p *progressPusher) Active(ctx context.Context, _ ...string) (ActiveJobs, error) {
 | |
| 	return p.status, nil
 | |
| }
 | |
| 
 | |
| func (p *progressPusher) Check(ctx context.Context, dgst digest.Digest) (bool, error) {
 | |
| 	return p.status.Check(ctx, dgst)
 | |
| }
 | |
| 
 | |
| type progressWriter struct {
 | |
| 	content.Writer
 | |
| 	ref      string
 | |
| 	desc     ocispec.Descriptor
 | |
| 	status   *pushStatus
 | |
| 	progress *ProgressTracker
 | |
| }
 | |
| 
 | |
| func (pw *progressWriter) Write(p []byte) (n int, err error) {
 | |
| 	n, err = pw.Writer.Write(p)
 | |
| 	if err != nil {
 | |
| 		// TODO: Handle reset error to reset progress
 | |
| 		return
 | |
| 	}
 | |
| 	pw.status.update(pw.ref, n)
 | |
| 	return
 | |
| }
 | |
| func (pw *progressWriter) Commit(ctx context.Context, size int64, expected digest.Digest, opts ...content.Opt) error {
 | |
| 	err := pw.Writer.Commit(ctx, size, expected, opts...)
 | |
| 	if err != nil {
 | |
| 		if errdefs.IsAlreadyExists(err) {
 | |
| 			pw.progress.MarkExists(pw.desc)
 | |
| 		}
 | |
| 		// TODO: Handle reset error to reset progress
 | |
| 	}
 | |
| 	pw.status.markComplete(pw.ref, pw.desc)
 | |
| 	return err
 | |
| }
 | 
